def GetMetadata(media, movie, TVDBid, TMDbid, IMDbid): Log.Info("=== TheMovieDb.GetMetadata() ===".ljust(157, '=')) TheMovieDb_dict = {} TSDbid = "" Log.Info("TVDBid: '{}', TMDbid: '{}', IMDbid: '{}'".format(TVDBid, TMDbid, IMDbid)) if TMDbid: url, filename = TMDB_MOVIE_SEARCH_BY_TMDBID.format(id=TMDbid), "TMDB-"+TMDbid+".json" elif IMDbid: url, filename = TMDB_MOVIE_SEARCH_BY_TMDBID.format(id=IMDbid), "IMDb-"+IMDbid+".json" elif TVDBid.isdigit(): url, filename = TMDB_SERIE_SEARCH_BY_TVDBID.format(id=TVDBid), "TVDB-"+TVDBid+".json" else: return TheMovieDb_dict, TSDbid, TMDbid, IMDbid mode = "movie" if movie else "tv" Log.Info(("--- %s ---" % mode).ljust(157, '-')) json = common.LoadFile(filename=filename, relativeDirectory=os.path.join('TheMovieDb', 'json'), url=url) config_dict = common.LoadFile(filename="TMDB_CONFIG_URL.json", relativeDirectory="TheMovieDb", url=TMDB_CONFIG_URL, cache=CACHE_1MONTH) image_base_url = Dict(config_dict, 'images', 'secure_base_url') if not json: Log.Info("TMDB - url: failed to get json" + TMDB_MOVIE_SEARCH_BY_TMDBID.format(id=TMDbid)) else: if Dict(json, 'tv_results' ): json, mode = json['tv_results' ][0], "tv" elif Dict(json, 'movie_results'): json, mode = json['movie_results'][0], "movie" Log.Info("[ ] title: {}" .format(SaveDict( Dict(json, 'title') or Dict(json, 'name'), TheMovieDb_dict, 'title' )))` Log.Info("[ ] rating: {}" .format(SaveDict( Dict(json, 'vote_average'), TheMovieDb_dict, 'rating' ))) #if 'vote_count' in json and json['vote_count'] > 3: SaveDict( Dict(json, 'vote_average'), TheMovieDb_dict, 'rating') Log.Info("[ ] tagline: {}" .format(SaveDict( Dict(json, 'tagline'), TheMovieDb_dict, 'tagline' ))) Log.Info("[ ] summary: {}" .format(SaveDict( Dict(json, 'overview'), TheMovieDb_dict, 'summary' ))) Log.Info("[ ] duration: {}" .format(SaveDict( Dict(json, 'runtime'), TheMovieDb_dict, 'duration' ))) Log.Info("[ ] countries: {}" .format(SaveDict( Dict(json, 'origin_country'), TheMovieDb_dict, 'countries' ))) Log.Info("[ ] originally_available_at: {}".format(SaveDict( Dict(json, 'first_air_date') or Dict(json, 'release_date'), TheMovieDb_dict, 'originally_available_at'))) if Dict(json, 'belongs_to_collection', 'name'): Log.Info("[ ] collections: {}".format(SaveDict( [ Dict(json, 'belongs_to_collection', 'name')], TheMovieDb_dict, 'collections'))) if Dict(json, 'genres' ): Log.Info("[ ] genres: {}" .format(SaveDict( sorted([ Dict(genre, 'name') for genre in Dict(json, 'genres', default=[]) ]), TheMovieDb_dict, 'genres' ))) if Dict(json, 'poster_path' ): Log.Info("[ ] poster: {}" .format(image_base_url + 'original' + json['poster_path'] )); SaveDict( (os.path.join('TheMovieDb', 'poster', json['poster_path' ].lstrip('/')), common.poster_rank('TheMovieDb', 'posters'), None), TheMovieDb_dict, 'posters', image_base_url + 'original' + json['poster_path'] ) if Dict(json, 'backdrop_path' ): Log.Info("[ ] art: {}" .format(image_base_url + 'original' + json['backdrop_path'])); SaveDict( (os.path.join('TheMovieDb', 'artwork', json['backdrop_path'].lstrip('/')), common.poster_rank('TheMovieDb', 'art' ), image_base_url + 'w300' + json['backdrop_path']), TheMovieDb_dict, 'art', image_base_url + 'original' + json['backdrop_path']) try: Log.Info("[ ] duration: {}".format(SaveDict( int(Dict(json, 'duration')) * 60 * 1000, TheMovieDb_dict, 'duration'))) except: pass if mode=='tv': TSDbid = str(Dict(json, 'id')) elif not TMDbid: TMDbid = str(Dict(json, 'id')) if not IMDbid: IMDbid = Dict(json, 'imdb_id') for studio in Dict(json, 'production_companies', default=[]): if studio['id'] <= json['production_companies'][0]['id']: Log.Info("[ ] studio: {}".format(SaveDict( studio['name'].strip(), TheMovieDb_dict, 'studio'))) ### More pictures ### Log.Info("--- pictures.more ---".ljust(157, '-')) Log.Info("TMDbid: '{}', TSDbid: '{}', IMDbid: '{}'".format(TMDbid, TSDbid, IMDbid)) for id in IMDbid.split(',') if ',' in IMDbid else []: json = common.LoadFile(filename="TMDB-"+(IMDbid or TMDbid)+".json", relativeDirectory="TMDB", url=TMDB_MOVIE_IMAGES_URL.format(id=id, mode=mode)) for index, poster in enumerate(Dict(json, 'posters', default=[])): if Dict(poster, 'file_path'): Log.Info("[ ] poster: {}" .format(image_base_url + 'original' + poster['file_path'] )); SaveDict((os.path.join('TheMovieDb', 'poster', "%s-%s.jpg" % (TMDbid, index)), common.poster_rank('TheMovieDb', 'posters'), None), TheMovieDb_dict, 'posters', image_base_url + 'original' + poster['file_path'] ) for index, backdrop in enumerate(Dict(json, 'backdrops', default=[])): if Dict(backdrop, 'file_path'): Log.Info("[ ] artwork: {}".format(image_base_url + 'original'+ backdrop['file_path'])); SaveDict((os.path.join('TheMovieDb', 'artwork', "%s-%s-art.jpg" % (TMDbid, index)), common.poster_rank('TheMovieDb', 'art'), image_base_url + 'w300' + backdrop['file_path']), TheMovieDb_dict, 'art', image_base_url + 'original' + backdrop['file_path']) Log.Info("--- return ---".ljust(157, '-')) Log.Info("TheMovieDb_dict: {}".format(DictString(TheMovieDb_dict, 4))) return TheMovieDb_dict, TSDbid, TMDbid, IMDbid
def GetMetadata(metadata, error_log, TVDBid, title): Log.Info("=== Plex.GetMetadata() ===".ljust(157, '=')) url = THEME_URL.format(TVDBid) Plex_dict = {} Log.Info("Prefs['themes']: '{}', TVDBid: '{}'".format( Prefs['themes'], TVDBid)) Log.Info("--- themes ---".ljust(157, '-')) if 'Plex' in Prefs['themes'] and TVDBid.isdigit(): title = title or TVDBid result = '*' if url in metadata.themes else common.GetStatusCode(url) Log.Info("result code: '{plex}', url: '{url}'".format(plex=result, url=url)) if result in (200, "*"): Log.Info("[ ] theme: {}".format( SaveDict(("Plex/%s.mp3" % TVDBid, 2, None), Plex_dict, 'themes', url))) else: error_log['Plex themes missing'].append( "TVDBid: '{}' | Title: '{}' | {}".format( common.WEB_LINK % (common.TVDB_SERIE_URL + TVDBid, title), title, common.WEB_LINK % ("mailto:[email protected]?cc=&subject=Missing%%20theme%%20song%%20-%%20'%s%%20-%%20%s.mp3'" % (title, TVDBid), 'Upload'))) else: Log.Info( "Not pulling meta - 'Plex' in Prefs['themes']: '{}', TVDBid: '{}'". format('Plex' in Prefs['themes'], TVDBid)) Log.Info("--- return ---".ljust(157, '-')) Log.Info("Plex_dict: {}".format(DictString(Plex_dict, 1))) return Plex_dict
def GetMetadata(metadata, title1, title2): Log.Info("=== TVTunes.GetMetadata() ===".ljust(157, '=')) THEME_URL = "http://www.televisiontunes.com/uploads/audio/{}.mp3" TVTunes_dict = {} Log.Info("Prefs['themes']: '{}', title: '{}', title2: '{}'".format( Prefs['themes'], title1, title2)) Log.Info("--- themes ---".ljust(157, '-')) if 'TVTunes' in Prefs['themes'] and (title1 or title2): for url in [ THEME_URL.format(String.Quote(t)) for t in (title1, title2) if t ]: result = '*' if url in metadata.themes or Data.Exists( url.split('/')[-1]) else common.GetStatusCode(url) Log.Info("Return code: '{}', url: '{}'".format(result, url)) if result in (200, "*"): Log.Info("[ ] theme: {}".format( SaveDict( ("TelevisionTunes/" + url.split('/')[-1], 1, None), TVTunes_dict, 'themes', url))) Log.Info("--- return ---".ljust(157, '-')) Log.Info("TVTunes_dict: {}".format(DictString(TVTunes_dict, 1))) return TVTunes_dict
def GetMetadata(AniDBid, MALid): Log.Info("=== AniList.GetMetadata() ===".ljust(157, '=')) AniList_dict = {} # Try to match the AniDB id to an AniList id as it has a higher chance of being correct ALid = Dict(common.LoadFile(filename=AniDBid + '.json', relativeDirectory=os.path.join( 'AniList', 'json', 'AniDBid'), url=ARM_SERVER_URL.format(id=AniDBid)), "anilist", default=None) Log.Info("AniDBid={}, MALid={}, ALid={}".format(AniDBid, MALid, ALid)) if not MALid or not MALid.isdigit(): return AniList_dict Log.Info("--- series ---".ljust(157, "-")) # Use the AniList id if we got one, but fall back to the MAL id variables = {} if ALid is not None: SaveDict(ALid, variables, "id") else: SaveDict(int(MALid), variables, "malId") # Fetch data data = MakeGraphqlQuery(ANIME_DATA_DOCUMENT, variables) if data: Log.Info("--- images ---".ljust(157, "-")) posterUrl = Dict(data, "anime", "coverImage", "url") if posterUrl: Log.Info("[ ] poster: {}".format(posterUrl)) SaveDict((os.path.join('AniList', 'poster', os.path.basename(posterUrl)), common.poster_rank('AniList', 'posters'), None), AniList_dict, 'posters', posterUrl) bannerUrl = Dict(data, "anime", "bannerImage") if bannerUrl: Log.Info("[ ] banner: {}".format(bannerUrl)) SaveDict((os.path.join('AniList', 'banners', os.path.basename(bannerUrl)), common.poster_rank('AniList', 'banners'), None), AniList_dict, 'banners', bannerUrl) Log.Info("--- return ---".ljust(157, '-')) Log.Info("AniList_dict: {}".format(DictString(AniList_dict, 4))) return AniList_dict
def GetMetadata(media, movie): Log.Info("=== Local.GetMetadata() ===".ljust(157, '=')) Local_dict = {} dir = common.GetMediaDir(media, movie) library, root, path = common.GetLibraryRootPath(dir) if movie: return Local_dict Log.Info("dir: {}".format(dir)) Log.Info("library: {}".format(library)) Log.Info("root: {}".format(root)) Log.Info("path: {}".format(path)) if not path in ('_unknown_folder', '.'): series_root_folder = os.path.join(root, path.split(os.sep, 1)[0]) Log.Info("series_root_folder: {}".format(series_root_folder)) Log.Info("Grouping folder: {}".format( os.path.basename(series_root_folder))) if not os.path.exists(series_root_folder): Log.Info('files are currently inaccessible') return Local_dict subfolder_count = len([ file for file in os.listdir(series_root_folder) if os.path.isdir(os.path.join(series_root_folder, file)) ]) Log.Info("subfolder_count: {}".format(subfolder_count)) ### Extract season and transparent folder to reduce complexity and use folder as serie name ### reverse_path, season_folder_first = list(reversed(path.split( os.sep))), False for folder in reverse_path[: -1]: # remove root folder from test, [:-1] Doesn't thow errors but gives an empty list if items don't exist, might not be what you want in other cases for rx in SEASON_RX: # in anime, more specials folders than season folders, so doing it first if re.search( rx, folder, re.IGNORECASE ): # get season number but Skip last entry in seasons (skipped folders) reverse_path.remove( folder ) # Since iterating slice [:] or [:-1] doesn't hinder iteration. All ways to remove: reverse_path.pop(-1), reverse_path.remove(thing|array[0]) if rx != SEASON_RX[-1] and len( reverse_path) >= 2 and folder == reverse_path[-2]: season_folder_first = True break Log.Info("reverse_path: {}".format(reverse_path)) Log.Info("season_folder_first: {}".format(season_folder_first)) if len( reverse_path ) > 1 and not season_folder_first and subfolder_count > 1: ### grouping folders only ### Log.Info("[ ] collection (Grouping folder): {}".format( SaveDict([reverse_path[-1]], Local_dict, 'collections'))) else: Log.Info("Grouping folder not found") Log.Info("--- return ---".ljust(157, '-')) Log.Info("Local_dict: {}".format(DictString(Local_dict, 1))) return Local_dict
def GetMetadata(media, movie, error_log, id): Log.Info("=== AnimeLists.GetMetadata() ===".ljust(157, '=')) mappingList, AnimeLists_dict = {}, {} found = False source, id = id.split('-', 1) if '-' in id else ("", id) AniDB_id = id if source.startswith('anidb') else "" TVDB_id = id if source.startswith('tvdb') else "" TMDB_id = id if source.startswith('tmdb') else "" IMDB_id = id if source.startswith('imdb') else "" AniDBid = "" TVDBid = "" TMDBid = "" IMDBid = "" tvdb_numbering = True if not movie and ( TVDB_id or AniDB_id and max(map(int, media.seasons.keys())) > 1) else False tvdbcounts = {} ### Search for match ### Log.Info("tvdb_numbering: {}".format(tvdb_numbering)) AniDB_id2, TVDB_id2 = "", "" AniDBTVDBMapCustom = GetAniDBTVDBMapCustom(media, movie) if AniDBTVDBMapCustom: AniDBTVDBMapFull = MergeMaps(AniDBTVDBMap, AniDBTVDBMapCustom) else: AniDBTVDBMapFull = AniDBTVDBMap def anime_core(anime): defaulttvdbseason = anime.get('defaulttvdbseason') if anime.get( 'defaulttvdbseason' ) and anime.get('defaulttvdbseason') != 'a' else '1' episodeoffset = anime.get('episodeoffset') if anime.get( 'episodeoffset') else '0' s1_mapping_count = len( anime.xpath( "mapping-list/mapping[@anidbseason='1'][@tvdbseason='0' or @tvdbseason='1']" )) s1e1_mapping = True if anime.xpath( "mapping-list/mapping[@anidbseason='1'][@tvdbseason='1'][contains(text(), '-1;')]" ) else False is_primary_series = True if defaulttvdbseason == '1' and episodeoffset == '0' and ( s1_mapping_count == 0 or s1e1_mapping) else False return defaulttvdbseason, episodeoffset, s1_mapping_count, is_primary_series Log.Info("--- AniDBTVDBMap ---".ljust(157, '-')) forcedID = { 'anidbid': AniDB_id, 'tvdbid': TVDB_id, 'tmdbid': TMDB_id, 'imdbid': IMDB_id } for anime in AniDBTVDBMapFull.iter('anime') if AniDBTVDBMapFull else []: # gather any manually specified source ids foundID, wantedID = {}, {} for check in forcedID.keys(): foundID[check] = anime.get(check, "") wantedID[check] = True if foundID[check] == forcedID[ check] and forcedID[check] != '' else False # if this row matches our specified source-id if True in wantedID.values(): # save the found values for later use in other GetMetadata that dont depend on AniDB etc. IMDBid, TMDBid, TVDBid, AniDBid = foundID['imdbid'], foundID[ 'tmdbid'], foundID['tvdbid'], foundID['anidbid'] # use the old check to decide whether to proceed if TVDBid == '' and AniDBid == '': continue # nothing found, skip else: continue found = True # record the number of entries using the same tvdb id SaveDict(Dict(tvdbcounts, TVDBid, default=0) + 1, tvdbcounts, TVDBid) defaulttvdbseason, episodeoffset, s1_mapping_count, is_primary_series = anime_core( anime) if not tvdb_numbering and not TVDB_id: TVDB_id2 = TVDBid if tvdb_numbering and AniDBid and TVDBid.isdigit( ) and is_primary_series and not AniDB_id: AniDB_id2 = AniDBid Log.Info( "[+] AniDBid: {:>5}, TVDBid: {:>6}, defaulttvdbseason: {:>4}, offset: {:>3}, TMDBid: {:>7}, IMDBid: {:>10}, name: {}" .format( AniDBid, TVDBid, ("({})".format(anime.get('defaulttvdbseason')) if anime.get('defaulttvdbseason') != defaulttvdbseason else '') + defaulttvdbseason, episodeoffset, TMDBid, IMDBid, GetXml(anime, 'name'))) ### AniDB/TMDB/IMDB numbered series ### if AniDB_id or TMDB_id or IMDB_id: AniDB_id2 = AniDBid # Needs to be set if TMDB/IMDB TVDB_id2 = TVDBid SaveDict(TMDBid, mappingList, 'tmdbid') SaveDict(IMDBid, mappingList, 'imdbid') SaveDict(defaulttvdbseason, mappingList, 'defaulttvdbseason') SaveDict(True if anime.get('defaulttvdbseason') == 'a' else False, mappingList, 'defaulttvdbseason_a') SaveDict(episodeoffset, mappingList, 'episodeoffset') SaveDict(GetXml(anime, 'name'), mappingList, 'name') SaveDict(GetXml(anime, "supplemental-info/studio"), AnimeLists_dict, 'studio') SaveDict(GetXml(anime, "supplemental-info/director"), AnimeLists_dict, 'director') SaveDict(GetXml(anime, "supplemental-info/credits"), AnimeLists_dict, 'writer') for genre in anime.xpath('supplemental-info/genre'): SaveDict([genre.text], AnimeLists_dict, 'genres') for art in anime.xpath('supplemental-info/fanart/thumb'): SaveDict( { art.text: ('/'.join( art.text.split('/')[3:]), 1, art.get('preview')) }, AnimeLists_dict, 'art') ### TheTVDB/multi-season numbered series and the Primary/Starting(s1e1) AniDB id ### if (TVDB_id or not movie and max(map(int, media.seasons.keys())) > 1 and AniDB_id == '') and TVDBid.isdigit() and is_primary_series: AniDB_id2 = AniDBid SaveDict(TMDBid, mappingList, 'tmdbid') SaveDict(IMDBid, mappingList, 'imdbid') SaveDict(defaulttvdbseason, mappingList, 'defaulttvdbseason') SaveDict(True if anime.get('defaulttvdbseason') == 'a' else False, mappingList, 'defaulttvdbseason_a') ### if TVDBid.isdigit(): SaveDict(episodeoffset, mappingList, 'TVDB', 's-1' if defaulttvdbseason == '0' and s1_mapping_count >= 1 else 's' + defaulttvdbseason, AniDBid) #mappingList['TVDB'][s1][anidbid]=episodeoffset SaveDict( { 'min': defaulttvdbseason, 'max': defaulttvdbseason }, mappingList, 'season_map', AniDBid) # Set the min/max season to the 'defaulttvdbseason' if source == "tvdb6" and int(episodeoffset) > 0: SaveDict( { 'min': '0', 'max': '0' }, mappingList, 'season_map', AniDBid ) # Force series as special if not starting the TVDB season for season in anime.iter( 'mapping' ): ### mapping list: <mapping-list> <mapping anidbseason="0" tvdbseason="0">;1-12;2-14;3-16;4-18;</mapping> </mapping-list> anidbseason, tvdbseason, offset, start, end = season.get( 'anidbseason'), season.get( 'tvdbseason'), season.get('offset') or '0', season.get( 'start'), season.get('end') Log.Info( " - season: [{:>2}], [{:>2}], range: [{:>3}-{:>3}], offset: {:>3}, text: {}" .format(anidbseason, tvdbseason, start or '000', end or '000', offset, (season.text or '').strip(';'))) for ep in range(int(start), int(end) + 1) if start else []: #Log.Info("[?] start: {}, end: {}, ep: {}".format(start, end, ep)) if not Dict(mappingList, 'TVDB', 's' + tvdbseason + 'e' + str(ep + int(offset))): SaveDict( (anidbseason, str(ep), AniDBid), mappingList, 'TVDB', 's' + tvdbseason + 'e' + str(ep + int(offset)) ) #mappingList['TVDB'][s1e1]=(AniDB_season, AniDB_episode, AniDBid) for start-end mappings #else: Log.Info("already present") for ep in filter( None, season.text.split(';')) if season.text else []: if not '-' in ep: Log.Info( '[!] MAPPING ERROR, season.text: "{}", ep mapping missing hyphen: "{}"' .format(season.text, ep)) elif not Dict(mappingList, 'TVDB', 's' + tvdbseason + 'e' + ep.split('-')[1]): SaveDict( (anidbseason, ep.split('-')[0], AniDBid), mappingList, 'TVDB', 's' + tvdbseason + 'e' + ep.split('-')[1] ) #mappingList['TVDB'][s1e1]=(AniDB_season, AniDB_episode, AniDBid) for manual mapping like '1-12' #elif '-' not in (mappingList, 'TVDB', 's'+tvdbseason+'e'+ep.split('-')[1]): # SaveDict((anidbseason, Dict(mappingList, 'TVDB', 's'+tvdbseason+'e'+ep.split('-')[1])[1]+'-'+ep.split('-')[0], AniDBid), mappingList, 'TVDB', 's'+tvdbseason+'e'+ep.split('-')[1]) # Log.Info("already present so converting to range but range not supported") if int(Dict(mappingList, 'season_map', AniDBid, 'max')) < int( season.get("tvdbseason")): SaveDict( season.get("tvdbseason"), mappingList, 'season_map', AniDBid, 'max' ) # Update the max season to the largest 'tvdbseason' season seen in 'mapping-list' ### if TVDBid == "hentai": SaveDict("X", AnimeLists_dict, 'content_rating') elif TVDBid in ("", "unknown", None): link = SCHUDLEE_FEEDBACK.format( title="aid:%s '%s' TVDBid:" % (AniDB_id, "title"), body=String.StripTags( XML.StringFromElement(anime, encoding='utf8'))) error_log['anime-list TVDBid missing'].append( 'AniDBid: "{}" | Title: "{}" | Has no matching TVDBid "{}" in mapping file | <a href="{}" target="_blank">Submit bug report</a>' .format(AniDB_id, "title", TVDBid, link)) Log.Info( '"anime-list TVDBid missing.htm" log added as tvdb serie id missing in mapping file: "{}"' .format(TVDBid)) # guid need 1 entry only, not an TheTVDB numbered serie with anidb guid if (AniDB_id or TMDB_id or IMDB_id) and (movie or max(map(int, media.seasons.keys())) <= 1): break else: # Loop has gone through all entries. This only happens when the exact entry is not found or a TVDB entry that needs to loop through all. if not found: Log.Info("ERROR: Could not find %s: %s" % (source, id)) if AniDB_id != "": error_log['anime-list AniDBid missing'].append( "AniDBid: " + common.WEB_LINK % (common.ANIDB_SERIE_URL + AniDB_id, AniDB_id)) # Reset the variables used for matching so it does not just keep the value of the last entry in the loop IMDBid, TMDBid, TVDBid, AniDBid = '', '', '', '' AniDB_winner = AniDB_id or AniDB_id2 TVDB_winner = TVDB_id or TVDB_id2 Log.Info(' ----- ------') Log.Info(' {:>5} {:>6}'.format( AniDB_winner, TVDB_winner)) SaveDict(Dict(tvdbcounts, TVDB_winner), mappingList, 'tvdbcount') if source == "tvdb": for s in media.seasons: for e in media.seasons[s].episodes: if int(e) > 100: SaveDict(True, mappingList, 'possible_anidb3') break else: SaveDict(False, mappingList, 'possible_anidb3') else: SaveDict(False, mappingList, 'possible_anidb3') for values in Dict(mappingList, 'TVDB', default={}).values(): if isinstance(values, tuple) and values[0] == '1' and values[1] == '1': SaveDict(True, mappingList, 's1e1_mapped') break else: SaveDict(False, mappingList, 's1e1_mapped') ### Update collection/studio TVDB_collection, title, studio = [], '', '' for anime in AniDBTVDBMapFull.iter( 'anime') if AniDBTVDBMapFull and TVDB_winner.isdigit() else []: if anime.get('tvdbid', "") == TVDB_winner: TVDB_collection.append(anime.get("anidbid", "")) if anime_core(anime)[3]: #[3]==is_primary_series title = AniDB.GetAniDBTitle( AniDB.AniDBTitlesDB.xpath( '/animetitles/anime[@aid="{}"]/title'.format( anime.get("anidbid", ""))))[ 0] #returns [title, main, language_rank] studio = GetXml(anime, "supplemental-info/studio") if len( TVDB_collection ) > 1 and title: # Require that there be at least 2 anidb mappings for a collection Log.Info( "[ ] collection: TVDBid '%s' is part of collection: '%s', related_anime_list: %s" % (TVDB_winner, SaveDict([title + ' Collection'], AnimeLists_dict, 'collections'), TVDB_collection)) else: Log.Info("[ ] collection: TVDBid '%s' is not part of any collection" % TVDB_winner) Log.Info("[ ] studio: {}".format( SaveDict(studio, AnimeLists_dict, 'studio'))) Log.Info("--- return ---".ljust(157, '-')) Log.Info( "AniDB_id: '{}', AniDB_id2: '{}', AniDBid: '{}', TVDB_id: '{}', TVDB_id2: '{}', TVDBid: '{}'" .format(AniDB_id, AniDB_id2, AniDBid, TVDB_id, TVDB_id2, TVDBid)) Log.Info("mappingList: {}".format(DictString(mappingList, 1))) Log.Info("AnimeLists_dict: {}".format(DictString(AnimeLists_dict, 1))) return AnimeLists_dict, AniDB_winner, TVDB_winner if TVDB_winner.isdigit( ) else "", Dict(mappingList, 'tmdbid'), Dict(mappingList, 'imdbid'), mappingList
def GetMetadata(media, movie, error_log, id, AniDBMovieSets): MAPPING_FEEDBACK = 'http://github.com/ScudLee/anime-lists/issues/new?title=%s&body=%s' # ScudLee mapping file git feedback url mappingList, AnimeLists_dict = {}, {} #mappingList['poster_id_array'] = {} tmdbid, imdbid, found = '', '', False source, id = id.split('-', 1) if '-' in id else ("", id) AniDB_id = id if source.startswith('anidb') else "" TVDB_id = id if source.startswith('tvdb') else "" tvdb_numbering = True if not movie and ( TVDB_id or AniDB_id and max(map(int, media.seasons.keys())) > 1) else False ### Search for match ### Log.Info("".ljust(157, '-')) Log.Info( "AnimeLists.GetMetadata() - tvdb_numbering: {}".format(tvdb_numbering)) AniDB_id2, TVDB_id2 = '', '' for anime in AniDBTVDBMap.iter('anime') if AniDBTVDBMap else []: if (anime.get("anidbid", "") == '' or anime.get("anidbid", "") != AniDB_id) and (anime.get( 'tvdbid', "") == '' or anime.get('tvdbid', "") != TVDB_id): continue AniDBid = anime.get("anidbid", "") TVDBid = anime.get('tvdbid', "") found = True if not tvdb_numbering and not TVDB_id: TVDB_id = TVDBid if tvdb_numbering and AniDBid and TVDBid.isdigit() and anime.get( 'defaulttvdbseason') == '1' and anime.get('episodeoffset') in ( '', None, '0') and not AniDB_id: AniDB_id2 = AniDBid Log.Info( "[+] AniDBid: {:>5}, TVDBid: {:>6}, defaulttvdbseason: {:>2}, offset: {:>3}, name: {}" .format(AniDBid, TVDBid, anime.get('defaulttvdbseason'), anime.get('episodeoffset') or '0', GetXml(anime, 'name'))) ### Anidb numbered serie ### if AniDB_id: # or defaulttvdbseason=='1': SaveDict(anime.get('tmdbid', ""), mappingList, 'tmdbid') SaveDict(anime.get('imdbid', ""), mappingList, 'imdbid') SaveDict(anime.get('defaulttvdbseason'), mappingList, 'defaulttvdbseason') SaveDict( anime.get('episodeoffset') or '0', mappingList, 'episodeoffset') SaveDict(GetXml(anime, 'name'), mappingList, 'name') SaveDict(GetXml(anime, 'studio'), AnimeLists_dict, 'studio') SaveDict(GetXml(anime, "supplemental-info/director"), AnimeLists_dict, 'director') SaveDict(GetXml(anime, "supplemental-info/credits"), AnimeLists_dict, 'writer') for genre in anime.xpath('supplemental-info/genre'): SaveDict([genre.text], AnimeLists_dict, 'genres') for art in anime.xpath('supplemental-info/fanart/thumb'): SaveDict( { art.text: ('/'.join( art.text.split('/')[3:]), 1, art.get('preview')) }, AnimeLists_dict, 'art') ### TheTVDB numbered series ### if TVDB_id or not movie and max(map(int, media.seasons.keys( ))) > 1 and AniDB_id == '': #In case AniDB guid but multiple seasons if TVDBid.isdigit(): if anime.get('defaulttvdbseason'): if anime.get('defaulttvdbseason') in ['a', '1']: SaveDict(anime.get('defaulttvdbseason'), mappingList, 'defaulttvdbseason') AniDB_id2 = AniDBid SaveDict( anime.get('episodeoffset') or '0', mappingList, 'TVDB', 's' + anime.get('defaulttvdbseason'), AniDBid ) #mappingList['TVDB'][s1][anidbid]=episodeoffset SaveDict( { 'min': anime.get('defaulttvdbseason'), 'max': anime.get('defaulttvdbseason') }, mappingList, 'season_map', AniDBid) for season in anime.iter( 'mapping' ): ### mapping list: <mapping-list> <mapping anidbseason="0" tvdbseason="0">;1-12;2-14;3-16;4-18;</mapping> </mapping-list> anidbseason, tvdbseason, offset, start, end = season.get( 'anidbseason'), season.get('tvdbseason'), season.get( 'offset') or '0', season.get('start'), season.get( 'end') Log.Info( " - season: [{:>2}], [{:>2}], range: [{:>3}-{:>3}], offset: {:>3}, text: {}" .format(anidbseason, tvdbseason, start or '000', end or '000', offset, (season.text or '').strip(';'))) for ep in range(int(start), int(end) + 1) if start else []: #Log.Info("[?] start: {}, end: {}, ep: {}".format(start, end, ep)) if not Dict( mappingList, 'TVDB', 's' + tvdbseason + 'e' + str(ep + int(offset))): SaveDict( (anidbseason, ep, AniDBid), mappingList, 'TVDB', 's' + tvdbseason + 'e' + str(ep + int(offset)) ) #mappingList['TVDB'][s1e1]=(AniDB_season, AniDB_episode, AniDBid) for start-end mappings #else: Log.Info("already present") for ep in filter( None, season.text.split(';')) if season.text else []: if not Dict(mappingList, 'TVDB', 's' + tvdbseason + 'e' + ep.split('-')[1]): SaveDict( (anidbseason, ep.split('-')[0], AniDBid), mappingList, 'TVDB', 's' + tvdbseason + 'e' + ep.split('-')[1] ) #mappingList['TVDB'][s1e1]=(AniDB_season, AniDB_episode, AniDBid) for manual mapping like '1-12' #elif '-' not in (mappingList, 'TVDB', 's'+tvdbseason+'e'+ep.split('-')[1]): # SaveDict((anidbseason, Dict(mappingList, 'TVDB', 's'+tvdbseason+'e'+ep.split('-')[1])[1]+'-'+ep.split('-')[0], AniDBid), mappingList, 'TVDB', 's'+tvdbseason+'e'+ep.split('-')[1]) # Log.Info("already present so converting to range but range not supported") if Dict(mappingList, 'season_map', AniDBid, 'max').isdigit() and int( Dict(mappingList, 'season_map', AniDBid, 'max')) < int(season.get("tvdbseason")): SaveDict( season.get("tvdbseason"), mappingList, 'season_map', AniDBid, 'max' ) # Update the max season to the largest 'tvdbseason' season seen in 'mapping-list' elif TVDBid == "hentai": SaveDict("X", AnimeLists_dict, 'content_rating') elif TVDBid in ("", "unknown", None): error_log['anime-list TVDBid missing'].append( "AniDBid: %s | Title: '%s' | Has no matching TVDBid ('%s') in mapping file | " % (AniDB_id, "title", TVDBid) + common.WEB_LINK % (MAPPING_FEEDBACK % ("aid:%s '%s' TVDBid:" % (AniDB_id, "title"), String.StripTags( XML.StringFromElement(anime, encoding='utf8'))), "Submit bug report")) Log.Warn( "'anime-list TVDBid missing.htm' log added as tvdb serie id missing in mapping file: '%s'" % TVDBid) #AniDB guid need 1 AniDB xml only, not an TheTVDB numbered serie with anidb guid (not anidb2 since seen as TheTVDB) if AniDB_id and (movie or max(map(int, media.seasons.keys())) <= 1): break else: #Log.Info('#2 - TVDB_id: {}, TVDBid: {}'.format(TVDB_id, TVDBid)) if not found: Log.Error("source '{}', id: '{}' not found in file".format( source, id)) error_log['anime-list AniDBid missing'].append( "AniDBid: " + common.WEB_LINK % (common.ANIDB_SERIE_URL + AniDBid, AniDBid)) AniDBid, TVDBid = '', '' Log.Info(' ----- ------') Log.Info(' {:>5} {:>6}'.format( AniDB_id or AniDB_id2 or AniDBid, TVDB_id or TVDBid)) #Log.Info('[=] mappingList: {}'.format(mappingList)) ### Update collection #for element in AniDBMovieSets.iter("anime") if AniDBMovieSets else []: # if element.get('AniDBid')==AniDB_id or TVDBid in mappingList['TVDB'] and element.get('AniDBid') in mappingList['TVDB']: # node = element.getparent() # title, main = GetAniDBTitle(node.xpath('titles')[0]) # SaveDict(title, AnimeLists_dict, 'collection') # Log.Info("AnimeLists.GetMetadata() - AniDBid '%s' is part of movie collection: '%s'" % (AniDBid, title)) # break #else TVDB_collection, title = [], '' for anime in AniDBTVDBMap.iter('anime') if AniDBTVDBMap else []: if anime.get('tvdbid', "") == TVDB_id: TVDB_collection.append(anime.get("anidbid", "")) if anime.get('defaulttvdbseason') == '1' and anime.get( 'episodeoffset') == '': title = GetXml(anime, 'name') if len(TVDB_collection) > 1 and title: SaveDict(title + ' Collection', AnimeLists_dict, 'collection') Log.Info("mappingList: {}".format(mappingList)) return AnimeLists_dict, AniDB_id or AniDB_id2 or AniDBid, ( TVDB_id or TVDBid) if ( TVDB_id or TVDBid).isdigit() else "", tmdbid, imdbid, mappingList
def GetMetadata( movie=False, TVDBid="", tmdbid="", imdbid="", season=0, num=100): #Fetch from fanart.tv - Posters Seasons Fanarts Banner API_MOVIES_URL = 'http://webservice.fanart.tv/v3/movies/{id}?api_key={api_key}' API_TV_URL = 'http://webservice.fanart.tv/v3/tv/{id}?api_key={api_key}' API_KEY = 'cfa9dc054d221b8d107f8411cd20b13f' FanartTV_dict = {} Log.Info("".ljust(157, '-')) Log.Info( "FanartTv.GetMetadata() - movie:'{movie}', TVDBid: '{TVDBid}', tmdbid: '{tmdbid}', imdbid:'{imdbid}', season: '{season}', num: '{num}'" .format(movie=movie, TVDBid=TVDBid, tmdbid=tmdbid, imdbid=imdbid, season=season, num=num)) if "," in imdbid: (GetMetadata(metadata, movie, "", "", imdbid_unique, season, num) for imdbid_unique in (tmdbid or imdbid).split(",")) return #recusive call for each imdbid to reduce complexity if "," in tmdbid: (GetMetadata(metadata, movie, "", tmdbid_unique, "", season, num) for tmdbid_unique in tmdbid.split(",")) return #recusive call for each tmdbid to reduce complexity if not movie and TVDBid.isdigit(): id, relativeDirectory, url = TVDBid, "FanartTV/tv/" + TVDBid, API_TV_URL.format( id=TVDBid, api_key=API_KEY) elif movie and (imdbid or tmdbid): id, relativeDirectory, url = imdbid or tmdbid, "FanartTV/movie/" + imdbid or tmdbid, API_MOVIES_URL.format( id=imdbid or tmdbid, api_key=API_KEY) else: return if TVDBid or tmdbid or imdbid: json = common.LoadFile(filename=id + ".json", relativeDirectory=relativeDirectory, url=url, cache=CACHE_1WEEK) #Movies if json and (imdbid or tmdbid): for item in Dict(json, 'movieposter') or []: SaveDict((relativeDirectory + "{id}/movieposter/{filename}.jpg".format( id=id, filename=item['id']), num, None), FanartTV_dict, 'posters', item['url']) for item in Dict(json, 'moviebackground') or []: SaveDict((relativeDirectory + "{id}/moviebackground/{filename}.jpg".format( id=id, filename=item['id']), num, None), FanartTV_dict, 'art', item['url']) #Series if json and TVDBid.isdigit(): for item in Dict(json, 'tvposter') or []: SaveDict( (relativeDirectory + "{id}/tvposter/{filename}.jpg".format( id=id, filename=item['id']), num, None), FanartTV_dict, 'posters', item['url']) for item in Dict(json, 'showbackground') or []: SaveDict((relativeDirectory + "{id}/showbackground/{filename}.jpg".format( id=id, filename=item['id']), num, None), FanartTV_dict, 'art', item['url']) for item in Dict(json, 'tvbanner') or []: SaveDict( (relativeDirectory + "{id}/tvbanner/{filename}.jpg".format( id=id, filename=item['id']), num, None), FanartTV_dict, 'banners', item['url']) for item in Dict(json, 'seasonposter') or []: SaveDict((relativeDirectory + "{id}/seasonposter/{filename}.jpg".format( id=id, filename=item['id']), num, None), FanartTV_dict, 'seasons', item['season'], 'posters', item['url']) return FanartTV_dict
def GetMetadata(media, movie, error_log, id): Log.Info("=== AnimeLists.GetMetadata() ===".ljust(157, '=')) MAPPING_FEEDBACK = 'http://github.com/ScudLee/anime-lists/issues/new?title=%s&body=%s' # ScudLee mapping file git feedback url mappingList, AnimeLists_dict = {}, {} #mappingList['poster_id_array'] = {} found = False source, id = id.split('-', 1) if '-' in id else ("", id) AniDB_id = id if source.startswith('anidb') else "" TVDB_id = id if source.startswith('tvdb') else "" TMDB_id = id if source.startswith('tmdb') else "" AniDBid = "" TVDBid = "" TMDBid = "" IMDBid = "" tvdb_numbering = True if not movie and ( TVDB_id or AniDB_id and max(map(int, media.seasons.keys())) > 1) else False tvdbcounts = {} ### Search for match ### Log.Info("tvdb_numbering: {}".format(tvdb_numbering)) AniDB_id2, TVDB_id2 = "", "" Log.Info("--- AniDBTVDBMap ---".ljust(157, '-')) forcedID = { 'anidbid': AniDB_id, 'tvdbid': TVDB_id, 'tmdbid': TMDB_id, "imdbid": "" } for anime in AniDBTVDBMap.iter('anime') if AniDBTVDBMap else []: # gather any manually specified source ids foundID, wantedID = {}, {} for check in forcedID.keys(): foundID[check] = anime.get(check, "") wantedID[check] = True if foundID[check] == forcedID[ check] and forcedID[check] != '' else False # if this row matches our specified source-id if True in wantedID.values(): # save the found values for later use in other GetMetadata that dont depend on AniDB etc. IMDBid, TMDBid, TVDBid, AniDBid = foundID['imdbid'], foundID[ 'tmdbid'], foundID['tvdbid'], foundID['anidbid'] # use the old check to decide whether to proceed if TVDBid == '' and AniDBid == '': continue # nothing found, skip else: continue # record the number of entries using the same tvdb id SaveDict(Dict(tvdbcounts, TVDBid, default=0) + 1, tvdbcounts, TVDBid) found = True if not tvdb_numbering and not TVDB_id: TVDB_id = TVDBid if tvdb_numbering and AniDBid and TVDBid.isdigit( ) and anime.get('defaulttvdbseason') in [ 'a', '1' ] and anime.get('episodeoffset') in ['', '0'] and len( anime.xpath( "mapping-list/mapping[@anidbseason='1'][@tvdbseason='0']") ) == 0 and not AniDB_id: AniDB_id2 = AniDBid Log.Info( "[+] AniDBid: {:>5}, TVDBid: {:>6}, defaulttvdbseason: {:>2}, offset: {:>3}, name: {}" .format(AniDBid, TVDBid, anime.get('defaulttvdbseason'), anime.get('episodeoffset') or '0', GetXml(anime, 'name'))) ### Anidb numbered serie ### if AniDB_id: # or defaulttvdbseason=='1': SaveDict(anime.get('tmdbid', ""), mappingList, 'tmdbid') SaveDict(anime.get('imdbid', ""), mappingList, 'imdbid') SaveDict(anime.get('defaulttvdbseason'), mappingList, 'defaulttvdbseason') SaveDict( anime.get('episodeoffset') or '0', mappingList, 'episodeoffset') SaveDict(GetXml(anime, 'name'), mappingList, 'name') SaveDict(GetXml(anime, 'studio'), AnimeLists_dict, 'studio') SaveDict(GetXml(anime, "supplemental-info/director"), AnimeLists_dict, 'director') SaveDict(GetXml(anime, "supplemental-info/credits"), AnimeLists_dict, 'writer') for genre in anime.xpath('supplemental-info/genre'): SaveDict([genre.text], AnimeLists_dict, 'genres') for art in anime.xpath('supplemental-info/fanart/thumb'): SaveDict( { art.text: ('/'.join( art.text.split('/')[3:]), 1, art.get('preview')) }, AnimeLists_dict, 'art') ### TheTVDB numbered series ### if TVDB_id or not movie and max(map(int, media.seasons.keys( ))) > 1 and AniDB_id == '': #In case AniDB guid but multiple seasons if TVDBid.isdigit(): if anime.get('defaulttvdbseason'): if anime.get('defaulttvdbseason') in [ 'a', '1' ] and anime.get('episodeoffset') in ['', '0'] and len( anime.xpath( "mapping-list/mapping[@anidbseason='1'][@tvdbseason='0']" )) == 0: SaveDict(anime.get('defaulttvdbseason'), mappingList, 'defaulttvdbseason') AniDB_id2 = AniDBid SaveDict( anime.get('episodeoffset') or '0', mappingList, 'TVDB', 's-1' if anime.get('defaulttvdbseason') == '0' and len( anime.xpath( "mapping-list/mapping[@anidbseason='1'][@tvdbseason='0']" )) >= 1 else 's' + anime.get('defaulttvdbseason'), AniDBid ) #mappingList['TVDB'][s1][anidbid]=episodeoffset SaveDict( { 'min': anime.get('defaulttvdbseason'), 'max': anime.get('defaulttvdbseason') }, mappingList, 'season_map', AniDBid ) # Set the min/max season to the 'defaulttvdbseason' if source == "tvdb6" and anime.get( 'episodeoffset').isdigit() and int( anime.get('episodeoffset')) > 0: SaveDict( { 'min': '0', 'max': '0' }, mappingList, 'season_map', AniDBid ) # Force series as special if not starting the TVDB season for season in anime.iter( 'mapping' ): ### mapping list: <mapping-list> <mapping anidbseason="0" tvdbseason="0">;1-12;2-14;3-16;4-18;</mapping> </mapping-list> anidbseason, tvdbseason, offset, start, end = season.get( 'anidbseason'), season.get('tvdbseason'), season.get( 'offset') or '0', season.get('start'), season.get( 'end') Log.Info( " - season: [{:>2}], [{:>2}], range: [{:>3}-{:>3}], offset: {:>3}, text: {}" .format(anidbseason, tvdbseason, start or '000', end or '000', offset, (season.text or '').strip(';'))) for ep in range(int(start), int(end) + 1) if start else []: #Log.Info("[?] start: {}, end: {}, ep: {}".format(start, end, ep)) if not Dict( mappingList, 'TVDB', 's' + tvdbseason + 'e' + str(ep + int(offset))): SaveDict( (anidbseason, str(ep), AniDBid), mappingList, 'TVDB', 's' + tvdbseason + 'e' + str(ep + int(offset)) ) #mappingList['TVDB'][s1e1]=(AniDB_season, AniDB_episode, AniDBid) for start-end mappings #else: Log.Info("already present") for ep in filter( None, season.text.split(';')) if season.text else []: if not '-' in ep: Log.Info( '[!] MAPPING ERROR, season.text: "{}", ep mapping missing hyphen: "{}"' .format(season.text, ep)) elif not Dict( mappingList, 'TVDB', 's' + tvdbseason + 'e' + ep.split('-')[1]): SaveDict( (anidbseason, ep.split('-')[0], AniDBid), mappingList, 'TVDB', 's' + tvdbseason + 'e' + ep.split('-')[1] ) #mappingList['TVDB'][s1e1]=(AniDB_season, AniDB_episode, AniDBid) for manual mapping like '1-12' #elif '-' not in (mappingList, 'TVDB', 's'+tvdbseason+'e'+ep.split('-')[1]): # SaveDict((anidbseason, Dict(mappingList, 'TVDB', 's'+tvdbseason+'e'+ep.split('-')[1])[1]+'-'+ep.split('-')[0], AniDBid), mappingList, 'TVDB', 's'+tvdbseason+'e'+ep.split('-')[1]) # Log.Info("already present so converting to range but range not supported") if Dict(mappingList, 'season_map', AniDBid, 'max').isdigit() and int( Dict(mappingList, 'season_map', AniDBid, 'max')) < int(season.get("tvdbseason")): SaveDict( season.get("tvdbseason"), mappingList, 'season_map', AniDBid, 'max' ) # Update the max season to the largest 'tvdbseason' season seen in 'mapping-list' elif TVDBid == "hentai": SaveDict("X", AnimeLists_dict, 'content_rating') elif TVDBid in ("", "unknown", None): link = MAPPING_FEEDBACK % ("aid:%s '%s' TVDBid:" % (AniDB_id, "title"), String.StripTags( XML.StringFromElement( anime, encoding='utf8'))) error_log['anime-list TVDBid missing'].append( 'AniDBid: "{}" | Title: "{}" | Has no matching TVDBid "{}" in mapping file | <a href="{}" target="_blank">Submit bug report</a>' .format(AniDB_id, "title", TVDBid, link)) Log.Info( '"anime-list TVDBid missing.htm" log added as tvdb serie id missing in mapping file: "{}"' .format(TVDBid)) #AniDB guid need 1 AniDB xml only, not an TheTVDB numbered serie with anidb guid (not anidb2 since seen as TheTVDB) if AniDB_id and (movie or max(map(int, media.seasons.keys())) <= 1): break else: # case [tmdb-123]: # <anime anidbid="456" tvdbid="" defaulttvdbseason="" episodeoffset="" tmdbid="123" imdbid=""> # fails the above tvdbid + anidb check, but useful info was still obtained (anidbid=456) # <anime tmdbid="123"> # fails the above tvdbid + anidbid check, so this used to return a blank tmdbid to be later used in # TheMovieDB.GetMetadata(), and '' as AniDBid to be used in AniDB.GetMetadata() # so, not resetting the AniDBid/TVDBid, and saving found info if ((TMDB_id or TMDBid) or IMDBid): SaveDict(TMDB_id or TMDBid or '', mappingList, 'tmdbid') SaveDict(IMDBid or '', mappingList, 'imdbid') Log.Info( "Saved possible tmdb/imdb values for later '%s'/'%s' for later, since not in AnimeList." % (Dict(mappingList, 'tmdbid'), Dict(mappingList, 'imdbid'))) elif not found: Log.Info("ERROR: Could not find %s: %s" % (source, id)) # this error only makes sense if it's AniDB_id, right? otherwise AniDB_id is always == "" # since it cant be not found and also have been set if AniDB_id != "": error_log['anime-list AniDBid missing'].append( "AniDBid: " + common.WEB_LINK % (common.ANIDB_SERIE_URL + AniDB_id, AniDB_id)) # keeping this reset since im not clear on it's purpose. AniDBid, TVDBid = '', '' Log.Info(' ----- ------') Log.Info(' {:>5} {:>6}'.format( AniDB_id or AniDB_id2 or AniDBid, TVDB_id or TVDBid)) SaveDict(Dict(tvdbcounts, TVDB_id or TVDBid), mappingList, 'tvdbcount') ### Update collection TVDB_collection, title = [], '' for anime in AniDBTVDBMap.iter( 'anime') if AniDBTVDBMap and TVDB_id.isdigit() else []: if anime.get('tvdbid', "") == TVDB_id: TVDB_collection.append(anime.get("anidbid", "")) if anime.get('defaulttvdbseason') in [ 'a', '1' ] and anime.get('episodeoffset') in ['', '0'] and len( anime.xpath( "mapping-list/mapping[@anidbseason='1'][@tvdbseason='0']" )) == 0: title = AniDB.GetAniDBTitle( AniDB.AniDBTitlesDB.xpath( '/animetitles/anime[@aid="{}"]/title'.format( anime.get("anidbid", ""))))[ 0] #returns [title, main, language_rank] if len(TVDB_collection) > 1 and title: SaveDict([title + ' Collection'], AnimeLists_dict, 'collections') Log.Info("[ ] collection: TVDBid '%s' is part of collection: '%s'" % (TVDB_id, title)) else: Log.Info("[ ] collection: TVDBid '%s' is not part of any collection" % (TVDB_id)) Log.Info("--- return ---".ljust(157, '-')) Log.Info( "AniDB_id: '{}', AniDB_id2: '{}', AniDBid: '{}', TVDB_id: '{}', TVDBid: '{}'" .format(AniDB_id, AniDB_id2, AniDBid, TVDB_id, TVDBid)) Log.Info("mappingList: {}".format(DictString(mappingList, 1))) Log.Info("AnimeLists_dict: {}".format(DictString(AnimeLists_dict, 1))) return AnimeLists_dict, AniDB_id or AniDB_id2 or AniDBid, ( TVDB_id or TVDBid) if (TVDB_id or TVDBid).isdigit() else "", Dict( mappingList, 'tmdbid'), Dict(mappingList, 'imdbid'), mappingList
def GetMetadata(media, movie, error_log, source, AniDBid, TVDBid, AniDBMovieSets, mappingList): ''' Download metadata to dict_AniDB, ANNid, MALid ''' Log.Info("=== AniDB.GetMetadata() ===".ljust(157, '=')) 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_PIC_THUMB_URL = 'http://img7.anidb.net/pics/anime/thumbs/150/{}.jpg-thumb.jpg' AniDB_dict, ANNid, MALid = {}, "", "" original = AniDBid language_posters = [language.strip() for language in Prefs['PosterLanguagePriority'].split(',')] priority_posters = [provider.strip() for provider in Prefs['posters' ].split(',')] ### Build the list of anidbids for files present #### if source.startswith("tvdb") or source.startswith("anidb") and not movie and max(map(int, media.seasons.keys()))>1: #multi anidbid required only for tvdb numbering full_array = [ anidbid for season in Dict(mappingList, 'TVDB') or [] for anidbid in Dict(mappingList, 'TVDB', season) if season and 'e' not in season and anidbid.isdigit() ] AniDB_array = { AniDBid: [] } if Dict(mappingList, 'defaulttvdbseason')=='1' or Dict(mappingList, 'TVDB', 'sa') else {} for season in sorted(media.seasons, key=common.natural_sort_key) if not movie else []: # For each season, media, then use metadata['season'][season]... for episode in sorted(media.seasons[season].episodes, key=common.natural_sort_key): new_season, new_episode, anidbid = AnimeLists.anidb_ep(mappingList, season, episode) numbering = 's{}e{}'.format(season, episode) if anidbid and not (new_season=='0' and new_episode=='0'): SaveDict([numbering], AniDB_array, anidbid) else: continue elif source.startswith('anidb') and AniDBid != "": full_array, AniDB_array = [AniDBid], {AniDBid:[]} else: full_array, AniDB_array = [], {} Log.Info("AniDBid: {}, AniDBids list: {}, source: {}".format(AniDBid, full_array, source)) for anidbid in AniDB_array: Log.Info('[+] {:>5}: {}'.format(anidbid, AniDB_array[anidbid])) Log.Info('language_posters: {}'.format(language_posters)) ### Load anidb xmls in tvdb numbering format if needed ### for AniDBid in full_array: Log.Info(("--- %s ---" % AniDBid).ljust(157, '-')) Log.Info('AniDBid: {}, url: {}'.format(AniDBid, ANIDB_HTTP_API_URL+AniDBid)) Log.Info(("--- %s.series ---" % AniDBid).ljust(157, '-')) xml = common.LoadFile(filename=AniDBid+".xml", relativeDirectory=os.path.join("AniDB", "xml"), url=ANIDB_HTTP_API_URL+AniDBid) # AniDB title database loaded once every 2 weeks if not xml or isinstance(xml, str): if not xml: SaveDict(True, AniDB_dict, 'Banned') if isinstance(xml, str): Log.Error('Invalid str returned: "{}"'.format(xml)) title, original_title, language_rank = GetAniDBTitle(AniDBTitlesDB.xpath('/animetitles/anime[@aid="{}"]/title'.format(AniDBid))) if AniDBid==original or len(full_array)==1: Log.Info("[ ] title: {}" .format(SaveDict(title, AniDB_dict, 'title' ))) Log.Info("[ ] original_title: {}".format(SaveDict(original_title, AniDB_dict, 'original_title'))) Log.Info("[ ] language_rank: {}" .format(SaveDict(language_rank, AniDB_dict, 'language_rank' ))) elif xml: title, original_title, language_rank = GetAniDBTitle(xml.xpath('/anime/titles/title')) if AniDBid==original or len(full_array)==1: #Dict(mappingList, 'poster_id_array', TVDBid, AniDBid)[0]in ('1', 'a'): ### for each main anime AniDBid ### Log.Info("[ ] title: {}" .format(SaveDict(title, AniDB_dict, 'title' ))) Log.Info("[ ] original_title: {}".format(SaveDict(original_title, AniDB_dict, 'original_title'))) Log.Info("[ ] language_rank: {}" .format(SaveDict(language_rank, AniDB_dict, 'language_rank' ))) if SaveDict( GetXml(xml, 'startdate' ), AniDB_dict, 'originally_available_at'): Log.Info("[ ] originally_available_at: '{}'".format(AniDB_dict['originally_available_at'])) if SaveDict(summary_sanitizer(GetXml(xml, 'description')), AniDB_dict, 'summary') and not movie and Dict(mappingList, 'defaulttvdbseason').isdigit() and mappingList['defaulttvdbseason'] in media.seasons: SaveDict(AniDB_dict['summary'], AniDB_dict, 'seasons', mappingList['defaulttvdbseason'], 'summary') Log.Info("[ ] rating: '{}'".format(SaveDict( GetXml(xml, 'ratings/temporary'), AniDB_dict, 'rating'))) ### Posters if GetXml(xml, 'picture'): rank = 1 if 'en' in language_posters: rank = (rank//30)*30*language_posters.index('en')+rank%30 if 'AniDB' in priority_posters: rank = rank+ 6*priority_posters.index('AniDB') AniDB_dict['posters'] = {ANIDB_PIC_BASE_URL + GetXml(xml, 'picture'): ( os.path.join('AniDB', 'poster', GetXml(xml, 'picture')), rank, ANIDB_PIC_THUMB_URL.format(GetXml(xml, 'picture').split('.')[0]))} ### genre ### RESTRICTED_GENRE = {"18 restricted": 'X', "pornography": 'X', "tv censoring": 'TV-MA', "borderline p**n": 'TV-MA'} for tag in xml.xpath('tags/tag'): if GetXml(tag, 'name') and tag.get('weight', '').isdigit() and int(tag.get('weight', '') or '200') >= int(Prefs['MinimumWeight'] or '200'): SaveDict( [string.capwords(GetXml(tag, 'name'), '-')], AniDB_dict, 'genres') if GetXml(tag, 'name').lower() in RESTRICTED_GENRE: AniDB_dict['content_rating'] = RESTRICTED_GENRE[ GetXml(tag, 'name').lower() ] if Dict(AniDB_dict, 'genres'): AniDB_dict['genres'].sort() SaveDict( "Continuing" if GetXml(xml, 'Anime/enddate')=="1970-01-01" else "Ended", AniDB_dict, 'status') Log.Info("[ ] genres ({}/{} above {} weight): {}".format(len(Dict(AniDB_dict, 'genres')), len(xml.xpath('tags/tag')), int(Prefs['MinimumWeight'] or 200), Dict(AniDB_dict, 'genres'))) for element in AniDBMovieSets.xpath("/anime-set-list/set/anime"): if element.get('anidbid') == AniDBid or element.get('anidbid') in full_array: node = element.getparent() title, main, language_rank = GetAniDBTitle(node.xpath('titles')[0]) if title not in Dict(AniDB_dict, 'collections', default=[]): Log.Info("[ ] title: {}, main: {}, language_rank: {}".format(title, main, language_rank)) SaveDict([title], AniDB_dict, 'collections') Log.Info("[ ] collection: AniDBid '%s' is part of movie collection: '%s', related_anime_list: %s" % (AniDBid, title, str(full_array))) if not Dict(AniDB_dict, 'collections'): Log.Info("[ ] collection: AniDBid '%s' is not part of any collection, related_anime_list: %s" % (AniDBid, str(full_array))) #roles ### NEW, NOT IN Plex FrameWork Documentation 2.1.1 ### Log.Info(("--- %s.actors ---" % AniDBid).ljust(157, '-')) for role in xml.xpath('characters/character[(@type="secondary cast in") or (@type="main character in")]'): try: if GetXml(role, 'seiyuu') and GetXml(role, 'name'): role_dict = {'role': role.find('name').text, 'name': role.find('seiyuu').text, 'photo': ANIDB_PIC_BASE_URL + role.find('seiyuu').get('picture')} SaveDict([role_dict], AniDB_dict, 'roles') Log.Info('[ ] role: {:<20}, name: {:<20}, photo: {}'.format(role_dict['role'], role_dict['name'], role_dict['photo'])) except Exception as e: Log.Info("Seyiuu error: {}".format(e)) if movie: Log.Info("[ ] year: '{}'".format(SaveDict(GetXml(xml, 'startdate')[0:4], AniDB_dict, 'year'))) Log.Info(("--- %s.summary info ---" % AniDBid).ljust(157, '-')) ### Series ### else: ### not listed for serie but is for eps roles = { "Animation Work":"studio", "Direction":"directors", "Series Composition":"producers", "Original Work":"writers", "Script":"writers", "Screenplay":"writers" } ep_roles = {} for creator in xml.xpath('creators/name'): for role in roles: if not role in creator.get('type'): continue if roles[role]=="studio": SaveDict(creator.text, AniDB_dict, 'studio') else: SaveDict([creator.text], ep_roles, roles[role]) Log.Info("[ ] roles (creators tag): " +str(ep_roles)) if SaveDict(GetXml(xml, 'type')=='Movie', AniDB_dict, 'movie'): Log.Info("'movie': '{}'".format(AniDB_dict['movie'])) ### Translate into season/episode mapping numEpisodes, totalDuration, mapped_eps, ending_table, op_nb = 0, 0, [], {}, 0 specials = {'S': [0, 'Special'], 'C': [100, 'Opening/Ending'], 'T': [200, 'Trailer'], 'P': [300, 'Parody'], 'O': [400, 'Other']} movie_ep_groups = {} missing={'0': [], '1':[]} ### Episodes (and specials) not always in right order ### Log.Info(("--- %s.episodes ---" % AniDBid).ljust(157, '-')) ending_offset = 99 for ep_obj in sorted(xml.xpath('episodes/episode'), key=lambda x: [int(x.xpath('epno')[0].get('type')), int(x.xpath('epno')[0].text if x.xpath('epno')[0].text.isdigit() else x.xpath('epno')[0].text[1:])]): ### Title, Season, Episode number, Specials title, main, language_rank = GetAniDBTitle (ep_obj.xpath('title'), [language.strip() for language in Prefs['EpisodeLanguagePriority'].split(',')]) epNum = ep_obj.xpath('epno')[0] epNumType = epNum.get('type') season = "1" if epNumType == "1" else "0" if epNumType=="3" and ep_obj.xpath('title')[0].text.startswith('Ending') and int(epNum.text[1:])-1<ending_offset: ending_offset = int(epNum.text[1:])-1 if epNumType=="3" and int(epNum.text[1:])>ending_offset: episode = str(int(epNum.text[1:])+150-ending_offset) #shifted to 150 for 1st ending. elif epNumType=="1": episode = epNum.text else: episode = str( specials[ epNum.text[0] ][0] + int(epNum.text[1:])) numbering = "s{}e{:>3}".format(season, episode) #If tvdb numbering used, save anidb episode meta using tvdb numbering if source.startswith("tvdb") or source.startswith("anidb") and not movie and max(map(int, media.seasons.keys()))>1: season, episode = AnimeLists.tvdb_ep(mappingList, season, episode, AniDBid) ###Broken for tvdbseason='a' # Get season from absolute number OR convert episode number to absolute number if source in ('tvdb3', 'tvdb4') and season not in ('-1', '0'): if season=='a' or source=='tvdb4': season = Dict(mappingList, 'absolute_map', episode, default=(season, episode))[0] elif episode!='0': try: episode = list(Dict(mappingList, 'absolute_map', default={}).keys())[list(Dict(mappingList, 'absolute_map', default={}).values()).index((season, episode))] except Exception as e: Log.Error("Exception: {}".format(e)) if season=='0' and episode=='0' or not season in media.seasons or not episode in media.seasons[season].episodes: Log.Info('[ ] {} => s{:>1}e{:>3} epNumType: {}'.format(numbering, season, episode, epNumType)); continue ### Series poster as season poster if GetXml(xml, 'picture') and not Dict(AniDB_dict, 'seasons', season, 'posters', ANIDB_PIC_BASE_URL + GetXml(xml, 'picture')): rank = 1 if 'en' in language_posters: rank = (rank//30)*30*language_posters.index('en')+rank%30 if 'AniDB' in priority_posters: rank = rank+ 6*priority_posters.index('AniDB') SaveDict((os.path.join('AniDB', 'poster', GetXml(xml, 'picture')), rank, ANIDB_PIC_THUMB_URL.format(GetXml(xml, 'picture').split('.')[0])), AniDB_dict, 'seasons', season, 'posters', ANIDB_PIC_BASE_URL + GetXml(xml, 'picture')) ### In AniDB numbering, Movie episode group, create key and create key in dict with empty list if doesn't exist ### else: #if source.startswith("anidb") and not movie and max(map(int, media.seasons.keys()))<=1: ### Movie episode group, create key and create key in dict with empty list if doesn't exist ### key = '' if epNumType=='1' and GetXml(xml, '/anime/episodecount')=='1' and GetXml(xml, '/anime/type') in ('Movie', 'OVA'): key = '1' if title in ('Complete Movie', 'OVA') else title[-1] if title.startswith('Part ') and title[-1].isdigit() else '' #'-1' if key: SaveDict([], movie_ep_groups, key) #Episode missing from disk if not season in media.seasons or not episode in media.seasons[season].episodes: Log.Info('[ ] {} => s{:>1}e{:>3} air_date: {}'.format(numbering, season, episode, GetXml(ep_obj, 'airdate'))) current_air_date = GetXml(ep_obj, 'airdate').replace('-','') current_air_date = int(current_air_date) if current_air_date.isdigit() and int(current_air_date) > 10000000 else 99999999 if int(time.strftime("%Y%m%d")) > current_air_date+1: if epNumType == '1' and key: SaveDict([numbering], movie_ep_groups, key ) elif epNumType in ['1', '2']: SaveDict([episode], missing, season) continue ### Episodes SaveDict(language_rank, AniDB_dict, 'seasons', season, 'episodes', episode, 'language_rank') SaveDict(title, AniDB_dict, 'seasons', season, 'episodes', episode, 'title' ) Log.Info('[X] {} => s{:>1}e{:>3} air_date: {} language_rank: {}, title: "{}"'.format(numbering, season, episode, GetXml(ep_obj, 'airdate'), language_rank, title)) if GetXml(ep_obj, 'length').isdigit(): SaveDict(int(GetXml(ep_obj, 'length'))*1000*60, AniDB_dict, 'seasons', season, 'episodes', episode, 'duration') # AniDB stores it in minutes, Plex save duration in millisecs if season == "1": numEpisodes, totalDuration = numEpisodes+1, totalDuration + int(GetXml(ep_obj, 'length')) SaveDict(GetXml(ep_obj, 'rating' ), AniDB_dict, 'seasons', season, 'episodes', episode, 'rating' ) SaveDict(GetXml(ep_obj, 'airdate'), AniDB_dict, 'seasons', season, 'episodes', episode, 'originally_available_at') if SaveDict(summary_sanitizer(GetXml(ep_obj, 'summary')), AniDB_dict, 'seasons', season, 'episodes', episode, 'summary'): Log.Info(" - [ ] summary: {}".format(Dict(AniDB_dict, 'seasons', season, 'episodes', episode, 'summary'))) #for role in ep_roles: SaveDict(",".join(ep_roles[role]), AniDB_dict, 'seasons', season, 'episodes', episode, role) #Log.Info("role: '%s', value: %s " % (role, str(ep_roles[role]))) ### End of for ep_obj... Log.Info(("--- %s.summary info ---" % AniDBid).ljust(157, '-')) if SaveDict(int(totalDuration)/int(numEpisodes) if int(numEpisodes) else 0, AniDB_dict, 'duration'): Log.Info("Duration: {}, numEpisodes: {}, average duration: {}".format(str(totalDuration), str(numEpisodes), AniDB_dict['duration'])) ### AniDB numbering Missing Episodes ### if source.startswith("anidb") and not movie and max(map(int, media.seasons.keys()))<=1: if movie_ep_groups: Log.Info("Movie/OVA Ep Groups: %s" % movie_ep_groups) #movie_ep_groups: {'1': ['s1e1'], '3': ['s1e4', 's1e5', 's1e6'], '2': ['s1e3'], '-1': []} SaveDict([value for key in movie_ep_groups for value in movie_ep_groups[key] if 0 < len(movie_ep_groups[key]) < int(key)], missing, '1') for season in sorted(missing): missing_eps = sorted(missing[season], key=common.natural_sort_key) Log.Info('Season: {} Episodes: {} not on disk'.format(season, missing_eps)) if missing_eps: error_log['Missing Specials' if season=='0' else 'Missing Episodes'].append("AniDBid: %s | Title: '%s' | Missing Episodes: %s" % (common.WEB_LINK % (common.ANIDB_SERIE_URL + AniDBid, AniDBid), AniDB_dict['title'], str(missing_eps))) ### End of if not movie ### # Generate relations_map for anidb3/4(tvdb1/6) modes for relatedAnime in xml.xpath('/anime/relatedanime/anime'): if relatedAnime.get('id') not in Dict(mappingList, 'relations_map', AniDBid, relatedAnime.get('type'), default=[]): SaveDict([relatedAnime.get('id')], mappingList, 'relations_map', AniDBid, relatedAnime.get('type')) # External IDs ANNid = GetXml(xml, "/anime/resources/resource[@type='1']/externalentity/identifier") MALid = GetXml(xml, "/anime/resources/resource[@type='2']/externalentity/identifier") #ANFOid = GetXml(xml, "/anime/resources/resource[@type='3']/externalentity/identifier"), GetXml(xml, "/anime/resources/resource[@type='3']/externalentity/identifier") # Logs if not Dict(AniDB_dict, 'summary'): error_log['AniDB summaries missing'].append("AniDBid: %s" % (common.WEB_LINK % (common.ANIDB_SERIE_URL + AniDBid, AniDBid) + " | Title: '%s'" % Dict(AniDB_dict, 'title'))) if not Dict(AniDB_dict, 'posters'): error_log['AniDB posters missing' ].append("AniDBid: %s" % (common.WEB_LINK % (common.ANIDB_SERIE_URL + AniDBid, AniDBid) + " | Title: '%s'" % Dict(AniDB_dict, 'title'))) #if not Dict(AniDB_dict, 'studio' ): error_log['anime-list studio logos'].append("AniDBid: %s | Title: '%s' | AniDB has studio '%s' and anime-list has '%s' | " % (common.WEB_LINK % (ANIDB_SERIE_URL % AniDBid, AniDBid), title, metadata.studio, mapping_studio) + common.WEB_LINK % (ANIDB_TVDB_MAPPING_FEEDBACK % ("aid:" + metadata.id + " " + title, String.StripTags( XML.StringFromElement(xml, encoding='utf8'))), "Submit bug report (need GIT account)")) #if metadata.studio and 'studio' in AniDB_dict and AniDB_dict ['studio'] and AniDB_dict ['studio'] != metadata.studio: error_log['anime-list studio logos'].append("AniDBid: %s | Title: '%s' | AniDB has studio '%s' and anime-list has '%s' | " % (common.WEB_LINK % (ANIDB_SERIE_URL % AniDBid, AniDBid), title, metadata.studio, mapping_studio) + common.WEB_LINK % (ANIDB_TVDB_MAPPING_FEEDBACK % ("aid:" + metadata.id + " " + title, String.StripTags( XML.StringFromElement(xml, encoding='utf8'))), "Submit bug report (need GIT account)")) #if metadata.studio == "" and 'studio' in AniDB_dict and AniDB_dict ['studio'] == "": error_log['anime-list studio logos'].append("AniDBid: %s | Title: '%s' | AniDB and anime-list are both missing the studio" % (common.WEB_LINK % (ANIDB_SERIE_URL % AniDBid, AniDBid), title) ) Log.Info("ANNid: '%s', MALid: '%s', xml loaded: '%s'" % (ANNid, MALid, str(xml is not None))) Log.Info("--- return ---".ljust(157, '-')) Log.Info("relations_map: {}".format(DictString(Dict(mappingList, 'relations_map', default={}), 1))) Log.Info("AniDB_dict: {}".format(DictString(AniDB_dict, 4))) return AniDB_dict, ANNid, MALid
def GetMetadata( movie, imdbid, num=98 ): # return 200 but not downloaded correctly - IMDB has a single poster, downloading through OMDB xml, prefered by mapping file OMDB_HTTP_API_URL = "http://www.omdbapi.com/?apikey={api_key}&i=".format( api_key=Prefs['OMDbApiKey']) #' OMDb_dict = {} Log.Info("".ljust(157, '-')) Log.Info("OMDb.GetMetadata() - background, Poster - imdbid: '%s'" % imdbid) for imdbid_single in imdbid.split(",") if imdbid.startswith("tt") else []: json = common.LoadFile(filename=imdbid_single + ".json", relativeDirectory=os.path.join('OMDb', 'json'), url=OMDB_HTTP_API_URL + imdbid_single, cache=CACHE_1WEEK) if json: SaveDict(Dict(json, 'title'), OMDb_dict, 'title') SaveDict(Dict(json, 'Plot'), OMDb_dict, 'summary') SaveDict(Dict(json, 'Released'), OMDb_dict, 'originally_available_at') SaveDict(Dict(json, 'Country'), OMDb_dict, 'countries') SaveDict(Dict(json, 'Director'), OMDb_dict, 'directors') SaveDict(Dict(json, 'Genre'), OMDb_dict, 'genres') SaveDict(Dict(json, 'Writer'), OMDb_dict, 'writers') SaveDict(Dict(json, 'imdbRating'), OMDb_dict, 'rating') if Dict(json, 'Metascore').isdigit() and not Dict(OMDb_dict, 'rating'): SaveDict(float(json['Metascore']) / 10, OMDb_dict, 'rating') if SaveDict( Dict(json, 'Rated'), OMDb_dict, 'content_rating' ) in common.Movie_to_Serie_US_rating and not movie and Dict( json, 'Type') == "movie": SaveDict(common.Movie_to_Serie_US_rating[json['Rated']], OMDb_dict, 'content_rating') if Dict(json, 'Poster'): SaveDict( os.path.join('OMDb', 'poster', imdbid_single + '.jpg', num, None), OMDb_dict, 'posters', json['Poster']) try: SaveDict( int(Dict(json, 'Runtime').replace(' min', '')) * 60 * 1000, OMDb_dict, 'duration') # Plex save duration in millisecs except: pass return OMDb_dict
def GetMetadata(movie=False, TVDBid="", tmdbid="", imdbid="", season=0): #Fetch from fanart.tv - Posters Seasons Fanarts Banner Log.Info("=== FanartTv.GetMetadata() ===".ljust(157, '=')) FanartTV_dict = {} Log.Info("movie:'{movie}', TVDBid: '{TVDBid}', tmdbid: '{tmdbid}', imdbid:'{imdbid}', season: '{season}'".format(movie=movie, TVDBid=TVDBid, tmdbid=tmdbid, imdbid=imdbid, season=season)) if "," in imdbid: (GetMetadata(movie, "", "", imdbid_unique, season) for imdbid_unique in (tmdbid or imdbid).split(",")); return #recusive call for each imdbid to reduce complexity if "," in tmdbid: (GetMetadata(movie, "", tmdbid_unique, "", season) for tmdbid_unique in tmdbid.split(",")); return #recusive call for each tmdbid to reduce complexity if not movie and TVDBid.isdigit(): id, relativeDirectory, url = TVDBid, os.path.join("FanartTV", "tv", TVDBid), FTV_API_TV_URL.format(id=TVDBid) elif movie and (imdbid or tmdbid): id, relativeDirectory, url = imdbid or tmdbid, os.path.join("FanartTV", "movie", imdbid or tmdbid), FTV_API_MOVIES_URL.format(id=imdbid or tmdbid) else: return FanartTV_dict if TVDBid or tmdbid or imdbid: Log.Info(("--- %s.images ---" % id).ljust(157, '-')) json = common.LoadFile(filename=id+".json", relativeDirectory=relativeDirectory, url=url) #Movies if json and (imdbid or tmdbid): for item in Dict(json, 'movieposter' , default=[]): Log.Info("[ ] poster: {}".format(Dict(item, 'url'))); SaveDict((os.path.join(relativeDirectory, id, "movieposter", "{filename}.jpg".format(filename=Dict(item, 'id'))), common.poster_rank('FanartTV', 'posters'), None), FanartTV_dict, 'posters', Dict(item, 'url')) for item in Dict(json, 'moviebackground', default=[]): Log.Info("[ ] art: {}" .format(Dict(item, 'url'))); SaveDict((os.path.join(relativeDirectory, id, "moviebackground", "{filename}.jpg".format(filename=Dict(item, 'id'))), common.poster_rank('FanartTV', 'art' ), None), FanartTV_dict, 'art', Dict(item, 'url')) #Series if json and TVDBid.isdigit(): for item in Dict(json, 'tvposter' , default=[]): Log.Info("[ ] poster: {}" .format(Dict(item, 'url'))); SaveDict((os.path.join(relativeDirectory, id, "tvposter", "{filename}.jpg".format(filename=Dict(item, 'id'))), common.poster_rank('FanartTV', 'posters'), None), FanartTV_dict, 'posters', Dict(item, 'url')) for item in Dict(json, 'showbackground' , default=[]): Log.Info("[ ] art: {}" .format(Dict(item, 'url'))); SaveDict((os.path.join(relativeDirectory, id, "showbackground", "{filename}.jpg".format(filename=Dict(item, 'id'))), common.poster_rank('FanartTV', 'art' ), None), FanartTV_dict, 'art', Dict(item, 'url')) for item in Dict(json, 'tvbanner' , default=[]): Log.Info("[ ] banner: {}" .format(Dict(item, 'url'))); SaveDict((os.path.join(relativeDirectory, id, "tvbanner", "{filename}.jpg".format(filename=Dict(item, 'id'))), common.poster_rank('FanartTV', 'banners'), None), FanartTV_dict, 'banners', Dict(item, 'url')) for item in Dict(json, 'seasonposter' , default=[]): Log.Info("[ ] season poster: {}".format(Dict(item, 'url'))); SaveDict((os.path.join(relativeDirectory, id, "seasonposter", "{filename}.jpg".format(filename=Dict(item, 'id'))), common.poster_rank('FanartTV', 'posters'), None), FanartTV_dict, 'seasons', Dict(item, 'season'), 'posters', Dict(item, 'url')) Log.Info("--- return ---".ljust(157, '-')) Log.Info("FanartTV_dict: {}".format(DictString(FanartTV_dict, 4))) return FanartTV_dict
def GetMetadata(media, movie, error_log, lang, metadata_source, AniDBid, TVDBid, IMDbid, mappingList): ''' TVDB - Load serie JSON ''' Log.Info("=== TheTVDB.GetMetadata() ===".ljust(157, '=')) TheTVDB_dict = {} max_season = 0 anidb_numbering = metadata_source == "anidb" and ( movie or max(map(int, media.seasons.keys())) <= 1) anidb_prefered = anidb_numbering and Dict(mappingList, 'defaulttvdbseason') != '1' language_series = [ language.strip() for language in Prefs['SerieLanguagePriority'].split(',') if language.strip() not in ('x-jat', 'zh-Hans', 'zh-Hant', 'zh-x-yue', 'zh-x-cmn', 'zh-x-nan', 'main') ] language_episodes = [ language.strip() for language in Prefs['EpisodeLanguagePriority'].split(',') if language.strip() not in ('x-jat', 'zh-Hans', 'zh-Hant', 'zh-x-yue', 'zh-x-cmn', 'zh-x-nan', 'main') ] Log.Info( "TVDBid: '{}', IMDbid: '{}', language_series : {}, language_episodes: {}" .format(TVDBid, IMDbid, language_series, language_episodes)) if not TVDBid.isdigit(): Log.Info('TVDBid non-digit') return TheTVDB_dict, IMDbid ### TVDB Series JSON ### Log.Info("--- series ---".ljust(157, '-')) json = {} if lang not in language_series: language_series.insert( 0, lang) #for summary in lang (library setting) language if 'en' not in language_series: language_series.insert(0, 'en') #for failover title if lang not in language_episodes: language_episodes.append( lang) #for summary in lang (library setting) language if 'en' not in language_episodes: language_episodes.append('en') #for failover title for language in language_series: json[language] = Dict( LoadFileTVDB(id=TVDBid, filename='series_{}.json'.format(language), url=TVDB_SERIES_URL.format(id=TVDBid) + '?' + language, headers={'Accept-Language': language}), 'data') if Dict(json[language], 'seriesName'): # and not Dict(TheTVDB_dict, 'language_rank'): SaveDict( language_series.index(language) if not anidb_prefered else len(language_series), TheTVDB_dict, 'language_rank') Log.Info("[ ] language_rank: {}".format( Dict(TheTVDB_dict, 'language_rank'))) Log.Info("[ ] title: {}".format( SaveDict( Dict(json[language], 'seriesName') or Dict(serie2_json, 'seriesName'), TheTVDB_dict, 'title'))) Log.Info("[ ] original_title: {}".format( SaveDict( Dict(json[language], 'seriesName') or Dict(serie2_json, 'seriesName'), TheTVDB_dict, 'original_title'))) if Dict(json, lang) and (Dict(json, lang, 'overview') or Dict(TheTVDB_dict, 'language_rank')): break #only need json in lang for summary, in 'en' for most things if not anidb_prefered: SaveDict( Dict(json, lang, 'overview').strip(" \n\r") or Dict(json, 'en', 'overview').strip(" \n\r"), TheTVDB_dict, 'summary') if Dict(json, lang): #format: { "id","seriesId", "airsDayOfWeek", "imdbId", "zap2itId", "added", "addedBy", "lastUpdated", "seriesName", "aliases", "banner", "status", # "firstAired", "network", "networkId", "runtime", "genre, "overview", "airsTime", "rating" , "siteRating", "siteRatingCount" } Log.Info("[ ] IMDbid: {}".format( SaveDict(Dict(json[lang], 'imdbId' or IMDbid), TheTVDB_dict, 'IMDbid'))) Log.Info("[ ] zap2itId: {}".format( SaveDict(Dict(json[lang], 'zap2it_id'), TheTVDB_dict, 'zap2itId'))) Log.Info("[ ] content_rating: {}".format( SaveDict(Dict(json[lang], 'rating'), TheTVDB_dict, 'content_rating'))) Log.Info("[ ] originally_available_at: {}".format( SaveDict(Dict(json[lang], 'firstAired'), TheTVDB_dict, 'originally_available_at'))) Log.Info("[ ] studio: {}".format( SaveDict(Dict(json[lang], 'network'), TheTVDB_dict, 'studio'))) Log.Info("[ ] rating: {}".format( SaveDict(Dict(json[lang], 'siteRating'), TheTVDB_dict, 'rating'))) Log.Info("[ ] status: {}".format( SaveDict(Dict(json[lang], 'status'), TheTVDB_dict, 'status'))) Log.Info("[ ] genres: {}".format( SaveDict(sorted(Dict(json[lang], 'genre')), TheTVDB_dict, 'genres'))) if Dict(json[lang], 'runtime').isdigit(): Log.Info('[ ] duration: {}'.format( SaveDict( int(Dict(json[lang], 'runtime')) * 60 * 1000, TheTVDB_dict, 'duration'))) #in ms in plex series_images = { # Pull the primary images used for the series entry 'poster': Dict(json[language], 'poster'), 'banner': Dict(json[language], 'banner'), 'fanart': Dict(json[language], 'fanart'), 'seasonwide': Dict(json[language], 'seasonwide'), 'series': Dict(json[language], 'series') } ### TVDB Series Actors JSON ### Log.Info("--- actors ---".ljust(157, '-')) actor_json = Dict(LoadFileTVDB(id=TVDBid, filename='actors_{}.json'.format(lang), url=TVDB_ACTORS_URL.format(id=TVDBid), headers={'Accept-Language': lang}), 'data', default=[]) if actor_json: #JSON format: 'data': [{"seriesId", "name", "image", "lastUpdated", "imageAuthor", "role", "sortOrder", "id", "imageAdded", },...] for role in actor_json: try: role_dict = { 'role': Dict(role, 'role'), 'name': Dict(role, 'name'), 'photo': TVDB_IMG_ROOT + role['image'] if Dict(role, 'image') else '' } SaveDict([role_dict], TheTVDB_dict, 'roles') Log.Info( "[ ] role: {:<50}, name: {:<20}, photo: {}".format( role_dict['role'], role_dict['name'], role_dict['photo'])) except Exception as e: Log.Info(" role: {}, error: '{}'".format( str(role), str(e))) #common.DisplayDict(actor_json, ['role', 'name', 'image']) ### Load pages of episodes ### Log.Info("--- episodes ---".ljust(157, '-')) episodes_json, sorted_episodes_json, next_page = [], {}, 1 while next_page not in (None, '', 'null'): episodes_json_page = LoadFileTVDB( id=TVDBid, filename='episodes_page{}_{}.json'.format(next_page, lang), url=TVDB_EPISODE_PAGE_URL.format(id=TVDBid, page=next_page), headers={'Accept-Language': lang}) next_page = Dict(episodes_json_page, 'links', 'next') episodes_json.extend(Dict(episodes_json_page, 'data')) for episode_json in episodes_json: sorted_episodes_json['s{:02d}e{:03d}'.format( Dict(episode_json, 'airedSeason'), Dict(episode_json, 'airedEpisodeNumber'))] = episode_json ### Build list_abs_eps for tvdb 3/4/5 ### list_abs_eps, list_sp_eps = {}, [] if metadata_source in ('tvdb3', 'tvdb4'): for s in media.seasons: for e in media.seasons[s].episodes: if s == '0': list_sp_eps.append(e) else: list_abs_eps[e] = s Log.Info('Present abs eps: {}'.format(list_abs_eps)) ### episode loop ### tvdb_special_missing, summary_missing_special, summary_missing, summary_present, episode_missing, episode_missing_season, episode_missing_season_all, abs_number, ep_count = [], [], [], [], [], [], True, 0, 0 for key in sorted(sorted_episodes_json): # Episode and Absolute number calculation engine, episode translation episode_json = sorted_episodes_json[key] episode = str(Dict(episode_json, 'airedEpisodeNumber')) season = str(Dict(episode_json, 'airedSeason')) numbering = "s{}e{}".format(season, episode) # Replace all the individual episodes reported as missing with a single season 'sX' entry if episode == "1": if not episode_missing_season_all: episode_missing.extend(episode_missing_season) elif episode_missing_season: first_entry, last_entry = episode_missing_season[ 0], episode_missing_season[-1] fm = re.match( r'((?P<abs>\d+) \()?s(?P<s>\d+)e(?P<e>\d+)\)?', first_entry).groupdict() lm = re.match( r'((?P<abs>\d+) \()?s(?P<s>\d+)e(?P<e>\d+)\)?', last_entry).groupdict() episode_missing.append( "s{}e{}-{}".format(fm['s'], fm['e'], lm['e']) if fm['abs'] is None else "{}-{} (s{}e{}-{})".format( fm['abs'], lm['abs'], fm['s'], fm['e'], lm['e'])) episode_missing_season, episode_missing_season_all = [], True # Get the max season number from TVDB API if int(season) > max_season: max_season = int(season) ### ep translation [Thetvdb absolute numbering followed, including in specials to stay accurate with scudlee's mapping] anidbid = "" abs_number = Dict(episode_json, 'absoluteNumber', default=0 if season == '0' else abs_number + 1) if anidb_numbering: if Dict(mappingList, 'defaulttvdbseason_a'): season, episode = '1', str(abs_number) else: season, episode, anidbid = AnimeLists.anidb_ep( mappingList, season, episode) elif metadata_source in ('tvdb3', 'tvdb4'): for s in media.seasons: #if abs id exists on disk, leave specials with no translation if str(abs_number) in list_abs_eps and str( abs_number ) in media.seasons[s].episodes and s != "0": season, episode = s, str(abs_number) break elif metadata_source == 'tvdb5': if abs_number: season, episode = '1', str(abs_number) # Record absolute number mapping for AniDB metadata pull if metadata_source == 'tvdb3': SaveDict((str(Dict(episode_json, 'airedSeason')), str(Dict(episode_json, 'airedEpisodeNumber'))), mappingList, 'absolute_map', str(abs_number)) ### Missing summaries logs ### if Dict(episode_json, 'overview'): summary_present.append(numbering) elif season != '0': summary_missing.append(numbering) else: summary_missing_special.append(numbering) ### Check for Missing Episodes ### is_missing = False if not(str(Dict(episode_json, 'airedSeason'))=='0' and str(Dict(episode_json, 'airedEpisodeNumber')) in list_sp_eps) and \ not(metadata_source in ('tvdb3', 'tvdb4') and str(abs_number) in list_abs_eps) and \ not(not movie and season in media.seasons and episode in media.seasons[season].episodes): is_missing = True Log.Info( '[ ] {:>7} s{:0>2}e{:0>3} anidbid: {:>7} air_date: {}'. format(numbering, season, episode, anidbid, Dict(episode_json, 'firstAired'))) air_date = Dict(episode_json, 'firstAired') air_date = int(air_date.replace( '-', '')) if air_date.replace('-', '').isdigit() and int( air_date.replace('-', '')) > 10000000 else 99999999 if int(time.strftime("%Y%m%d")) <= air_date + 1: pass #Log.Info("TVDB - Episode '{}' missing but not aired/missing '{}'".format(numbering, air_date)) elif season == '0': tvdb_special_missing.append(episode) elif metadata_source != 'tvdb6': episode_missing_season.append( str(abs_number) + " (" + numbering + ")" if metadata_source in ('tvdb3', 'tvdb4') else numbering) ### File present on disk if not is_missing or Dict( mappingList, 'possible_anidb3' ) or metadata_source == "tvdb6": # Only pull all if anidb3(tvdb)/anidb4(tvdb6) usage for tvdb ep/season adjustments episode_missing_season_all = False if not is_missing: Log.Info( '[X] {:>7} s{:0>2}e{:0>3} anidbid: {:>7} air_date: {} abs_number: {}, title: {}' .format(numbering, season, episode, anidbid, Dict(episode_json, 'firstAired'), abs_number, Dict(episode_json, 'episodeName'))) if not anidb_numbering: SaveDict(abs_number, TheTVDB_dict, 'seasons', season, 'episodes', episode, 'absolute_index') SaveDict(Dict(json[lang], 'rating'), TheTVDB_dict, 'seasons', season, 'episodes', episode, 'content_rating') SaveDict(Dict(TheTVDB_dict, 'duration'), TheTVDB_dict, 'seasons', season, 'episodes', episode, 'duration') SaveDict(Dict(episode_json, 'firstAired'), TheTVDB_dict, 'seasons', season, 'episodes', episode, 'originally_available_at') # Title from serie page rank, title = len(language_episodes) + 1, '' if Dict(episode_json, 'episodeName'): rank = language_episodes.index( lang) if lang in language_episodes else len( language_episodes) title = Dict(episode_json, 'episodeName') Log.Info(" - [1] title: [{}] {}".format( language_episodes[rank], title)) #Summary from serie page if Dict(episode_json, 'overview').strip(" \n\r"): SaveDict( Dict(episode_json, 'overview').strip(" \n\r"), TheTVDB_dict, 'seasons', season, 'episodes', episode, 'summary') Log.Info(' - [1] summary: [{}] {}'.format( lang, Dict(TheTVDB_dict, 'seasons', season, 'episodes', episode, 'summary'))) ### Ep advance information ### ep_count += 1 lang2 = 'en' if len( language_episodes) <= 1 else language_episodes[1] episode_details_json = Dict( LoadFileTVDB(id=TVDBid, filename='episode_{}_{}.json'.format( Dict(episode_json, 'id'), lang2), url=TVDB_EPISODE_URL.format( id=str(Dict(episode_json, 'id'))), headers={'Accept-Language': lang2}), 'data') if episode_details_json: # Std ep info loaded for Library language ten details for 1st language, loading other languages if needed if lang2 in language_episodes and language_episodes.index( lang2) < rank and Dict( episode_details_json, 'language', 'episodeName') == lang2 and Dict( episode_details_json, 'episodeName'): rank = language_episodes.index(lang2) title = Dict(episode_details_json, 'episodeName') Log.Info(" - [2] title: [{}] {}".format( language_episodes[rank], title)) #Summary if not Dict(TheTVDB_dict, 'seasons', season, 'episodes', episode, 'summary') and Dict( episode_details_json, 'overview'): SaveDict( Dict(episode_details_json, 'overview').strip(" \n\r"), TheTVDB_dict, 'seasons', season, 'episodes', episode, 'summary') Log.Info(' - [2] summary: [{}] {}'.format( lang2, Dict(TheTVDB_dict, 'seasons', season, 'episodes', episode, 'summary'))) SaveDict(Dict(episode_details_json, 'writers'), TheTVDB_dict, 'seasons', season, 'episodes', episode, 'writers') SaveDict(Dict(episode_details_json, 'directors'), TheTVDB_dict, 'seasons', season, 'episodes', episode, 'directors') SaveDict(Dict(episode_details_json, 'siteRating'), TheTVDB_dict, 'seasons', season, 'episodes', episode, 'rating') #SaveDict( Dict(episode_details_json, 'guestStars' ), TheTVDB_dict, 'seasons', season, 'episodes', episode, 'guest_stars') # Episode screenshot/Thumbnail if Dict(episode_details_json, 'filename'): SaveDict((os.path.join( "TheTVDB", "episodes", os.path.basename( Dict(episode_details_json, 'filename'))), 1, None), TheTVDB_dict, 'seasons', season, 'episodes', episode, 'thumbs', str(TVDB_IMG_ROOT + Dict(episode_details_json, 'filename'))) Log.Info(' - [ ] thumb: {}'.format( TVDB_IMG_ROOT + Dict(episode_details_json, 'filename') if Dict( episode_details_json, 'filename') else '')) #Ep title fallback (first lang title come from ep list, second from ep details) for lang_rank, language in enumerate( language_episodes[2:rank - 1] if len(language_episodes) > 1 and rank >= 2 and not title else []): if not language: continue episode_details_json = Dict(LoadFileTVDB( id=TVDBid, filename='episode_{}_{}.json'.format( Dict(episode_json, 'id'), language), url=TVDB_EPISODE_URL.format( id=str(Dict(episode_json, 'id'))), headers={'Accept-Language': lang}), 'data', default={}) if Dict(episode_details_json, 'episodeName'): title = Dict(episode_details_json, 'episodeName') rank = lang_rank Log.Info(" - [3] title: [{}] {}".format( language_episodes[rank], title)) if not Dict(TheTVDB_dict, 'seasons', season, 'episodes', episode, 'summary') and Dict( episode_details_json, 'overview'): SaveDict( Dict(episode_details_json, 'overview')[:160].strip(" \n\r"), TheTVDB_dict, 'seasons', season, 'episodes', episode, 'summary') Log.Info(' - [3] summary: [{}] {}'.format( language_episodes[lang_rank], Dict(TheTVDB_dict, 'seasons', season, 'episodes', episode, 'summary'))) if title and Dict(TheTVDB_dict, 'seasons', season, 'episodes', episode, 'summary'): break SaveDict(title, TheTVDB_dict, 'seasons', season, 'episodes', episode, 'title') SaveDict(rank, TheTVDB_dict, 'seasons', season, 'episodes', episode, 'language_rank') # (last season) Replace all the individual episodes reported as missing with a single season 'sX' entry if not episode_missing_season_all: episode_missing.extend(episode_missing_season) elif episode_missing_season: first_entry, last_entry = episode_missing_season[ 0], episode_missing_season[-1] fm = re.match(r'((?P<abs>\d+) \()?s(?P<s>\d+)e(?P<e>\d+)\)?', first_entry).groupdict() lm = re.match(r'((?P<abs>\d+) \()?s(?P<s>\d+)e(?P<e>\d+)\)?', last_entry).groupdict() episode_missing.append( "s{}e{}-{}".format(fm['s'], fm['e'], lm['e']) if fm['abs'] is None else "{}-{} (s{}e{}-{})". format(fm['abs'], lm['abs'], fm['s'], fm['e'], lm['e'])) # Set the min/max season to ints & update max value to the next min-1 to handle multi tvdb season anidb entries map_min_values = [ int(Dict(mappingList, 'season_map')[x]['min']) for x in Dict(mappingList, 'season_map', default={}) for y in Dict(mappingList, 'season_map')[x] if y == 'min' ] for entry in Dict(mappingList, 'season_map', default={}): entry_min, entry_max = int( mappingList['season_map'][entry]['min']), int( mappingList['season_map'][entry]['max']) while entry_min != 0 and entry_max + 1 not in map_min_values and entry_max < max_season: entry_max += 1 mappingList['season_map'][entry] = { 'min': entry_min, 'max': entry_max } SaveDict(max_season, mappingList, 'season_map', 'max_season') ### Logging ### if not movie: if summary_missing: error_log['Missing Episode Summaries'].append( "TVDBid: %s | Title: '%s' | Missing Episode Summaries: %s" % (common.WEB_LINK % (common.TVDB_SERIE_URL + TVDBid, TVDBid), Dict(TheTVDB_dict, 'title'), str(summary_missing))) if summary_missing_special: error_log['Missing Special Summaries'].append( "TVDBid: %s | Title: '%s' | Missing Special Summaries: %s" % (common.WEB_LINK % (common.TVDB_SERIE_URL + TVDBid, TVDBid), Dict(TheTVDB_dict, 'title'), str(summary_missing_special))) if metadata_source.startswith("tvdb") or metadata_source.startswith( "anidb") and not movie and max(map(int, media.seasons.keys())) > 1: if episode_missing: error_log['Missing Episodes'].append( "TVDBid: %s | Title: '%s' | Missing Episodes: %s" % (common.WEB_LINK % (common.TVDB_SERIE_URL + TVDBid, TVDBid), Dict(TheTVDB_dict, 'title'), str(episode_missing))) if tvdb_special_missing: error_log['Missing Specials'].append( "TVDBid: %s | Title: '%s' | Missing Specials: %s" % (common.WEB_LINK % (common.TVDB_SERIE_URL + TVDBid, TVDBid), Dict(TheTVDB_dict, 'title'), str(tvdb_special_missing))) #Log.Debug("Episodes without Summary: " + str(sorted(summary_missing, key=common.natural_sort_key))) ### Picture types JSON download ### Log.Info("--- images ---".ljust(157, '-')) languages = [ language.strip() for language in Prefs['PosterLanguagePriority'].split(',') ] Log.Info('languages: {}'.format(languages)) for language in languages: try: bannerTypes = Dict(LoadFileTVDB( id=TVDBid, filename='images_{}.json'.format(language), url=TVDB_SERIES_IMG_INFO_URL.format(id=TVDBid), headers={'Accept-Language': language}), 'data', default={}) except: Log.Info("Invalid image JSON from url: " + TVDB_SERIES_IMG_INFO_URL % TVDBid) else: #JSON format = {"fanart", "poster", "season", "seasonwide", "series"} metanames = { 'fanart': "art", 'poster': "posters", 'series': "banners", 'season': "seasons", 'seasonwide': 'seasonwide' } count_valid = {key: 0 for key in metanames} Log.Info("bannerTypes: {}".format(bannerTypes)) #Loop per banner type ("fanart", "poster", "season", "series") skip 'seasonwide' - Load bannerType images list JSON for bannerType in bannerTypes: if bannerTypes[ bannerType] == 0 or bannerType == 'seasonwide' or movie and not bannerType in ( 'fanart', 'poster'): continue #Loop if no images #if anidb_numbering and Dict(mappingList, 'defaulttvdbseason') != '1' and bannerType=='poster': continue #skip if anidb numbered serie mapping to season 0 or 2+ Log.Info( ("--- images.%s ---" % bannerType).ljust(157, '-')) try: images = Dict(LoadFileTVDB( id=TVDBid, filename='images_{}_{}.json'.format( bannerType, language), url=TVDB_SERIES_IMG_QUERY_URL.format( id=TVDBid, type=bannerType), headers={'Accept-Language': language}), 'data', default={}) except: images = {} Log.Info( "Bad image type query data for TVDB id: %s (bannerType: %s)" % (TVDBid, bannerType)) else: images = sorted( images, key=lambda x: Dict( x, "ratingsInfo", "average", default=0), reverse=True) for image in images: #JSON format = {"data": [{"id", "keyType", "subKey"(season/graphical/text), "fileName", "resolution", "ratingsInfo": {"average", "count"}, "thumbnail"}]} if not Dict(image, 'fileName'): continue #Avod "IOError: [Errno 21] Is a directory: u'/var/lib/plexmediaserver/Library/Application Support/Plex Media Server/Plug-in Support/Data/com.plexapp.agents.hama/DataItems/TheTVDB'" if filename empty count_valid[ bannerType] = count_valid[bannerType] + 1 ### Adding picture ### rank = common.poster_rank( 'TheTVDB', metanames[bannerType], language, 0 if Dict(image, 'fileName') == Dict( series_images, bannerType) else count_valid[bannerType]) fileNamePath = os.path.join( 'TheTVDB', Dict(image, 'fileName').replace('/', os.sep)) fileNameURL = TVDB_IMG_ROOT + Dict( image, 'fileName') thumbnail = TVDB_IMG_ROOT + Dict( image, 'thumbnail') if Dict( image, 'thumbnail') else None subKey = str(Dict( image, 'subKey')) # Convert to string once if bannerType == 'season': #tvdb season posters or anidb specials and defaulttvdb season ## season 0 et empty+ season ==defaulttvdbseason(a=1) if not anidb_numbering: SaveDict((fileNamePath, rank, thumbnail), TheTVDB_dict, 'seasons', subKey, 'posters', fileNameURL) else: if subKey == Dict( mappingList, 'defaulttvdbseason' ): # If the TVDB season is the AniDB default season, add season poster as series poster SaveDict( (fileNamePath, rank, thumbnail), TheTVDB_dict, 'posters', fileNameURL) if subKey in [ '0', Dict(mappingList, 'defaulttvdbseason') ]: # If the TVDB season is the season 0 OR AniDB default season, add season poster SaveDict( (fileNamePath, rank, thumbnail), TheTVDB_dict, 'seasons', '0' if subKey == '0' else '1', 'posters', fileNameURL) else: if anidb_numbering and Dict( mappingList, 'defaulttvdbseason') != '1': rank = rank + 10 SaveDict((fileNamePath, rank, thumbnail), TheTVDB_dict, metanames[bannerType], fileNameURL) Log.Info( "[!] bannerType: {:>7} subKey: {:>9} rank: {:>3} filename: {} thumbnail: {} resolution: {} average: {} count: {}" .format(metanames[bannerType], subKey, rank, fileNameURL, thumbnail, Dict(image, 'resolution'), Dict(image, 'ratingsInfo', 'average'), Dict(image, 'ratingsInfo', 'count'))) #Log.Info("Posters : {}/{}, Season posters: {}/{}, Art: {}/{}".format(count_valid['poster'], Dict(bannerTypes, 'poster'), count_valid['season'], Dict(bannerTypes, 'season') or 0, count_valid['fanart'], Dict(bannerTypes, 'fanart'))) if not Dict(bannerTypes, 'poster'): error_log['TVDB posters missing'].append( "TVDBid: %s | Title: '%s'" % (common.WEB_LINK % (common.TVDB_SERIE_URL + TVDBid, TVDBid), Dict(TheTVDB_dict, 'title'))) if not Dict(bannerTypes, 'season'): error_log['TVDB season posters missing'].append( "TVDBid: %s | Title: '%s'" % (common.WEB_LINK % (common.TVDB_SERIE_URL + TVDBid, TVDBid), Dict(TheTVDB_dict, 'title'))) Log.Info("--- final summary info ---".ljust(157, '-')) Log.Info( "url: '{}', IMDbid: {}, Present episodes: {}, Missing: {}".format( TVDB_SERIES_URL.format(id=TVDBid), IMDbid, ep_count, sorted(episode_missing, key=common.natural_sort_key))) Log.Info("--- return ---".ljust(157, '-')) Log.Info("absolute_map: {}".format( DictString(Dict(mappingList, 'absolute_map', default={}), 0))) Log.Info("season_map: {}".format( DictString(Dict(mappingList, 'season_map', default={}), 0))) Log.Info("TheTVDB_dict: {}".format(DictString(TheTVDB_dict, 4))) return TheTVDB_dict, IMDbid
def GetMetadata(media, movie, error_log, source, AniDBid, TVDBid, AniDBMovieSets, mappingList): ''' Download metadata to dict_AniDB, ANNid, MALid ''' Log.Info("=== AniDB.GetMetadata() ===".ljust(157, '=')) AniDB_dict, ANNid, MALid = {}, "", "" original = AniDBid anidb_numbering = source == "anidb" and ( movie or max(map(int, media.seasons.keys())) <= 1) language_posters = [ language.strip() for language in Prefs['PosterLanguagePriority'].split(',') ] priority_posters = [ provider.strip() for provider in Prefs['posters'].split(',') ] ### Build the list of anidbids for files present #### if source.startswith("tvdb") or source.startswith( "anidb") and not movie and max(map(int, media.seasons.keys( ))) > 1: #multi anidbid required only for tvdb numbering full_array = [ anidbid for season in Dict(mappingList, 'TVDB') or [] for anidbid in Dict(mappingList, 'TVDB', season) if season and 'e' not in season and anidbid.isdigit() ] AniDB_array = { AniDBid: [] } if Dict(mappingList, 'defaulttvdbseason') == '1' and source != 'tvdb4' else {} for season in sorted( media.seasons, key=common.natural_sort_key ) if not movie else []: # For each season, media, then use metadata['season'][season]... for episode in sorted(media.seasons[season].episodes, key=common.natural_sort_key): if int(episode) > 99: continue # AniDB non-normal special (op/ed/t/o) that is not mapable if source == 'tvdb3' and season != 0: new_season, new_episode, anidbid = AnimeLists.anidb_ep( mappingList, season, Dict(mappingList, 'absolute_map', episode, default=(None, episode)) [1]) # Pull absolute number then try to map elif source == 'tvdb4' and season != 0: new_season, new_episode = Dict(mappingList, 'absolute_map', episode, default=(season, episode)) anidbid = 'UNKN' # Not TVDB mapping. Use custom ASS mapping to pull season/episode else: new_season, new_episode, anidbid = AnimeLists.anidb_ep( mappingList, season, episode) # Try to map numbering = 's{}e{}'.format(season, episode) + ( '(s{}e{})'.format(new_season, new_episode) if season != new_season and episode != new_episode else '') if anidbid and not (new_season == '0' and new_episode == '0'): SaveDict([numbering], AniDB_array, anidbid) else: continue elif source.startswith('anidb') and AniDBid != "": full_array, AniDB_array = [AniDBid], {AniDBid: []} else: full_array, AniDB_array = [], {} active_array = full_array if Dict( mappingList, 'possible_anidb3' ) or source in ("tvdb4", "tvdb6") else AniDB_array.keys( ) # anidb3(tvdb)/anidb4(tvdb6) for full relation_map data | tvdb4 bc the above will not be able to know the AniDBid Log.Info( "Source: {}, AniDBid: {}, Full AniDBids list: {}, Active AniDBids list: {}" .format(source, AniDBid, full_array, active_array)) for anidbid in sorted(AniDB_array, key=common.natural_sort_key): Log.Info('[+] {:>5}: {}'.format(anidbid, AniDB_array[anidbid])) Log.Info('language_posters: {}'.format(language_posters)) ### Build list_abs_eps for tvdb 3/4/5 ### list_abs_eps, list_sp_eps = {}, [] if source in ('tvdb3', 'tvdb4'): for s in media.seasons: for e in media.seasons[s].episodes: if s == '0': list_sp_eps.append(e) else: list_abs_eps[e] = s Log.Info('Present abs eps: {}'.format(list_abs_eps)) ### Load anidb xmls in tvdb numbering format if needed ### for AniDBid in sorted(active_array, key=common.natural_sort_key): is_primary_entry = AniDBid == original or len(active_array) == 1 Log.Info(("--- %s ---" % AniDBid).ljust(157, '-')) Log.Info('AniDBid: {}, IsPrimary: {}, url: {}'.format( AniDBid, is_primary_entry, ANIDB_HTTP_API_URL + AniDBid)) Log.Info(("--- %s.series ---" % AniDBid).ljust(157, '-')) xml, cache = None, CACHE_1DAY * 6 xml_cache = common.LoadFileCache(filename=AniDBid + ".xml", relativeDirectory=os.path.join( "AniDB", "xml"))[0] if xml_cache: # Pull the enddate and adjust max cache age based on series enddate in relation to now ed = GetXml( xml_cache, 'enddate') or datetime.datetime.now().strftime("%Y-%m-%d") enddate = datetime.datetime.strptime( "{}-12-31".format(ed) if len(ed) == 4 else "{}-{}".format( ed, ([30, 31] if int(ed[-2:]) <= 7 else [31, 30] )[int(ed[-2:]) % 2] if ed[-2:] != '02' else 28) if len(ed) == 7 else ed, '%Y-%m-%d') days_old = (datetime.datetime.now() - enddate).days if days_old > 1825: cache = CACHE_1DAY * 365 # enddate > 5 years ago => 1 year cache elif days_old > 30: cache = ( days_old * CACHE_1DAY * 365 ) / 1825 # enddate > 30 days ago => (days_old/5yrs ended = x/1yrs cache) if AniDBBan: xml = xml_cache # Ban has been hit in this process' life span (which is transient) else: xml = common.LoadFile(filename=AniDBid + ".xml", relativeDirectory=os.path.join( "AniDB", "xml"), url=ANIDB_HTTP_API_URL + AniDBid, cache=cache, sleep=6, throttle=['AniDB', CACHE_1HOUR, 100]) if isinstance(xml, str) and 'banned' in xml: global AniDBBan AniDBBan = True # Set ban hit on process level if AniDBBan: SaveDict(True, AniDB_dict, 'Banned') # Set ban hit on series level if not xml or isinstance(xml, str): title, original_title, language_rank = GetAniDBTitle( AniDBTitlesDB.xpath( '/animetitles/anime[@aid="{}"]/title'.format(AniDBid))) if is_primary_entry: Log.Info("[ ] title: {}".format( SaveDict(title, AniDB_dict, 'title'))) Log.Info("[ ] original_title: {}".format( SaveDict(original_title, AniDB_dict, 'original_title'))) Log.Info("[ ] language_rank: {}".format( SaveDict(language_rank, AniDB_dict, 'language_rank'))) elif xml: title, original_title, language_rank = GetAniDBTitle( xml.xpath('/anime/titles/title')) if is_primary_entry: ### for each main anime AniDBid ### Log.Info("[ ] title: {}".format( SaveDict(title, AniDB_dict, 'title'))) Log.Info("[ ] original_title: {}".format( SaveDict(original_title, AniDB_dict, 'original_title'))) Log.Info("[ ] language_rank: {}".format( SaveDict(language_rank, AniDB_dict, 'language_rank'))) if SaveDict(GetXml(xml, 'startdate'), AniDB_dict, 'originally_available_at'): Log.Info("[ ] originally_available_at: '{}'".format( AniDB_dict['originally_available_at'])) if SaveDict(summary_sanitizer(GetXml( xml, 'description')), AniDB_dict, 'summary' ) and not movie and not anidb_numbering and Dict( mappingList, 'defaulttvdbseason').isdigit() and mappingList[ 'defaulttvdbseason'] in media.seasons: SaveDict(AniDB_dict['summary'], AniDB_dict, 'seasons', mappingList['defaulttvdbseason'], 'summary') Log.Info("[ ] rating: '{}'".format( SaveDict(GetXml(xml, 'ratings/permanent'), AniDB_dict, 'rating'))) ### Posters if GetXml(xml, 'picture'): rank = 1 if 'en' in language_posters: rank = (rank // 30) * 30 * language_posters.index( 'en') + rank % 30 if 'AniDB' in priority_posters: rank = rank + 6 * priority_posters.index('AniDB') AniDB_dict['posters'] = { ANIDB_PIC_BASE_URL + GetXml(xml, 'picture'): (os.path.join('AniDB', 'poster', GetXml(xml, 'picture')), rank, None) } # ANIDB_PIC_THUMB_URL.format(name=GetXml(xml, 'picture').split('.')[0]) ### genre ### RESTRICTED_GENRE = { "18 restricted": 'X', "pornography": 'X', "tv censoring": 'TV-MA', "borderline p**n": 'TV-MA' } for tag in xml.xpath('tags/tag'): if GetXml(tag, 'name') and tag.get( 'weight', '').isdigit() and int( tag.get('weight', '') or '200') >= int( Prefs['MinimumWeight'] or '200'): SaveDict([string.capwords(GetXml(tag, 'name'), '-')], AniDB_dict, 'genres') if GetXml(tag, 'name').lower() in RESTRICTED_GENRE: AniDB_dict['content_rating'] = RESTRICTED_GENRE[ GetXml(tag, 'name').lower()] if Dict(AniDB_dict, 'genres'): AniDB_dict['genres'].sort() SaveDict( "Continuing" if GetXml(xml, 'Anime/enddate') == "1970-01-01" else "Ended", AniDB_dict, 'status') Log.Info("[ ] genres ({}/{} above {} weight): {}".format( len(Dict(AniDB_dict, 'genres')), len(xml.xpath('tags/tag')), int(Prefs['MinimumWeight'] or 200), Dict(AniDB_dict, 'genres'))) for element in AniDBMovieSets.xpath( "/anime-set-list/set/anime"): if element.get('anidbid') == AniDBid or element.get( 'anidbid') in full_array: node = element.getparent() title, main, language_rank = GetAniDBTitle( node.xpath('titles')[0]) if title not in Dict(AniDB_dict, 'collections', default=[]): Log.Info( "[ ] title: {}, main: {}, language_rank: {}". format(title, main, language_rank)) SaveDict([title], AniDB_dict, 'collections') Log.Info( "[ ] collection: AniDBid '%s' is part of movie collection: '%s', related_anime_list: %s" % (AniDBid, title, str(full_array))) if not Dict(AniDB_dict, 'collections'): Log.Info( "[ ] collection: AniDBid '%s' is not part of any collection, related_anime_list: %s" % (AniDBid, str(full_array))) #roles ### NEW, NOT IN Plex FrameWork Documentation 2.1.1 ### Log.Info(("--- %s.actors ---" % AniDBid).ljust(157, '-')) for role in xml.xpath( 'characters/character[(@type="secondary cast in") or (@type="main character in")]' ): try: if GetXml(role, 'seiyuu') and GetXml(role, 'name'): role_dict = { 'role': role.find('name').text, 'name': role.find('seiyuu').text, 'photo': ANIDB_PIC_BASE_URL + role.find('seiyuu').get('picture') } SaveDict([role_dict], AniDB_dict, 'roles') Log.Info( '[ ] role: {:<20}, name: {:<20}, photo: {}'. format(role_dict['role'], role_dict['name'], role_dict['photo'])) except Exception as e: Log.Info("Seyiuu error: {}".format(e)) ### Creators ### creator_tags = { "Animation Work": "studio", "Work": "studio", "Direction": "directors", "Series Composition": "producers", "Original Work": "writers", "Script": "writers", "Screenplay": "writers" } studios = {} creators = {} for creator in xml.xpath('creators/name'): for tag in creator_tags: if tag != creator.get('type'): continue if creator_tags[tag] == "studio": studios[tag] = creator.text else: SaveDict([creator.text], creators, creator_tags[tag]) if is_primary_entry: Log.Info("[ ] studio: {}".format( SaveDict( Dict(studios, "Animation Work", default=Dict(studios, "Work")), AniDB_dict, 'studio'))) Log.Info("[ ] movie: {}".format( SaveDict(GetXml(xml, 'type') == 'Movie', AniDB_dict, 'movie'))) ### Movie ### if movie: Log.Info("[ ] year: '{}'".format( SaveDict( GetXml(xml, 'startdate')[0:4], AniDB_dict, 'year'))) if is_primary_entry: for creator in creators: Log.Info("[ ] {}: {}".format( creator, SaveDict(creators[creator], AniDB_dict, creator))) Log.Info(("--- %s.summary info ---" % AniDBid).ljust(157, '-')) ### Series ### else: ### Translate into season/episode mapping numEpisodes, totalDuration, mapped_eps, ending_table, op_nb = 0, 0, [], {}, 0 specials = { 'S': [0, 'Special'], 'C': [100, 'Opening/Ending'], 'T': [200, 'Trailer'], 'P': [300, 'Parody'], 'O': [400, 'Other'] } movie_ep_groups = {} ending_offset = 99 missing = {'0': [], '1': []} ### Episodes (and specials) not always in right order ### Log.Info(("--- %s.episodes ---" % AniDBid).ljust(157, '-')) Log.Info("[ ] ep creators (creators tag): " + str(creators)) for ep_obj in sorted( xml.xpath('episodes/episode'), key=lambda x: [ int(x.xpath('epno')[0].get('type')), int( x.xpath('epno')[0].text if x.xpath('epno')[0].text.isdigit() else x. xpath('epno')[0].text[1:]) ]): ### Title, Season, Episode number, Specials title, main, language_rank = GetAniDBTitle( ep_obj.xpath('title'), [ language.strip() for language in Prefs['EpisodeLanguagePriority'].split(',') ]) if not anidb_numbering and title == 'Complete Movie': title = "" # For mapping use meanningful titles epNum = ep_obj.xpath('epno')[0] epNumType = epNum.get('type') season = "1" if epNumType == "1" else "0" if epNumType == "3" and ep_obj.xpath( 'title')[0].text.startswith('Ending') and int( epNum.text[1:]) - 1 < ending_offset: ending_offset = int(epNum.text[1:]) - 1 if epNumType == "3" and int( epNum.text[1:]) > ending_offset: episode = str( int(epNum.text[1:]) + 150 - ending_offset) #shifted to 150 for 1st ending. elif epNumType == "1": episode = epNum.text else: episode = str(specials[epNum.text[0]][0] + int(epNum.text[1:])) numbering = "s{}e{:>3}".format(season, episode) #If tvdb numbering used, save anidb episode meta using tvdb numbering if source.startswith("tvdb") or source.startswith( "anidb") and not movie and max( map(int, media.seasons.keys())) > 1: season, episode = AnimeLists.tvdb_ep( mappingList, season, episode, AniDBid) # Get episode number to absolute number if source in ('tvdb3', 'tvdb4') and season not in ['-1', '0']: if source == 'tvdb4' or season == '1': ms, usl = ( season, True) if source == 'tvdb3' else ( Dict(mappingList, 'absolute_map', 'max_season'), Dict(mappingList, 'absolute_map', 'unknown_series_length')) if ms and usl: season = Dict(mappingList, 'absolute_map', episode, default=(ms if usl else str(int(ms) + 1), None))[0] else: try: episode = list( Dict(mappingList, 'absolute_map', default={}).keys())[list( Dict(mappingList, 'absolute_map', default={}).values() ).index((season, episode))] except: pass if not(season =='0' and episode in list_sp_eps) and \ not(source in ('tvdb3', 'tvdb4') and episode in list_abs_eps) and \ not(season in media.seasons and episode in media.seasons[season].episodes): Log.Info( '[ ] {} => s{:>1}e{:>3} epNumType: {}'.format( numbering, season, episode, epNumType)) continue ### Series poster as season poster if GetXml(xml, 'picture') and not Dict( AniDB_dict, 'seasons', season, 'posters', ANIDB_PIC_BASE_URL + GetXml(xml, 'picture')): rank = 1 if 'en' in language_posters: rank = (rank // 30) * 30 * language_posters.index( 'en') + rank % 30 if 'AniDB' in priority_posters: rank = rank + 6 * priority_posters.index( 'AniDB') SaveDict( (os.path.join('AniDB', 'poster', GetXml(xml, 'picture')), rank, None), AniDB_dict, 'seasons', season, 'posters', ANIDB_PIC_BASE_URL + GetXml(xml, 'picture')) ### In AniDB numbering, Movie episode group, create key and create key in dict with empty list if doesn't exist ### else: #if source.startswith("anidb") and not movie and max(map(int, media.seasons.keys()))<=1: ### Movie episode group, create key and create key in dict with empty list if doesn't exist ### key = '' if epNumType == '1' and GetXml( xml, '/anime/episodecount') == '1' and GetXml( xml, '/anime/type') in ('Movie', 'OVA'): key = '1' if title in ( 'Complete Movie', 'OVA' ) else title[-1] if title.startswith( 'Part ') and title[-1].isdigit() else '' #'-1' if key: SaveDict([], movie_ep_groups, key) #Episode missing from disk if not season in media.seasons or not episode in media.seasons[ season].episodes: Log.Info( '[ ] {} => s{:>1}e{:>3} air_date: {}'.format( numbering, season, episode, GetXml(ep_obj, 'airdate'))) current_air_date = GetXml(ep_obj, 'airdate').replace( '-', '') current_air_date = int( current_air_date ) if current_air_date.isdigit() and int( current_air_date) > 10000000 else 99999999 if int(time.strftime( "%Y%m%d")) > current_air_date + 1: if epNumType == '1' and key: SaveDict([numbering], movie_ep_groups, key) elif epNumType in ['1', '2']: SaveDict([episode], missing, season) continue ### Episodes SaveDict(language_rank, AniDB_dict, 'seasons', season, 'episodes', episode, 'language_rank') SaveDict(title, AniDB_dict, 'seasons', season, 'episodes', episode, 'title') Log.Info( '[X] {} => s{:>1}e{:>3} air_date: {} language_rank: {}, title: "{}"' .format(numbering, season, episode, GetXml(ep_obj, 'airdate'), language_rank, title)) if GetXml(ep_obj, 'length').isdigit(): SaveDict( int(GetXml(ep_obj, 'length')) * 1000 * 60, AniDB_dict, 'seasons', season, 'episodes', episode, 'duration' ) # AniDB stores it in minutes, Plex save duration in millisecs if season == "1": numEpisodes, totalDuration = numEpisodes + 1, totalDuration + int( GetXml(ep_obj, 'length')) SaveDict(GetXml(ep_obj, 'rating'), AniDB_dict, 'seasons', season, 'episodes', episode, 'rating') SaveDict(GetXml(ep_obj, 'airdate'), AniDB_dict, 'seasons', season, 'episodes', episode, 'originally_available_at') ep_summary = SaveDict( summary_sanitizer(GetXml(ep_obj, 'summary')), AniDB_dict, 'seasons', season, 'episodes', episode, 'summary') Log.Info(' - [ ] summary: {}'.format( (ep_summary[:200] ).replace("\n", "\\n").replace("\r", "\\r") + '..' if len(ep_summary) > 200 else ep_summary)) for creator in creators: SaveDict(",".join(creators[creator]), AniDB_dict, 'seasons', season, 'episodes', episode, creator) ### End of for ep_obj... Log.Info(("--- %s.summary info ---" % AniDBid).ljust(157, '-')) if SaveDict((int(totalDuration) / int(numEpisodes)) * 60 * 1000 if int(numEpisodes) else 0, AniDB_dict, 'duration'): Log.Info( "Duration: {}, numEpisodes: {}, average duration: {}". format(str(totalDuration), str(numEpisodes), AniDB_dict['duration'])) ### AniDB numbering Missing Episodes ### if source.startswith("anidb") and not movie and max( map(int, media.seasons.keys())) <= 1: if movie_ep_groups: Log.Info( "Movie/OVA Ep Groups: %s" % movie_ep_groups ) #movie_ep_groups: {'1': ['s1e1'], '3': ['s1e4', 's1e5', 's1e6'], '2': ['s1e3'], '-1': []} SaveDict([ value for key in movie_ep_groups for value in movie_ep_groups[key] if 0 < len(movie_ep_groups[key]) < int(key) ], missing, '1') for season in sorted(missing): missing_eps = sorted(missing[season], key=common.natural_sort_key) Log.Info('Season: {} Episodes: {} not on disk'.format( season, missing_eps)) if missing_eps: error_log[ 'Missing Specials' if season == '0' else 'Missing Episodes'].append( "AniDBid: %s | Title: '%s' | Missing Episodes: %s" % (common.WEB_LINK % (common.ANIDB_SERIE_URL + AniDBid, AniDBid), AniDB_dict['title'], str(missing_eps))) ### End of if not movie ### # Generate relations_map for anidb3/4(tvdb1/6) modes for relatedAnime in xml.xpath('/anime/relatedanime/anime'): if relatedAnime.get('id') not in Dict(mappingList, 'relations_map', AniDBid, relatedAnime.get('type'), default=[]): SaveDict([relatedAnime.get('id')], mappingList, 'relations_map', AniDBid, relatedAnime.get('type')) # External IDs ANNid = GetXml( xml, "/anime/resources/resource[@type='1']/externalentity/identifier" ) MALid = GetXml( xml, "/anime/resources/resource[@type='2']/externalentity/identifier" ) #ANFOid = GetXml(xml, "/anime/resources/resource[@type='3']/externalentity/identifier"), GetXml(xml, "/anime/resources/resource[@type='3']/externalentity/identifier") # Logs if not Dict(AniDB_dict, 'summary'): error_log['AniDB summaries missing'].append( "AniDBid: %s" % (common.WEB_LINK % (common.ANIDB_SERIE_URL + AniDBid, AniDBid) + " | Title: '%s'" % Dict(AniDB_dict, 'title'))) if not Dict(AniDB_dict, 'posters'): error_log['AniDB posters missing'].append( "AniDBid: %s" % (common.WEB_LINK % (common.ANIDB_SERIE_URL + AniDBid, AniDBid) + " | Title: '%s'" % Dict(AniDB_dict, 'title'))) #if not Dict(AniDB_dict, 'studio' ): error_log['anime-list studio logos'].append("AniDBid: %s | Title: '%s' | AniDB has studio '%s' and anime-list has '%s' | " % (common.WEB_LINK % (ANIDB_SERIE_URL % AniDBid, AniDBid), title, metadata.studio, mapping_studio) + common.WEB_LINK % (ANIDB_TVDB_MAPPING_FEEDBACK % ("aid:" + metadata.id + " " + title, String.StripTags( XML.StringFromElement(xml, encoding='utf8'))), "Submit bug report (need GIT account)")) #if metadata.studio and 'studio' in AniDB_dict and AniDB_dict ['studio'] and AniDB_dict ['studio'] != metadata.studio: error_log['anime-list studio logos'].append("AniDBid: %s | Title: '%s' | AniDB has studio '%s' and anime-list has '%s' | " % (common.WEB_LINK % (ANIDB_SERIE_URL % AniDBid, AniDBid), title, metadata.studio, mapping_studio) + common.WEB_LINK % (ANIDB_TVDB_MAPPING_FEEDBACK % ("aid:" + metadata.id + " " + title, String.StripTags( XML.StringFromElement(xml, encoding='utf8'))), "Submit bug report (need GIT account)")) #if metadata.studio == "" and 'studio' in AniDB_dict and AniDB_dict ['studio'] == "": error_log['anime-list studio logos'].append("AniDBid: %s | Title: '%s' | AniDB and anime-list are both missing the studio" % (common.WEB_LINK % (ANIDB_SERIE_URL % AniDBid, AniDBid), title) ) Log.Info("ANNid: '%s', MALid: '%s', xml loaded: '%s'" % (ANNid, MALid, str(xml is not None))) Log.Info("--- return ---".ljust(157, '-')) Log.Info("relations_map: {}".format( DictString(Dict(mappingList, 'relations_map', default={}), 1))) Log.Info("AniDB_dict: {}".format(DictString(AniDB_dict, 4))) return AniDB_dict, ANNid, MALid
def GetMetadata(movie, MALid): Log.Info("".ljust(157, '-')) Log.Info("MyAnimeList.GetMetadata() - MALid: '%s'" % MALid) if not MALid or not MALid.isdigit(): return {} MAL_HTTP_API_URL = "http://fribbtastic-api.net/fribbtastic-api/services/anime?id=" MAL_PREFIX = "https://myanimelist.cdn-dena.com" # Some links in the XML will come from TheTVDB, not adding those.... MyAnimeList_dict = {} xml = common.LoadFile(filename=MALid + ".xml", relativeDirectory=os.path.join('MyAnimeList', 'xml'), url=MAL_HTTP_API_URL + MALid, cache=CACHE_1DAY * 7) if xml: SaveDict(GetXml(xml, 'title'), MyAnimeList_dict, 'title') SaveDict(GetXml(xml, 'synopsis'), MyAnimeList_dict, 'summary') SaveDict(GetXml(xml, 'rating'), MyAnimeList_dict, 'score') #SaveDict( GetXml(xml, 'content_rating').split(" ")[0], MyAnimeList_dict, 'rating' ) SaveDict(GetXml(xml, 'firstAired'), MyAnimeList_dict, 'originally_available_at') #for item in xml.xpath('//anime/genres/genre' or []): SaveDict([item.text], MyAnimeList_dict, 'genres') if GetXml(xml, '//anime/genres/genre'): SaveDict([item.text for item in xml.xpath('//anime/genres/genre')], MyAnimeList_dict, 'genres') if GetXml(xml, 'status') == 'Currently Airing': SaveDict("Continuing", MyAnimeList_dict, 'genres') if GetXml(xml, 'status') == 'Finished Airing': SaveDict("Ended", MyAnimeList_dict, 'genres') if GetXml(xml, 'type') == 'TV': SaveDict("Serie", MyAnimeList_dict, 'genres') if GetXml(xml, 'type') == 'Movie': SaveDict("Movie", MyAnimeList_dict, 'genres') if GetXml(xml, 'type') == 'Special': SaveDict("Special", MyAnimeList_dict, 'genres') for item in xml.xpath('//anime/episodes/episode') or []: episode = GetXml(item, 'episodeNumber') SaveDict(GetXml(xml, 'engTitle'), MyAnimeList_dict, 'seasons', "1", 'episodes', episode, 'title') SaveDict(GetXml(xml, 'aired'), MyAnimeList_dict, 'seasons', "1", 'episodes', episode, 'originally_available_at') for item in xml.xpath('//anime/covers/cover'): SaveDict(("MyAnimeList/" + "/".join(item.text.split('/')[3:]), 50, None) if item.text.startswith(MAL_PREFIX) else "", MyAnimeList_dict, 'posters', item.text) for item in xml.xpath('//anime/backgrounds/background'): SaveDict(("MyAnimeList/" + "/".join(item.text.split('/')[3:]), 50, None) if item.text.startswith(MAL_PREFIX) else "", MyAnimeList_dict, 'art', item.text) for item in xml.xpath('//anime/banners/banner'): SaveDict(("MyAnimeList/" + "/".join(item.text.split('/')[3:]), 50, None) if item.text.startswith(MAL_PREFIX) else "", MyAnimeList_dict, 'banners', item.text) return MyAnimeList_dict
def GetMetadata(media, movie, TVDBid, TMDbid, IMDbid): TMDB_MOVIE_SEARCH_BY_TMDBID = 'http://api.tmdb.org/3/movie/%s?api_key=7f4a0bd0bd3315bb832e17feda70b5cd&append_to_response=releases,credits,trailers,external_ids&language=en' #Work with IMDbid TMDB_SERIE_SEARCH_BY_TVDBID = "http://api.TheMovieDb.org/3/find/%s?api_key=7f4a0bd0bd3315bb832e17feda70b5cd&external_source=tvdb_id&append_to_response=releases,credits,trailers,external_ids&language=en" TMDB_CONFIG_URL = 'http://api.tmdb.org/3/configuration?api_key=7f4a0bd0bd3315bb832e17feda70b5cd' #TMDB_MOVIE_GENRE_LIST = "https://api.TheMovieDb.org/3/genre/movie/list?api_key=7f4a0bd0bd3315bb832e17feda70b5cd&language=en-US" #TMDB_SERIE_GENRE_LIST = "https://api.TheMovieDb.org/3/genre/tv/list?api_key=7f4a0bd0bd3315bb832e17feda70b5cd&language=en-US" dict_TheMovieDb = {} TSDbid = "" Log.Info("".ljust(157, '-')) Log("TheMovieDb.GetMetadata() - TVDBid: {}, TMDbid: {}, IMDbid: {}".format( TVDBid, TMDbid, IMDbid)) if TMDbid: url, filename = TMDB_MOVIE_SEARCH_BY_TMDBID % TMDbid, "TMDB-" + TMDbid + ".json" elif IMDbid and TVDBid == 'movie': url, filename = TMDB_MOVIE_SEARCH_BY_TMDBID % IMDbid, "IMDb-" + IMDbid + ".json" elif TVDBid.isdigit(): url, filename = TMDB_SERIE_SEARCH_BY_TVDBID % TVDBid, "TVDB-" + TVDBid + ".json" else: return dict_TheMovieDb, TSDbid, TMDbid, IMDbid json = common.LoadFile(filename=filename, relativeDirectory=os.path.join( 'TheMovieDb', 'json'), url=url, cache=CACHE_1WEEK) config_dict = common.LoadFile(filename="TMDB_CONFIG_URL.json", relativeDirectory="TheMovieDb", url=TMDB_CONFIG_URL, cache=CACHE_1DAY * 30) mode = "movie" if movie else "tv" if not json: Log.Info("TMDB - url: failed to get json" + TMDB_MOVIE_SEARCH_BY_TMDBID % TMDbid) else: if 'tv_results' in json and json['tv_results']: json, mode = json['tv_results'][0], "tv" elif 'movie_results' in json and json['movie_results']: json, mode = json['movie_results'][0], "movie" SaveDict(Dict(json, 'title'), dict_TheMovieDb, 'title') SaveDict( Dict(json, 'vote_average'), dict_TheMovieDb, 'rating' ) #if 'vote_count' in json and json['vote_count'] > 3: SaveDict( Dict(json, 'vote_average'), dict_TheMovieDb, 'rating') SaveDict(Dict(json, 'tagline'), dict_TheMovieDb, 'tagline') SaveDict(Dict(json, 'overview'), dict_TheMovieDb, 'summary') SaveDict(Dict(json, 'runtime'), dict_TheMovieDb, 'duration') SaveDict(Dict(json, 'origin_country'), dict_TheMovieDb, 'countries') SaveDict(Dict(json, 'first_air_date'), dict_TheMovieDb, 'originally_available_at') if Dict(json, 'belongs_to_collection', 'name'): SaveDict([ Dict(json, 'belongs_to_collection', 'name').replace( ' Collection', '') ], dict_TheMovieDb, 'collections') if Dict(json, 'genres'): SaveDict( [Dict(genre, 'name') for genre in Dict(json, 'genres') or []], dict_TheMovieDb, 'genres') if Dict(json, 'poster_path'): dict_TheMovieDb['posters'] = { config_dict['images']['base_url'] + 'original' + json['poster_path']: (os.path.join('TheMovieDb', 'poster', json['poster_path'].lstrip('/')), 90, None) } if Dict(json, 'backdrop_path'): dict_TheMovieDb['art'] = { config_dict['images']['base_url'] + 'original' + json['backdrop_path']: (os.path.join('TheMovieDb', 'artwork', json['backdrop_path'].lstrip('/')), 90, config_dict['images']['base_url'] + 'w300' + json['backdrop_path']) } try: SaveDict( int(Dict(json, 'duration')) * 60 * 1000, dict_TheMovieDb, 'duration') except: pass #Log.Info(str(dict_TheMovieDb)) if mode == 'tv': TSDbid = Dict(json, 'id') elif not TMDbid: TMDbid = Dict(json, 'id') if not IMDbid: IMDbid = Dict(json, 'imdb_id') #SaveDict( studio['name'].strip(), dict_TheMovieDb, 'studio') for studio in Dict(json, 'production_companies') or []: if studio['id'] <= json['production_companies'][0]['id']: SaveDict(studio['name'].strip(), dict_TheMovieDb, 'studio') ### More pictures ### Log.Info( "TheMovieDb.GetMetadata() - TMDbid: '{}', TSDbid: '{}', IMDbid: '{}'". format(TMDbid, TSDbid, IMDbid)) for id in IMDbid.split(',') if ',' in IMDbid else []: TMDB_MOVIE_IMAGES_URL = 'https://api.tmdb.org/3/{mode}/{id}/images?api_key=7f4a0bd0bd3315bb832e17feda70b5cd' json = common.LoadFile(filename="TMDB-" + (IMDbid or TMDbid) + ".json", relativeDirectory="TMDB", url=TMDB_MOVIE_IMAGES_URL.format(id=id, mode=mode), cache=CACHE_1WEEK) for index, poster in enumerate(Dict(json, 'posters') or []): if Dict(json, 'posters', index, 'file_path'): SaveDict((os.path.join('TheMovieDb', 'poster', "%s-%s.jpg" % (TMDbid, index)), 40, None), dict_TheMovieDb, 'posters', config_dict['images']['base_url'] + 'original' + json['posters'][index]['file_path']) for index, poster in enumerate(Dict(json, 'backdrops') or []): if Dict(json, 'backdrops', index, 'file_path'): SaveDict((os.path.join( 'TheMovieDb', 'artwork', "%s-%s-art.jpg" % (TMDbid, index)), 40, config_dict['images']['base_url'] + 'w300' + json['backdrops'][index]['file_path']), dict_TheMovieDb, 'art', config_dict['images']['base_url'] + 'original' + json['backdrops'][index]['file_path']) return dict_TheMovieDb, TSDbid, TMDbid, IMDbid
def GetMetadata(media, movie): Log.Info("".ljust(157, '-')) Log.Info("Local.GetMetadata()") if movie: return Local_dict = {} dir = GetMediaDir(media, movie) library, root, path = GetLibraryRootPath(dir) if not path in ('_unknown_folder', '.'): Log.Info('Local.GetMetadata() - dir: {}, library:{}, root:{}, path:{}'. format(dir, library, root, path)) series_root_folder = os.path.join(root, path.split(os.sep, 1)[0]) subfolder_count = len([ file for file in os.listdir(series_root_folder) if os.path.isdir(os.path.join(series_root_folder, file)) ]) Log.Info( 'Local.GetMetadata() - series_root_folder: {}, subfolder_count: {}' .format(series_root_folder, subfolder_count)) ### Extract season and transparent folder to reduce complexity and use folder as serie name ### reverse_path, season_folder_first = list(reversed(path.split( os.sep))), False SEASON_RX = [ 'Specials', # Specials (season 0) '(Season|Series|Book|Saison|Livre|S)[ _\-]*(?P<season>[0-9]{1,2}).*', # Season ##, Series #Book ## Saison ##, Livre ##, S##, S ## '(?P<show>.*?)[\._\- ]+[sS](?P<season>[0-9]{2})', # (title) S01 '(?P<season>[0-9]{1,2})a? Stagione.*', # ##a Stagione '(?P<season>[0-9]{1,2}).*', # ## '^.*([Ss]aga]|([Ss]tory )?[Aa][Rr][KkCc]).*$' # Last entry in array, folder name droped but files kept: Story, Arc, Ark, Video ] # for folder in reverse_path[: -1]: # remove root folder from test, [:-1] Doesn't thow errors but gives an empty list if items don't exist, might not be what you want in other cases for rx in SEASON_RX: # in anime, more specials folders than season folders, so doing it first if re.match( rx, folder, re.IGNORECASE ): # get season number but Skip last entry in seasons (skipped folders) reverse_path.remove( folder ) # Since iterating slice [:] or [:-1] doesn't hinder iteration. All ways to remove: reverse_path.pop(-1), reverse_path.remove(thing|array[0]) if rx != SEASON_RX[-1] and len( reverse_path) >= 2 and folder == reverse_path[-2]: season_folder_first = True break if len( reverse_path ) > 1 and not season_folder_first and subfolder_count > 1: ### grouping folders only ### Log.Info( "Grouping folder found, root: {}, path: {}, Grouping folder: {}, subdirs: {}, reverse_path: {}" .format(root, path, os.path.basename(series_root_folder), subfolder_count, reverse_path)) SaveDict(reverse_path[-1], Local_dict, 'collection') else: Log.Info( "Grouping folder not found, root: {}, path: {}, Grouping folder: {}, subdirs: {}, reverse_path: {}" .format(root, path, os.path.basename(series_root_folder), subfolder_count, reverse_path)) return Local_dict
def GetMetadata(media, movie, error_log, lang, metadata_source, AniDBid, TVDBid, IMDbid, mappingList, AniDB_movie): ''' TVDB - Load serie JSON ''' Log.Info("=== TheTVDB.GetMetadata() ===".ljust(157, '=')) TheTVDB_dict = {} max_season = 0 anidb_numbering = metadata_source=="anidb" and (movie or max(map(int, media.seasons.keys()))<=1) anidb_prefered = anidb_numbering and Dict(mappingList, 'defaulttvdbseason') != '1' language_series = [language.strip() if language.strip() not in ('x-jat', 'zh-Hans', 'zh-Hant', 'zh-x-yue', 'zh-x-cmn', 'zh-x-nan') else '' for language in Prefs['SerieLanguagePriority' ].split(',') ] language_episodes = [language.strip() if language.strip() not in ('x-jat', 'zh-Hans', 'zh-Hant', 'zh-x-yue', 'zh-x-cmn', 'zh-x-nan') else '' for language in Prefs['EpisodeLanguagePriority'].split(',') ] Log.Info("TVDBid: '{}', IMDbid: '{}', language_series : {}, language_episodes: {}".format(TVDBid, IMDbid, language_series , language_episodes)) if not TVDBid.isdigit(): Log.Info('TVDBid non-digit'); return TheTVDB_dict, IMDbid ### TVDB Series JSON ### Log.Info("--- series ---".ljust(157, '-')) serie_json = Dict(common.LoadFile(filename='series_{}.json'.format(lang), relativeDirectory="TheTVDB/json/"+TVDBid, url=(TVDB_SERIES_URL % TVDBid)+'?'+lang, cache=CACHE_1DAY, headers={'Content-type': 'application/json', 'Accept-Language': lang}), 'data') if serie_json: #serie_json { "id","seriesId", "airsDayOfWeek", "imdbId", "zap2itId", "added", "addedBy", "lastUpdated", "seriesName", "aliases", "banner", "status", # "firstAired", "network", "networkId", "runtime", "genre, "overview", "airsTime", "rating" , "siteRating", "siteRatingCount" } SaveDict( language_series.index(lang) if lang in language_series and not anidb_prefered else len(language_series), TheTVDB_dict, 'language_rank') Log.Info("[ ] language_rank: {}" .format(Dict(TheTVDB_dict, 'language_rank'))) Log.Info("[ ] title: {}" .format(SaveDict( Dict(serie_json, 'seriesName'), TheTVDB_dict, 'title' ))) Log.Info("[ ] original_title: {}" .format(SaveDict( Dict(serie_json, 'seriesName'), TheTVDB_dict, 'original_title' ))) Log.Info("[ ] IMDbid: {}" .format(SaveDict( Dict(serie_json, 'imdbId' or IMDbid), TheTVDB_dict, 'IMDbid' ))) Log.Info("[ ] zap2itId: {}" .format(SaveDict( Dict(serie_json, 'zap2it_id' ), TheTVDB_dict, 'zap2itId' ))) Log.Info("[ ] content_rating: {}" .format(SaveDict( Dict(serie_json, 'rating' ), TheTVDB_dict, 'content_rating' ))) if not anidb_prefered: Log.Info("[ ] summary: {}" .format(SaveDict( Dict(serie_json, 'overview' ).strip(" \n\r"), TheTVDB_dict, 'summary' ))) Log.Info("[ ] originally_available_at: {}".format(SaveDict( Dict(serie_json, 'firstAired'), TheTVDB_dict, 'originally_available_at'))) Log.Info("[ ] genres: {}" .format(SaveDict( sorted(Dict(serie_json, 'genre')), TheTVDB_dict, 'genres' ))) Log.Info("[ ] studio: {}" .format(SaveDict( Dict(serie_json, 'network' ), TheTVDB_dict, 'studio' ))) Log.Info("[ ] rating: {}" .format(SaveDict( Dict(serie_json, 'siteRating'), TheTVDB_dict, 'rating' ))) Log.Info("[ ] status: {}" .format(SaveDict( Dict(serie_json, 'status' ), TheTVDB_dict, 'status' ))) if Dict(serie_json, 'runtime') and Dict(serie_json, 'runtime').isdigit(): Log.Info('[ ] duration: {}' .format(SaveDict(int(Dict(serie_json, 'runtime'))*60*1000, TheTVDB_dict, 'duration' ))) #in ms in plex if Dict(serie_json, 'banner'): SaveDict((os.path.join('TheTVDB', 'banner', Dict(serie_json, 'banner')), 1, None), TheTVDB_dict, 'banners', TVDB_IMG_ROOT+Dict(serie_json, 'banner')) Log.Info('[ ] banner: {}' .format(Dict(TheTVDB_dict, 'banners'))) ### TVDB Series Actors JSON ### Log.Info("--- actors ---".ljust(157, '-')) actor_json = Dict(common.LoadFile(filename='actors_{}.json'.format(lang), relativeDirectory="TheTVDB/json/"+TVDBid, url=TVDB_ACTORS_URL % TVDBid, cache=CACHE_1DAY, headers={'Content-type': 'application/json', 'Accept-Language': lang}), 'data') if actor_json: #JSON format: 'data': [{"seriesId", "name", "image", "lastUpdated", "imageAuthor", "role", "sortOrder", "id", "imageAdded", },...] for role in actor_json or []: try: role_dict = {'role': Dict(role, 'role'), 'name': Dict(role, 'name'), 'photo': TVDB_IMG_ROOT + role['image'] if Dict(role, 'image') else ''} SaveDict([role_dict], TheTVDB_dict, 'roles') Log.Info("[ ] role: {:<50}, name: {:<20}, photo: {}".format(role_dict['role'], role_dict['name'], role_dict['photo'])) except Exception as e: Log.Info(" role: {}, error: '{}'".format(str(role), str(e))) #common.DisplayDict(actor_json, ['role', 'name', 'image']) ### Load pages of episodes ### Log.Info("--- episodes ---".ljust(157, '-')) episodes_json, page = [], 1 while page not in (None, '', 'null'): episodes_json_page = common.LoadFile(filename='episodes_page{}_{}.json'.format(page, lang), relativeDirectory="TheTVDB/json/"+TVDBid, url=TVDB_EPISODES_URL % (TVDBid, page), cache=CACHE_1DAY, headers={'Content-type': 'application/json', 'Accept-Language': lang}) episodes_json.extend(Dict(episodes_json_page, 'data')) #Log.Info('TVDB_EPISODES_URL: {}, links: {}'.format(TVDB_EPISODES_URL % (TVDBid, page), Dict(episodes_json_page, 'links'))) page = Dict(episodes_json_page, 'links', 'next') sorted_episodes_json = {} for episode_json in episodes_json: sorted_episodes_json['s{:02d}e{:03d}'.format(Dict(episode_json, 'airedSeason'), Dict(episode_json, 'airedEpisodeNumber'))] = episode_json sorted_episodes_index_list = sorted(sorted_episodes_json, key=natural_sort_key) #Log.Info('len: {}, sorted_episodes_index_list: {}'.format(len(sorted_episodes_index_list), sorted_episodes_index_list)) ### Build list_abs_eps for tvdb 3/4/5 ### list_abs_eps, list_sp_eps={}, [] if metadata_source in ('tvdb3', 'tvdb4'): for s in media.seasons: for e in media.seasons[s].episodes: if s=='0': list_sp_eps.append(e) else: list_abs_eps[e]=s Log.Info('Present abs eps: {}'.format(list_abs_eps)) ### episode loop ### tvdb_special_missing, summary_missing_special, summary_missing, summary_present, episode_missing, episode_missing_season, episode_missing_season_all = [], [], [], [], [], [], True abs_number, ep_count = 0, 0 for index in sorted_episodes_index_list: # Episode and Absolute number calculation engine, episode translation episode_json = sorted_episodes_json[index] #Log.Info('s{:02d}e{:03d} abs: {:03d} ep: {}'.format(Dict(episode_json, 'airedSeason') or 0, Dict(episode_json, 'airedEpisodeNumber') or 0, Dict(episode_json, 'absoluteNumber') or 0, episode_json)) episode = str(Dict(episode_json, 'airedEpisodeNumber')) season = str(Dict(episode_json, 'airedSeason' )) numbering = "s{}e{}".format(season, episode) # Replace all the individual episodes reported as missing with a single season 'sX' entry if episode=="1": if not episode_missing_season_all: episode_missing.extend(episode_missing_season) elif episode_missing_season: first_entry, last_entry = episode_missing_season[0], episode_missing_season[-1] fm = re.match(r'((?P<abs>\d+) \()?s(?P<s>\d+)e(?P<e>\d+)\)?', first_entry).groupdict() lm = re.match(r'((?P<abs>\d+) \()?s(?P<s>\d+)e(?P<e>\d+)\)?', last_entry ).groupdict() episode_missing.append("s{}e{}-{}".format(fm['s'], fm['e'], lm['e']) if fm['abs'] is None else "{}-{} (s{}e{}-{})".format(fm['abs'], lm['abs'], fm['s'], fm['e'], lm['e'])) episode_missing_season, episode_missing_season_all = [], True # Get the max season number from TVDB API if int(season) > max_season: max_season = int(season) ### ep translation anidbid="" if season!='0': abs_number = abs_number + 1 if anidb_numbering: if season!='0' and Dict(mappingList, 'defaulttvdbseason_a'): season, episode = '1', str(abs_number) else: season, episode, anidbid = anidb_ep(mappingList, season, episode) elif season!='0' and metadata_source=='tvdb3': episode = str(abs_number) elif season!='0' and metadata_source=='tvdb4': ms, usl = Dict(mappingList, 'absolute_map', 'max_season'), Dict(mappingList, 'absolute_map', 'unknown_series_length') season, episode = Dict(mappingList, 'absolute_map', str(abs_number), default=(ms if usl else str(int(ms)+1), None))[0], str(abs_number) elif season!='0' and metadata_source=='tvdb5': episode, abs_number = str(Dict(episode_json, 'absoluteNumber') or abs_number), int(Dict(episode_json, 'absoluteNumber') or abs_number) # Record absolute number mapping for AniDB metadata pull if metadata_source=='tvdb3': SaveDict((str(Dict(episode_json, 'airedSeason')), str(Dict(episode_json, 'airedEpisodeNumber'))), mappingList, 'absolute_map', str(abs_number)) ### Missing summaries logs ### if Dict(episode_json, 'overview'): summary_present.append(numbering) elif season!='0': summary_missing.append(numbering) else: summary_missing_special.append(numbering) ### Check for Missing Episodes ### is_missing = False if not(season =='0' and episode in list_sp_eps) and \ not(metadata_source in ('tvdb3', 'tvdb4') and str(abs_number) in list_abs_eps) and \ not(not movie and season in media.seasons and episode in media.seasons[season].episodes): is_missing = True Log.Info('[ ] {:>7} s{:0>2}e{:0>3} anidbid: {:>7} air_date: {}'.format(numbering, season, episode, anidbid, Dict(episode_json, 'firstAired'))) air_date = Dict(episode_json, 'firstAired') air_date = int(air_date.replace('-','')) if air_date.replace('-','').isdigit() and int(air_date.replace('-','')) > 10000000 else 99999999 if int(time.strftime("%Y%m%d")) <= air_date+1: pass #Log.Info("TVDB - Episode '{}' missing but not aired/missing '{}'".format(numbering, air_date)) elif season=='0': tvdb_special_missing.append(episode) elif metadata_source!='tvdb6': episode_missing_season.append( str(abs_number)+" ("+numbering+")" if metadata_source in ('tvdb3', 'tvdb4') else numbering) ### File present on disk if not is_missing or metadata_source in ["tvdb", "tvdb6"]: # Only pull all if anidb3(tvdb)/anidb4(tvdb6) usage for tvdb ep/season adjustments episode_missing_season_all = False #Log.Info('[?] episode_json: {}'.format(episode_json)) if not is_missing: Log.Info('[X] {:>7} s{:0>2}e{:0>3} anidbid: {:>7} air_date: {} abs_number: {}, title: {}'.format(numbering, season, episode, anidbid, Dict(episode_json, 'firstAired'), abs_number, Dict(episode_json, 'episodeName'))) if not anidb_numbering: SaveDict( abs_number , TheTVDB_dict, 'seasons', season, 'episodes', episode, 'absolute_index' ) SaveDict( Dict(serie_json , 'rating' ), TheTVDB_dict, 'seasons', season, 'episodes', episode, 'content_rating' ) SaveDict( Dict(TheTVDB_dict, 'duration' ), TheTVDB_dict, 'seasons', season, 'episodes', episode, 'duration' ) ep_summary = SaveDict( Dict(episode_json, 'overview').strip(" \n\r"), TheTVDB_dict, 'seasons', season, 'episodes', episode, 'summary' ) Log.Info(' - [ ] summary: {}'.format((ep_summary[:200]).replace("\n", "\\n").replace("\r", "\\r")+'..' if len(ep_summary)> 200 else ep_summary)) SaveDict( Dict(episode_json, 'firstAired'), TheTVDB_dict, 'seasons', season, 'episodes', episode, 'originally_available_at') # Title from serie page if Dict(episode_json, 'episodeName'): rank = language_episodes.index(lang) if lang in language_episodes else len(language_episodes) title = Dict(episode_json, 'episodeName') else: rank, title = len(language_episodes)+1, '' Log.Info(" - [1] language_rank: {:>1}, language: {:>4}, title: {}".format(rank, Dict(episode_json, 'language', 'episodeName'), title)) ### Ep advance information ### ep_count += 1 for lang2 in language_episodes: if lang2 not in (lang, ''): break else: lang2 = 'en' episode_details_json = Dict(common.LoadFile(filename='episode_{}_{}.json'.format(Dict(episode_json, 'id'), lang2), relativeDirectory="TheTVDB/json/"+TVDBid, url=TVDB_EPISODE_DETAILS_URL + str(Dict(episode_json, 'id')), cache=CACHE_1DAY, headers={'Content-type': 'application/json', 'Accept-Language': lang2}), 'data') if episode_details_json: # Std ep info loaded for Library language ten details for 1st language, loading other languages if needed if lang2 in language_episodes and language_episodes.index(lang2)<rank and Dict(episode_details_json, 'language', 'episodeName')==lang2: rank = language_episodes.index(lang2) title = Dict(episode_details_json, 'episodeName') Log.Info(' - [2] language_rank: {:>1}, language: {:>4}, title: {}'.format(rank, lang2, title)) SaveDict( Dict(episode_details_json, 'writers' ), TheTVDB_dict, 'seasons', season, 'episodes', episode, 'writers' ) SaveDict( Dict(episode_details_json, 'directors' ), TheTVDB_dict, 'seasons', season, 'episodes', episode, 'directors' ) SaveDict( Dict(episode_details_json, 'guestStars' ), TheTVDB_dict, 'seasons', season, 'episodes', episode, 'guest_stars') SaveDict( Dict(episode_details_json, 'siteRating' ), TheTVDB_dict, 'seasons', season, 'episodes', episode, 'rating' ) # Episode screenshoT/Thumbnail if Dict(episode_details_json, 'filename'): SaveDict((str("TheTVDB/episodes/"+ os.path.basename(Dict(episode_details_json, 'filename'))), 1, None), TheTVDB_dict, 'seasons', season, 'episodes', episode, 'thumbs', str(TVDB_IMG_ROOT+Dict(episode_details_json, 'filename'))) else: Log.Info('[!] episode_details_json: {}'.format(episode_details_json)) Log.Info(' - [ ] thumb: {}'.format(TVDB_IMG_ROOT+Dict(episode_details_json, 'filename') if Dict(episode_details_json, 'filename') else '')) # for lang_rank, language in enumerate(language_episodes[1:rank-1] if len(language_episodes)>1 and rank>=2 else []): if not language: continue episode_details_json = Dict(common.LoadFile(filename='episode_{}_{}.json'.format(Dict(episode_json, 'id'), language), relativeDirectory="TheTVDB/json/"+TVDBid, url=TVDB_EPISODE_DETAILS_URL + str(Dict(episode_json, 'id')), cache=CACHE_1DAY, headers={'Content-type': 'application/json', 'Accept-Language': lang}), 'data', default={}) if Dict(episode_details_json, 'episodeName') : title = Dict(episode_details_json, 'episodeName') rank = lang_rank Log.Info(' - [3] language_rank: {}, title: {}'.format(rank, title)) break else: Log.Info('no ep title in language: {}'.format(language_episodes[lang_rank])) SaveDict( title, TheTVDB_dict, 'seasons', season, 'episodes', episode, 'title' ) SaveDict( rank , TheTVDB_dict, 'seasons', season, 'episodes', episode, 'language_rank') #Log.Info('[?] numbering: {} => s{:>1}e{:>3} language_rank: {:>1}, title: "{}"'.format(numbering, season, episode, rank, title)) # (last season) Replace all the individual episodes reported as missing with a single season 'sX' entry if not episode_missing_season_all: episode_missing.extend(episode_missing_season) elif episode_missing_season: first_entry, last_entry = episode_missing_season[0], episode_missing_season[-1] fm = re.match(r'((?P<abs>\d+) \()?s(?P<s>\d+)e(?P<e>\d+)\)?', first_entry).groupdict() lm = re.match(r'((?P<abs>\d+) \()?s(?P<s>\d+)e(?P<e>\d+)\)?', last_entry ).groupdict() episode_missing.append("s{}e{}-{}".format(fm['s'], fm['e'], lm['e']) if fm['abs'] is None else "{}-{} (s{}e{}-{})".format(fm['abs'], lm['abs'], fm['s'], fm['e'], lm['e'])) # Set the min/max season to ints & update max value to the next min-1 to handle multi tvdb season anidb entries map_min_values = [int(Dict(mappingList, 'season_map')[x]['min']) for x in Dict(mappingList, 'season_map', default={}) for y in Dict(mappingList, 'season_map')[x] if y=='min'] for entry in Dict(mappingList, 'season_map', default={}): entry_min, entry_max = int(mappingList['season_map'][entry]['min']), int(mappingList['season_map'][entry]['max']) while entry_min!=0 and entry_max+1 not in map_min_values + [max_season+1]: entry_max += 1 mappingList['season_map'][entry] = {'min': entry_min, 'max': entry_max} SaveDict(max_season, mappingList, 'season_map', 'max_season') ### Logging ### if not movie: if summary_missing: error_log['Missing Episode Summaries'].append("TVDBid: %s | Title: '%s' | Missing Episode Summaries: %s" % (common.WEB_LINK % (common.TVDB_SERIE_URL + TVDBid, TVDBid), Dict(TheTVDB_dict, 'title'), str(summary_missing ))) if summary_missing_special: error_log['Missing Special Summaries'].append("TVDBid: %s | Title: '%s' | Missing Special Summaries: %s" % (common.WEB_LINK % (common.TVDB_SERIE_URL + TVDBid, TVDBid), Dict(TheTVDB_dict, 'title'), str(summary_missing_special))) if metadata_source.startswith("tvdb") or metadata_source.startswith("anidb") and not movie and max(map(int, media.seasons.keys()))>1: if episode_missing: error_log['Missing Episodes' ].append("TVDBid: %s | Title: '%s' | Missing Episodes: %s" % (common.WEB_LINK % (common.TVDB_SERIE_URL + TVDBid, TVDBid), Dict(TheTVDB_dict, 'title'), str(episode_missing ))) if tvdb_special_missing: error_log['Missing Specials' ].append("TVDBid: %s | Title: '%s' | Missing Specials: %s" % (common.WEB_LINK % (common.TVDB_SERIE_URL + TVDBid, TVDBid), Dict(TheTVDB_dict, 'title'), str(tvdb_special_missing ))) #Log.Debug("Episodes without Summary: " + str(sorted(summary_missing, key=natural_sort_key))) ### Picture types JSON download ### Log.Info("--- images ---".ljust(157, '-')) language_posters = [language.strip() for language in Prefs['PosterLanguagePriority'].split(',')] priority_posters = [ source.strip() for source in Prefs['posters' ].split(',')] Log.Info('language_posters: {}'.format(language_posters)) for language in language_posters: try: bannerTypes = Dict(common.LoadFile(filename='images_{}.json'.format(language), relativeDirectory="TheTVDB/json/"+TVDBid, url=(TVDB_SERIES_IMG_INFO_URL % TVDBid), cache=0, headers={'Content-type': 'application/json', 'Accept-Language': language}), 'data', default={}) except: Log.Info("Invalid image JSON from url: " + TVDB_SERIES_IMG_INFO_URL % TVDBid) else: #JSON format = {"fanart", "poster", "season", "seasonwide", "series"} metanames = {'fanart': "art", 'poster': "posters", 'series': "banners", 'season': "seasons", 'seasonwide': 'seasonwide'}# count_valid = {key: 0 for key in metanames} anidb_offset = sorted((Dict(mappingList, 'poster_id_array', TVDBid) or {}).keys()).index(AniDBid) if AniDBid and AniDBid in Dict(mappingList, 'poster_id_array', TVDBid) else 0 language_priority = [item.strip() for item in Prefs['EpisodeLanguagePriority'].split(',')] Log.Info("bannerTypes: {}, anidb_offset: {}, AniDBid: {}, anidb_array: {}".format(bannerTypes, anidb_offset, AniDBid, str((Dict(mappingList, 'poster_id_array', TVDBid) or {}).keys()))) #Loop per banner type ("fanart", "poster", "season", "series") skip 'seasonwide' - Load bannerType images list JSON for bannerType in bannerTypes or []: if bannerTypes[bannerType]==0 or bannerType in ('seasonwide', 'series') or movie and not bannerType in ('fanart', 'poster'): continue #Loop if no images #if anidb_numbering and Dict(mappingList, 'defaulttvdbseason') != '1' and bannerType=='poster': continue #skip if anidb numbered serie mapping to season 0 or 2+ Log.Info(("--- images.%s ---" % bannerType).ljust(157, '-')) try: images = Dict( common.LoadFile(filename='images_{}_{}.json'.format(bannerType, language), relativeDirectory="TheTVDB/json/"+TVDBid, url=TVDB_SERIES_IMG_QUERY_URL.format(TVDBid, bannerType), cache=CACHE_1DAY, headers={'Accept-Language': language}), 'data', default={}) except: images = {}; Log.Info("Bad image type query data for TVDB id: %s (bannerType: %s)" % (TVDBid, bannerType)) else: images = sorted(images, key = lambda x: Dict(x, "ratingsInfo", "average", default=0), reverse=True) for image in images: #JSON format = {"data": [{"id", "keyType", "subKey"(season/graphical/text), "fileName", "resolution", "ratingsInfo": {"average", "count"}, "thumbnail"}]} #rank rank = 1 if bannerType=='poster' and anidb_offset == divmod(count_valid['poster'], Dict(bannerTypes, 'poster', default=0))[1] else count_valid[bannerType]+2 if language in language_posters: rank = (rank//30)*30*language_posters.index(language)+rank%30 if 'TheTVDB' in priority_posters: rank = rank+ 6*priority_posters.index('TheTVDB') rank = rank + language_posters.index(language)*20 if AniDB_movie: rank = rank+Dict(bannerTypes, 'poster', default=0) if rank+Dict(bannerTypes, 'poster', default=0)<99 else 99 ### Adding picture ### thumbnail = TVDB_IMG_ROOT + image['thumbnail'] if Dict(image, 'thumbnail') else None Log.Info("[!] bannerType: {:>7} subKey: {:>9} rank: {:>3} filename: {} thumbnail: {} resolution: {} average: {} count: {}".format( metanames[bannerType], Dict(image, 'subKey'), rank, TVDB_IMG_ROOT + Dict(image, 'fileName'), TVDB_IMG_ROOT + Dict(image, 'thumbnail'), Dict(image, 'resolution'), Dict(image, 'ratingsInfo','average'), Dict(image, 'ratingsInfo', 'average', 'count') )) if bannerType=='season': #tvdb season posters or anidb specials and defaulttvdb season ## season 0 et empty+ season ==defaulttvdbseason(a=1) if not anidb_numbering: SaveDict(('TheTVDB/'+image['fileName'], rank, thumbnail), TheTVDB_dict, 'seasons', str(image['subKey']), 'posters', TVDB_IMG_ROOT + image['fileName']) else: if str(image['subKey']) in [Dict(mappingList, 'defaulttvdbseason')]: SaveDict(('TheTVDB/'+image['fileName'], rank, thumbnail), TheTVDB_dict, 'posters', TVDB_IMG_ROOT + image['fileName']) if str(image['subKey']) in ['0', Dict(mappingList, 'defaulttvdbseason')]: SaveDict(('TheTVDB/'+image['fileName'], 1 if rank==3 else 3 if rank==1 else rank, thumbnail), TheTVDB_dict, 'seasons', '0' if str(image['subKey'])=='0' else '1', 'posters', TVDB_IMG_ROOT + image['fileName']) #if anidb_numbering else str(image['subKey']) else: new_rank = rank + 10 if anidb_numbering and Dict(mappingList, 'defaulttvdbseason') != '1' else rank SaveDict(('TheTVDB/'+image['fileName'], new_rank, thumbnail), TheTVDB_dict, metanames[bannerType], TVDB_IMG_ROOT + image['fileName']) #use art + posters tvdb #if bannerType == 'season': # if anidb_numbering and Dict(mappingList, 'defaulttvdbseason')==str(image['subKey']): # SaveDict(('TheTVDB/'+image['fileName'], 1 if rank==3 else 3 if rank==1 else rank, thumbnail), TheTVDB_dict, 'seasons', '0' if str(image['subKey'])=='0' else '1', 'posters', TVDB_IMG_ROOT + image['fileName']) # elif not anidb_numbering: # season = str(int(image['subKey'])+(0 if Dict(mappingList, 'defaulttvdbseason')=="0" or not Dict(mappingList, 'defaulttvdbseason').isdigit() else int(Dict(mappingList, 'defaulttvdbseason'))-1)) # SaveDict( ('TheTVDB/'+image['fileName'], rank, thumbnail), TheTVDB_dict, 'seasons', season, 'posters', TVDB_IMG_ROOT + image['fileName']) #if bannerType != 'season': # SaveDict(('TheTVDB/'+image['fileName'], rank, thumbnail), TheTVDB_dict, metanames[bannerType], TVDB_IMG_ROOT + image['fileName']) count_valid[bannerType] = count_valid[bannerType] + 1 #Otherwise with += SyntaxError: Line 142: Augmented assignment of object items and slices is not allowed #Log.Info("Posters : {}/{}, Season posters: {}/{}, Art: {}/{}".format(count_valid['poster'], Dict(bannerTypes, 'poster'), count_valid['season'], Dict(bannerTypes, 'season') or 0, count_valid['fanart'], Dict(bannerTypes, 'fanart'))) if not Dict(bannerTypes, 'poster'): error_log['TVDB posters missing' ].append("TVDBid: %s | Title: '%s'" % (common.WEB_LINK % (common.TVDB_SERIE_URL + TVDBid, TVDBid), Dict(TheTVDB_dict, 'title'))) if not Dict(bannerTypes, 'season'): error_log['TVDB season posters missing'].append("TVDBid: %s | Title: '%s'" % (common.WEB_LINK % (common.TVDB_SERIE_URL + TVDBid, TVDBid), Dict(TheTVDB_dict, 'title'))) Log.Info("--- final summary info ---".ljust(157, '-')) Log.Info("url: '{}', IMDbid: {}, Present episodes: {}, Missing: {}".format(TVDB_SERIES_URL % TVDBid, IMDbid, ep_count, sorted(episode_missing, key=natural_sort_key))) Log.Info("--- return ---".ljust(157, '-')) Log.Info("absolute_map: {}".format(DictString(Dict(mappingList, 'absolute_map', default={}), 0))) Log.Info("season_map: {}".format(DictString(Dict(mappingList, 'season_map', default={}), 0))) Log.Info("TheTVDB_dict: {}".format(DictString(TheTVDB_dict, 4))) return TheTVDB_dict, IMDbid
def GetMetadata(movie=False, TVDBid="", tmdbid="", imdbid="", season=0, num=100): #Fetch from fanart.tv - Posters Seasons Fanarts Banner Log.Info("=== FanartTv.GetMetadata() ===".ljust(157, '=')) FanartTV_dict = {} Log.Info("movie:'{movie}', TVDBid: '{TVDBid}', tmdbid: '{tmdbid}', imdbid:'{imdbid}', season: '{season}', num: '{num}'".format(movie=movie, TVDBid=TVDBid, tmdbid=tmdbid, imdbid=imdbid, season=season, num=num)) if "," in imdbid: (GetMetadata(movie, "", "", imdbid_unique, season, num) for imdbid_unique in (tmdbid or imdbid).split(",")); return #recusive call for each imdbid to reduce complexity if "," in tmdbid: (GetMetadata(movie, "", tmdbid_unique, "", season, num) for tmdbid_unique in tmdbid.split(",")); return #recusive call for each tmdbid to reduce complexity if not movie and TVDBid.isdigit(): id, relativeDirectory, url = TVDBid, "FanartTV/tv/" +TVDBid, FTV_API_TV_URL.format(id=TVDBid) elif movie and (imdbid or tmdbid): id, relativeDirectory, url = imdbid or tmdbid, "FanartTV/movie/"+imdbid or tmdbid, FTV_API_MOVIES_URL.format(id=imdbid or tmdbid) else: return FanartTV_dict if TVDBid or tmdbid or imdbid: Log.Info(("--- %s.images ---" % id).ljust(157, '-')) json = common.LoadFile(filename=id+".json", relativeDirectory=relativeDirectory, url=url, cache=CACHE_1WEEK) #Movies if json and (imdbid or tmdbid): for item in Dict(json, 'movieposter' ) or []: Log.Info("[ ] poster: {}".format(SaveDict((relativeDirectory+"{id}/movieposter/{filename}.jpg".format( id=id, filename=item['id']), num, None), FanartTV_dict, 'posters', item['url']))) for item in Dict(json, 'moviebackground') or []: Log.Info("[ ] art: {}" .format(SaveDict((relativeDirectory+"{id}/moviebackground/{filename}.jpg".format(id=id, filename=item['id']), num, None), FanartTV_dict, 'art', item['url']))) #Series if json and TVDBid.isdigit(): for item in Dict(json, 'tvposter' ) or []: Log.Info("[ ] poster: {}" .format(SaveDict((relativeDirectory+"{id}/tvposter/{filename}.jpg".format( id=id, filename=item['id']), num, None), FanartTV_dict, 'posters', item['url']))) for item in Dict(json, 'showbackground' ) or []: Log.Info("[ ] art: {}" .format(SaveDict((relativeDirectory+"{id}/showbackground/{filename}.jpg".format( id=id, filename=item['id']), num, None), FanartTV_dict, 'art', item['url']))) for item in Dict(json, 'tvbanner' ) or []: Log.Info("[ ] banner: {}" .format(SaveDict((relativeDirectory+"{id}/tvbanner/{filename}.jpg".format( id=id, filename=item['id']), num, None), FanartTV_dict, 'banners', item['url']))) for item in Dict(json, 'seasonposter' ) or []: Log.Info("[ ] season poster: {}".format(SaveDict((relativeDirectory+"{id}/seasonposter/{filename}.jpg".format( id=id, filename=item['id']), num, None), FanartTV_dict, 'seasons', item['season'], 'posters', item['url']))) Log.Info("--- return ---".ljust(157, '-')) Log.Info("FanartTV_dict: {}".format(DictString(FanartTV_dict, 4))) return FanartTV_dict
def GetMetadata(movie, MALid): Log.Info("=== MyAnimeList.GetMetadata() ===".ljust(157, '=')) MAL_HTTP_API_URL = "http://fribbtastic-api.net/fribbtastic-api/services/anime?id=" MAL_PREFIX = "https://myanimelist.cdn-dena.com" # Some links in the XML will come from TheTVDB, not adding those.... MyAnimeList_dict = {} Log.Info("MALid: '%s'" % MALid) if not MALid or not MALid.isdigit(): return MyAnimeList_dict Log.Info("--- series ---".ljust(157, '-')) xml = common.LoadFile(filename=MALid + ".xml", relativeDirectory=os.path.join('MyAnimeList', 'xml'), url=MAL_HTTP_API_URL + MALid, cache=CACHE_1DAY * 7) if isinstance(xml, str): Log.Error('Invalid str returned: "{}"'.format(xml)) elif xml: Log.Info("[ ] title: {}".format( SaveDict(GetXml(xml, 'title'), MyAnimeList_dict, 'title'))) Log.Info("[ ] summary: {}".format( SaveDict(GetXml(xml, 'synopsis'), MyAnimeList_dict, 'summary'))) Log.Info("[ ] score: {}".format( SaveDict(GetXml(xml, 'rating'), MyAnimeList_dict, 'score'))) #Log.Info("[ ] rating: {}" .format(SaveDict( GetXml(xml, 'content_rating').split(" ")[0], MyAnimeList_dict, 'rating' ))) Log.Info("[ ] originally_available_at: {}".format( SaveDict(GetXml(xml, 'firstAired'), MyAnimeList_dict, 'originally_available_at'))) #for item in xml.xpath('//anime/genres/genre' or []): SaveDict([item.text], MyAnimeList_dict, 'genres') if GetXml(xml, '//anime/genres/genre'): Log.Info("[ ] genres: {}".format( SaveDict( sorted([ item.text for item in xml.xpath('//anime/genres/genre') ]), MyAnimeList_dict, 'genres'))) if GetXml(xml, 'status') == 'Currently Airing': Log.Info("[ ] status: {}".format( SaveDict("Continuing", MyAnimeList_dict, 'status'))) if GetXml(xml, 'status') == 'Finished Airing': Log.Info("[ ] status: {}".format( SaveDict("Ended", MyAnimeList_dict, 'status'))) Log.Info("--- episodes ---".ljust(157, '-')) for item in xml.xpath('//anime/episodes/episode') or []: ep_number, ep_title, ep_air = GetXml( item, 'episodeNumber'), GetXml(xml, 'engTitle'), GetXml(xml, 'aired') Log.Info('[ ] s1e{:>3} air_date: {}, title: "{}"'.format( ep_number, ep_title, ep_air)) SaveDict(ep_title, MyAnimeList_dict, 'seasons', "1", 'episodes', ep_number, 'title') SaveDict(ep_air, MyAnimeList_dict, 'seasons', "1", 'episodes', ep_number, 'originally_available_at') Log.Info("--- images ---".ljust(157, '-')) for item in xml.xpath('//anime/covers/cover'): Log.Info("[ ] poster: {}".format( SaveDict( ("MyAnimeList/" + "/".join(item.text.split('/')[3:]), 50, None) if item.text.startswith(MAL_PREFIX) else "", MyAnimeList_dict, 'posters', item.text))) for item in xml.xpath('//anime/backgrounds/background'): Log.Info("[ ] art: {}".format( SaveDict( ("MyAnimeList/" + "/".join(item.text.split('/')[3:]), 50, None) if item.text.startswith(MAL_PREFIX) else "", MyAnimeList_dict, 'art', item.text))) for item in xml.xpath('//anime/banners/banner'): Log.Info("[ ] banner: {}".format( SaveDict( ("MyAnimeList/" + "/".join(item.text.split('/')[3:]), 50, None) if item.text.startswith(MAL_PREFIX) else "", MyAnimeList_dict, 'banners', item.text))) Log.Info("--- return ---".ljust(157, '-')) Log.Info("MyAnimeList_dict: {}".format(DictString(MyAnimeList_dict, 4))) return MyAnimeList_dict
def AdjustMapping(source, mappingList, dict_AniDB, dict_TheTVDB): """ EX: season_map: {'max_season': 2, '12560': {'max': 1, 'min': 1}, '13950': {'max': 0, 'min': 0}} relations_map: {'12560': {'Sequel': ['13950']}, '13950': {'Prequel': ['12560']}} TVDB Before: {'s1': {'12560': '0'}, 's0': {'13950': '0'}, '13950': (0, '')} 's0e5': ('1', '4', '9453') 's1': {'12560': '0'} '13950': (0, '') """ Log.Info("=== anidb34.AdjustMapping() ===".ljust(157, '=')) is_modified = False adjustments = {} tvdb6_seasons = {1: 1} is_banned = Dict(dict_AniDB, 'Banned', default=False) TVDB = Dict(mappingList, 'TVDB', default={}) season_map = Dict(mappingList, 'season_map', default={}) relations_map = Dict(mappingList, 'relations_map', default={}) if not (Dict(mappingList, 'possible_anidb3') or source=='tvdb6'): Log.Info("Neither a possible 'anidb3/tvdb' enrty nor 'anidb4/tvdb6' entry"); return is_modified Log.Info("adjusting mapping for 'anidb3/tvdb' & 'anidb4/tvdb6' usage") #Log.Info("dict_TheTVDB: {}".format(dict_TheTVDB)) Log.Info("season_map: {}".format(DictString(season_map, 0))) Log.Info("relations_map: {}".format(DictString(relations_map, 1))) try: Log.Info("--- tvdb mapping adjustments ---".ljust(157, '-')) Log.Info("TVDB Before: {}".format(DictString(TVDB, 0))) for id in sorted(season_map, key=common.natural_sort_key): new_season, new_episode = '', '' if id == 'max_season': continue #### Note: Below must match scanner (variable names are different but logic matches) #### Log.Info("Checking AniDBid: %s" % id) def get_prequel_info(prequel_id): Log.Info("-- get_prequel_info(prequel_id): %s, season min: %s, season max: %s" % (prequel_id, season_map[prequel_id]['min'], season_map[prequel_id]['max'])) if source=="tvdb": if season_map[prequel_id]['min'] == 0 and 'Prequel' in relations_map[prequel_id] and relations_map[prequel_id]['Prequel'][0] in season_map: a, b = get_prequel_info(relations_map[prequel_id]['Prequel'][0]) # Recurively go down the tree following prequels if not str(a).isdigit(): return ('', '') return (a, b+100) if a < season_map['max_season'] else (a+1, 0) # If the prequel is < max season, add 100 to the episode number offset: Else, add it into the next new season at episode 0 if season_map[prequel_id]['min'] == 0: return ('', '') # Root prequel is a special so leave mapping alone as special elif season_map[prequel_id]['max'] < season_map['max_season']: return (season_map[prequel_id]['max'], 100) # Root prequel season is < max season so add to the end of the Prequel season else: return (season_map['max_season']+1, 0) # Root prequel season is >= max season so add to the season after max if source=="tvdb6": if season_map[prequel_id]['min'] != 1 and 'Prequel' in relations_map[prequel_id] and relations_map[prequel_id]['Prequel'][0] in season_map: a, b = get_prequel_info(relations_map[prequel_id]['Prequel'][0]) # Recurively go down the tree following prequels #Log.Info("%s+%s+%s-%s" % (a,1,season_map[prequel_id]['max'],season_map[prequel_id]['min'])) return (a+1+season_map[prequel_id]['max']-season_map[prequel_id]['min'], 0) if str(a).isdigit() else ('', '') # Add 1 to the season number and start at episode 0 return (2, 0) if season_map[prequel_id]['min'] == 1 else ('', '') # Root prequel is season 1 so start counting up. Else was a sequel of specials only so leave mapping alone if source=="tvdb": if season_map[id]['min'] == 0 and 'Prequel' in relations_map[id] and relations_map[id]['Prequel'][0] in season_map: new_season, new_episode = get_prequel_info(relations_map[id]['Prequel'][0]) # Recurively go down the tree following prequels to a TVDB season non-0 AniDB prequel if source=="tvdb6": if 'Prequel' in relations_map[id] and relations_map[id]['Prequel'][0] in season_map: new_season, new_episode = get_prequel_info(relations_map[id]['Prequel'][0]) # Recurively go down the tree following prequels to the TVDB season 1 AniDB prequel if str(new_season).isdigit(): # A new season & eppisode offset has been assigned # As anidb4/tvdb6 does full season adjustments, we need to remove and existing season mapping is_modified = True removed = {} for key in TVDB.keys(): if isinstance(TVDB[key], dict) and id in TVDB[key]: Log.Info("-- Deleted: %s: {'%s': '%s'}" % (key, id, TVDB[key][id])) removed[key] = {id: TVDB[key][id]} del TVDB[key][id] # Delete season entries for its old anidb non-s0 season entries | 's4': {'11350': '0'} if isinstance(TVDB[key], tuple) and TVDB[key][0] == '1' and TVDB[key][2] == id: Log.Info("-- Deleted: {}: {}".format(key, TVDB[key])) removed[key] = TVDB[key] del TVDB[key] # Delete episode entries for its old anidb s1 entries | 's0e5': ('1', '4', '9453') SaveDict(str(new_episode), TVDB, 's'+str(new_season), id) Log.Info("-- Added : {}: {}".format('s'+str(new_season), {id: str(new_episode)})) adjustments['s'+str(new_season)+'e'+str(new_episode)] = {'deleted': removed, 'added': [str(new_season), str(new_episode)]} tvdb6_seasons[new_season] = season_map[id]['min'] # tvdb6_seasons[New season] = [Old season] Log.Info("TVDB After : {}".format(DictString(Dict(mappingList, 'TVDB'), 0))) # Push back the 'dict_TheTVDB' season munbers if tvdb6 for the new inserted season if source=="tvdb6": Log.Info("--- tvdb meta season adjustments ---".ljust(157, '-')) top_season, season, adjustment, new_seasons = max(map(int, dict_TheTVDB['seasons'].keys())), 1, 0, {} Log.Info("dict_TheTVDB Seasons Before : {}".format(sorted(dict_TheTVDB['seasons'].keys(), key=int))) Log.Info("tvdb6_seasons : {}".format(tvdb6_seasons)) if "0" in dict_TheTVDB['seasons']: new_seasons["0"] = dict_TheTVDB['seasons'].pop("0") while season <= top_season: if Dict(tvdb6_seasons, season + adjustment) == 0: Log.Info("-- New TVDB season '{}'".format(season + adjustment)) adjustment += 1 else: Log.Info("-- Adjusting season '{}' -> '{}'".format(season, season + adjustment)) if str(season) in dict_TheTVDB['seasons']: new_seasons[str(season + adjustment)] = dict_TheTVDB['seasons'].pop(str(season)) season += 1 SaveDict(new_seasons, dict_TheTVDB, 'seasons') Log.Info("dict_TheTVDB Seasons After : {}".format(sorted(dict_TheTVDB['seasons'].keys(), key=int))) # Copy in the 'dict_TheTVDB' deleted episode meta into its new added location Log.Info("--- tvdb meta episode adjustments ---".ljust(157, '-')) Log.Info("adjustments: {}".format(DictString(adjustments, 2))) for entry in sorted(adjustments, key=common.natural_sort_key): # EX: {'s6e0': {'added': ['6', '0'], 'deleted': {'s0e16': ('1', '1', '12909'), 's-1': {'12909': '0'}}}} added_season, added_offset = adjustments[entry]['added'] # 'added': ['6', '0'] Log.Info("added_season: '{}', added_offset: '{}'".format(added_season, added_offset)) for deleted in sorted(adjustments[entry]['deleted'], key=common.natural_sort_key): Log.Info("-- deleted: '{}': {}".format(deleted, adjustments[entry]['deleted'][deleted])) if isinstance(adjustments[entry]['deleted'][deleted], dict): deleted_season = deleted[1:] # {-->'s0'<--: {'6463': '0'}} deleted_offset = adjustments[entry]['deleted'][deleted].values()[0] # {'s0': {'6463': -->'0'<--}} if deleted=='s-1': Log.Info("---- {:<9}: Dead season".format("'%s'" % deleted)) continue # EX: {'s-1': {'12909': '0'}} if deleted!='s0' and added_offset=='0' and deleted_offset=='0': Log.Info("---- {:<9}: Whole season (s1+) was adjusted in previous section".format("'%s'" % deleted)) continue # EX: {'s3e0': 'added': ['3', '0'], 'deleted': {'s2': {'7680': '0'}}} == Adjusting season '2' -> '3' # EX: {'s2e0': 'added': ['2', '0' ], 'deleted': {'s0': {'6463': '0'}}} # EX: {'s1e100': 'added': ['1', '100'], 'deleted': {'s0': {'982': '1'}}} interation = 1 Log.Info("---- deleted_season: '{}', deleted_offset: '{}'".format(deleted_season, deleted_offset)) while Dict(dict_TheTVDB, 'seasons', deleted_season, 'episodes', str(int(deleted_offset) + interation)): a, b, x = deleted_season, str(int(deleted_offset) + interation), str(int(added_offset) + interation) SaveDict(Dict(dict_TheTVDB, 'seasons', a, 'episodes', b), dict_TheTVDB, 'seasons', added_season, 'episodes', x) Log.Info("---- {:<9}: dict_TheTVDB['seasons']['{}']['episodes']['{}'] => dict_TheTVDB['seasons']['{}']['episodes']['{}']".format("'%s'" % deleted, a, b, added_season, x)) interation += 1 if isinstance(adjustments[entry]['deleted'][deleted], tuple): a, b = list(filter(None, re.split(r"[se]", deleted))) # 's0e16' --> ['0', '16'] x = str(int(adjustments[entry]['deleted'][deleted][1]) + int(added_offset)) # ('1', -->'1'<--, '12909') Log.Info("---- {:<9}: dict_TheTVDB['seasons']['{}']['episodes']['{}'] => dict_TheTVDB['seasons']['{}']['episodes']['{}']".format("'%s'" % deleted, a, b, added_season, x)) SaveDict(Dict(dict_TheTVDB, 'seasons', a, 'episodes', b), dict_TheTVDB, 'seasons', added_season, 'episodes', x) except Exception as e: if is_banned: Log.Info("Expected exception hit as you were banned from AniDB so you have incomplete data to proceed") else: Log.Error("Unexpected exception hit") Log.Info('Exception: "{}"'.format(e)) Log.Info("If a key error, look at the 'season_map'/'relations_map' info to see why it is missing") if source=="tvdb": Log.Info("Source is 'tvdb' so metadata will be loaded but it will not be complete for any 'anidb3' end of season additions") if source=="tvdb6": Log.Info("Source is 'tvdb6' so removing AniDB & TVDB metadata from memory to prevent incorrect data from being loaded"); dict_AniDB.clear(); dict_TheTVDB.clear() is_modified = False Log.Info("--- return ---".ljust(157, '-')) Log.Info("is_modified: {}".format(is_modified)) return is_modified
def GetMetadata(movie, IMDbid): # return 200 but not downloaded correctly - IMDB has a single poster, downloading through OMDB xml, prefered by mapping file Log.Info("=== OMDb.GetMetadata() ===".ljust(157, '=')) url = OMDB_HTTP_API_URL.format(api_key=Prefs['OMDbApiKey']) #' OMDb_dict = {} if Prefs['OMDbApiKey'] in ('None', '', 'N/A'): Log.Info("No api key found - Prefs['OMDbApiKey']: '{}'".format(Prefs['OMDbApiKey'])); return OMDb_dict Log.Info("IMDbid: '%s'" % IMDbid) for imdbid_single in IMDbid.split(",") if IMDbid.startswith("tt") else []: Log.Info(("--- %s.series ---" % imdbid_single).ljust(157, '-')) json = common.LoadFile(filename=imdbid_single+".json", relativeDirectory=os.path.join('OMDb', 'json'), url=url + imdbid_single) if json: Log.Info("[ ] title: {}" .format(SaveDict( Dict(json,'title') , OMDb_dict, 'title' ))) Log.Info("[ ] summary: {}" .format(SaveDict( Dict(json,'Plot') , OMDb_dict, 'summary' ))) Log.Info("[ ] originally_available_at: {}".format(SaveDict( Dict(json,'Released') , OMDb_dict, 'originally_available_at'))) Log.Info("[ ] countries: {}" .format(SaveDict( Dict(json,'Country') , OMDb_dict, 'countries' ))) Log.Info("[ ] directors: {}" .format(SaveDict( Dict(json,'Director') , OMDb_dict, 'directors' ))) Log.Info("[ ] genres: {}" .format(SaveDict( sorted([x.strip() for x in Dict(json,'Genre').split(',')]), OMDb_dict, 'genres'))) Log.Info("[ ] writers: {}" .format(SaveDict( Dict(json,'Writer') , OMDb_dict, 'writers' ))) SaveDict( Dict(json,'imdbRating'), OMDb_dict, 'rating') if Dict(json,'Metascore').isdigit() and not Dict(OMDb_dict,'rating'): SaveDict( float(json['Metascore'])/10, OMDb_dict, 'rating') Log.Info("[ ] rating: {}".format(Dict(OMDb_dict,'rating'))) if SaveDict( Dict(json,'Rated'), OMDb_dict, 'content_rating') in common.Movie_to_Serie_US_rating and not movie and Dict(json,'Type')=="movie": Log.Info("[ ] content_rating: {}".format(SaveDict(common.Movie_to_Serie_US_rating[json['Rated']], OMDb_dict, 'content_rating'))) if Dict(json,'Poster'): Log.Info("[ ] poster: {}".format(json['Poster'])); SaveDict((os.path.join('OMDb', 'poster', imdbid_single+'.jpg'), common.poster_rank('OMDb', 'posters'), None), OMDb_dict, 'posters', json['Poster']) try: Log.Info("[ ] duration: {}".format(SaveDict( int(Dict(json,'Runtime').replace(' min','')) * 60 * 1000, OMDb_dict, 'duration'))) # Plex save duration in millisecs except: pass Log.Info("--- return ---".ljust(157, '-')) Log.Info("OMDb_dict: {}".format(DictString(OMDb_dict, 4))) return OMDb_dict
def GetMetadata(media, movie, source, TVDBid, mappingList, num=0): """ [tvdb4.mapping.xml] Attempt to get the ASS's episode mapping data [tvdb4.posters.xml] Attempt to get the ASS's image data """ Log.Info('=== tvdb4.GetMetadata() ==='.ljust(157, '=')) TVDB4_dict, TVDB4_mapping, TVDB4_xml = {}, None, None if movie or not source == "tvdb4": Log.Info("not tvdb4 mode") return TVDB4_dict Log.Info("tvdb4 mode") def find_tvdb4_file(file_to_find): try: folder = common.GetMediaDir(media, movie) while folder and folder[-1] not in ["/", "\\"]: filename = os.path.join(folder, file_to_find) if os.path.exists(filename): file = Core.storage.load(os.path.realpath(filename)) try: return XML.ElementFromString(file) except: return file folder = os.path.dirname(folder) else: Log.Info("No '{}' file detected locally".format(file_to_find)) except Exception as e: Log.Error( "Issues in finding setup info as directories have most likely changed post scan into Plex, Exception: '%s'" % e) return "" Log.Info("--- tvdb4.mapping.xml ---".ljust(157, '-')) TVDB4_mapping = find_tvdb4_file("tvdb4.mapping") if TVDB4_mapping: Log.Debug("'tvdb4.mapping' file detected locally") else: TVDB4_mapping = TVDB4_mapping or common.LoadFile( filename=os.path.basename(TVDB4_MAPPING_URL), url=TVDB4_MAPPING_URL, cache=CACHE_1DAY * 6) # AniDB title database loaded once every 2 weeks entry = "" if isinstance(TVDB4_mapping, str): entry = TVDB4_mapping else: entry = common.GetXml(TVDB4_mapping, "/tvdb4entries/anime[@tvdbid='%s']" % TVDBid) if not entry: Log.Error("TVDBid '%s' is not found in mapping file" % TVDBid) if entry: for line in filter(None, entry.strip().splitlines()): season = line.strip().split("|") for absolute_episode in range(int(season[1]), int(season[2]) + 1): SaveDict((str(int(season[0])), str(absolute_episode)), mappingList, 'absolute_map', str(absolute_episode)) SaveDict(True if "(unknown length)" in season[3] else False, mappingList, 'absolute_map', 'unknown_series_length') SaveDict(str(int(season[0])), mappingList, 'absolute_map', 'max_season') Log.Info("--- tvdb4.posters.xml ---".ljust(157, '-')) TVDB4_xml = find_tvdb4_file(os.path.basename(TVDB4_POSTERS_URL)) if TVDB4_xml: Log.Debug("'tvdb4.posters.xml' file detected locally") else: TVDB4_xml = TVDB4_xml or common.LoadFile( filename=os.path.basename(TVDB4_POSTERS_URL), url=TVDB4_POSTERS_URL, cache=CACHE_1DAY * 6) # AniDB title database loaded once every 2 weeks if TVDB4_xml: seasonposternum = 0 entry = common.GetXml(TVDB4_xml, "/tvdb4entries/posters[@tvdbid='%s']" % TVDBid) if not entry: Log.Error("TVDBid '%s' is not found in posters file" % TVDBid) for line in filter(None, entry.strip().splitlines()): season, url = line.strip().split("|", 1) season = season.lstrip("0") if season.lstrip("0") else "0" seasonposternum += 1 SaveDict(("TheTVDB/seasons/%s-%s-%s" % (TVDBid, season, os.path.basename(url)), 1, None), TVDB4_dict, 'seasons', season, 'posters', url) Log.Info("--- return ---".ljust(157, '-')) Log.Info("absolute_map: {}".format( DictString(Dict(mappingList, 'absolute_map', default={}), 0))) Log.Info("TVDB4_dict: {}".format(DictString(TVDB4_dict, 4))) return TVDB4_dict
def GetMetadata(media, movie, error_log, lang, metadata_source, AniDBid, TVDBid, IMDbid, mappingList, AniDB_movie): ''' TVDB - Load serie JSON ''' Log.Info("".ljust(157, '-')) if not TVDBid.isdigit(): Log.Info('TheTVDB.GetMetadata() - TVDBid empty') return {}, IMDbid TheTVDB_dict = {} anidb_numbering = metadata_source == "anidb" and ( movie or max(map(int, media.seasons.keys())) <= 1) anidb_prefered = anidb_numbering and Dict( mappingList, 'defaulttvdbseason') not in ('a', '1') language_series = [ language.strip() if language.strip() not in ('x-jat', 'zh-Hans', 'zh-Hant', 'zh-x-yue', 'zh-x-cmn', 'zh-x-nan') else '' for language in Prefs['SerieLanguagePriority'].split(',') ] language_episodes = [ language.strip() if language.strip() not in ('x-jat', 'zh-Hans', 'zh-Hant', 'zh-x-yue', 'zh-x-cmn', 'zh-x-nan') else '' for language in Prefs['EpisodeLanguagePriority'].split(',') ] Log.Info( "TheTVDB.GetMetadata() - TVDBid: '{}', IMDbid: '{}', language_series : {}, language_episodes: {}" .format(TVDBid, IMDbid, language_series, language_episodes)) ### TVDB Series JSON ### serie_json = Dict( common.LoadFile(filename='series_{}.json'.format(lang), relativeDirectory="TheTVDB/json/" + TVDBid, url=(TVDB_SERIES_URL % TVDBid) + '?' + lang, cache=CACHE_1DAY, headers={ 'Content-type': 'application/json', 'Accept-Language': lang }), 'data') if serie_json: #serie_json { "id","seriesId", "airsDayOfWeek", "imdbId", "zap2itId", "added", "addedBy", "lastUpdated", "seriesName", "aliases", "banner", "status", # "firstAired", "network", "networkId", "runtime", "genre, "overview", "airsTime", "rating" , "siteRating", "siteRatingCount" } SaveDict( language_series.index(lang) if lang in language_series and not anidb_prefered else len(language_series), TheTVDB_dict, 'language_rank') SaveDict(Dict(serie_json, 'seriesName'), TheTVDB_dict, 'title') SaveDict(Dict(serie_json, 'seriesName'), TheTVDB_dict, 'original_title') SaveDict(Dict(serie_json, 'imdbId' or IMDbid), TheTVDB_dict, 'IMDbid') SaveDict(Dict(serie_json, 'zap2it_id'), TheTVDB_dict, 'zap2itId') SaveDict(Dict(serie_json, 'rating'), TheTVDB_dict, 'content_rating') if not anidb_prefered: SaveDict(Dict(serie_json, 'overview'), TheTVDB_dict, 'summary') SaveDict(Dict(serie_json, 'firstAired'), TheTVDB_dict, 'originally_available_at') SaveDict(Dict(serie_json, 'genre'), TheTVDB_dict, 'genres') SaveDict(Dict(serie_json, 'network'), TheTVDB_dict, 'studio') SaveDict(Dict(serie_json, 'siteRating'), TheTVDB_dict, 'rating') SaveDict(Dict(serie_json, 'status'), TheTVDB_dict, 'status') if Dict(serie_json, 'runtime') and Dict(serie_json, 'runtime').isdigit(): Log.Info('[ ] duration: {}'.format(Dict(serie_json, 'runtime'))) SaveDict( int(Dict(serie_json, 'runtime')) * 60 * 1000, TheTVDB_dict, 'duration') #in ms in plex if Dict(serie_json, 'banner'): Log.Info('[ ] banner: {}'.format(TVDB_IMG_ROOT + Dict(serie_json, 'banner'))) SaveDict((os.path.join('TheTVDB', 'banner', Dict(serie_json, 'banner')), 1, None), TheTVDB_dict, 'banners', TVDB_IMG_ROOT + Dict(serie_json, 'banner')) ### TVDB Series Actors JSON ### actor_json = Dict( common.LoadFile(filename='actors_{}.json'.format(lang), relativeDirectory="TheTVDB/json/" + TVDBid, url=TVDB_ACTORS_URL % TVDBid, cache=CACHE_1DAY, headers={ 'Content-type': 'application/json', 'Accept-Language': lang }), 'data') if actor_json: #JSON format: 'data': [{"seriesId", "name", "image", "lastUpdated", "imageAuthor", "role", "sortOrder", "id", "imageAdded", },...] for role in actor_json or []: try: SaveDict([{ 'role': Dict(role, 'role'), 'name': Dict(role, 'name'), 'photo': TVDB_IMG_ROOT + role['image'] if Dict(role, 'image') else '' }], TheTVDB_dict, 'roles') except Exception as e: Log.Info("'roles' - error: '{}', role: '{}'".format( str(e), str(role))) #common.DisplayDict(actor_json, ['role', 'name', 'image']) ### Load pages of episodes ### episodes_json, page = [], 1 while page not in (None, '', 'null'): episodes_json_page = common.LoadFile( filename='episodes_page{}_{}.json'.format(page, lang), relativeDirectory="TheTVDB/json/" + TVDBid, url=TVDB_EPISODES_URL % (TVDBid, page), cache=CACHE_1DAY, headers={ 'Content-type': 'application/json', 'Accept-Language': lang }) episodes_json.extend( Dict(episodes_json_page, 'data') ) #Log.Info('TVDB_EPISODES_URL: {}, links: {}'.format(TVDB_EPISODES_URL % (TVDBid, page), Dict(episodes_json_page, 'links'))) page = Dict(episodes_json_page, 'links', 'next') sorted_episodes_json = {} for episode_json in episodes_json: sorted_episodes_json['s{:02d}e{:03d}'.format( Dict(episode_json, 'airedSeason'), Dict(episode_json, 'airedEpisodeNumber'))] = episode_json sorted_episodes_index_list = sorted( sorted_episodes_json, key=natural_sort_key ) #Log.Info('len: {}, sorted_episodes_index_list: {}'.format(len(sorted_episodes_index_list), sorted_episodes_index_list)) ### Build list_abs_eps for tvdb 3/4/5 ### list_abs_eps, list_sp_eps = {}, [] if metadata_source in ('tvdb3', 'tvdb4'): for s in media.seasons: for e in media.seasons[s].episodes: if s == '0': list_sp_eps.append(e) else: list_abs_eps[e] = s Log.Info('Present abs eps: {}'.format(list_abs_eps)) absolute_numering = metadata_source in ('tvdb3', 'tvdb4', 'tvdb5') ### episode loop ### tvdb_special_missing, summary_missing_special, summary_missing, summary_present, episode_missing, abs_manual_placement_info = [], [], [], [], [], [] abs_number, missing_abs_nb, ep_count = 0, False, 0 for index in sorted_episodes_index_list: # Episode and Absolute number calculation engine, episode translation episode_json = sorted_episodes_json[ index] #Log.Info('s{:02d}e{:03d} abs: {:03d} ep: {}'.format(Dict(episode_json, 'airedSeason') or 0, Dict(episode_json, 'airedEpisodeNumber') or 0, Dict(episode_json, 'absoluteNumber') or 0, episode_json)) season = str(Dict(episode_json, 'airedSeason')) episode = str(Dict(episode_json, 'airedEpisodeNumber')) if season != '0': abs_number = abs_number + 1 if not Dict(episode_json, 'absoluteNumber'): missing_abs_nb = True elif missing_abs_nb or Dict(episode_json, 'absoluteNumber') != abs_number: Log.Error( "(s{}e{}) Abs number {:>3} different from readings ({}), possibly tv special" .format(season, episode, abs_number, Dict(episode_json, 'absoluteNumber'))) abs_manual_placement_info.append( "s{}e{} = json abs ep {} / abs_number {}".format( season, episode, Dict(episode_json, 'absoluteNumber'), abs_number)) missing_abs_nb = False abs_number = Dict(episode_json, 'absoluteNumber') ### Missing summaries logs ### numbering = "s{}e{}".format(season, episode) if Dict(episode_json, 'overview'): summary_present.append(numbering) elif season != '0': summary_missing.append(numbering) else: summary_missing_special.append(numbering) ### ep translation anidbid = "" #if anidb_numbering: # if Dict(mappingList, 'defaulttvdbseason')!="a" or season=='0': season, episodex, anidbid = anidb_ep(mappingList, season, episode) # else: season, episode = '1', str(abs_number) #elif metadata_source in ('tvdb3', "tvdb4", "tvdb5") and season!='0': season, episode = '1', str(abs_number) if anidb_numbering: season, episode, anidbid = anidb_ep(mappingList, season, episode) elif metadata_source in ('tvdb3', "tvdb4", "tvdb5") and season != '0': season, episode = '1', str(abs_number) ### Check for Missing Episodes ### if not(season =='0' and episode in list_sp_eps) and \ not(metadata_source in ('tvdb3', 'tvdb4') and str(abs_number) in list_abs_eps) and \ not(not movie and season in media.seasons and episode in media.seasons[season].episodes): Log.Info( '[ ] {:>7} s{:0>2}e{:0>3} anidbid: {:>7} air_date: {}'. format(numbering, season, episode, anidbid, Dict(episode_json, 'FirstAired'))) air_date = Dict(episode_json, 'FirstAired') air_date = int(air_date.replace( '-', '')) if air_date.replace('-', '').isdigit() and int( air_date.replace('-', '')) > 10000000 else 99999999 if int(time.strftime("%Y%m%d")) <= air_date + 1: pass #Log.Info("TVDB - Episode '{}' missing but not aired/missing '{}'".format(numbering, air_date)) elif season == '0': Log.Info("TVDB - type of episode_missing: " + type(episode_missing).__name__) tvdb_special_missing.append(episode) else: Log.Info("TVDB - type of tvdb_special_missing: " + type(tvdb_special_missing).__name__) episode_missing.append( str(abs_number) + " (" + numbering + ")" if metadata_source in ('tvdb3', 'tvdb4') else numbering) ### File present on disk else: #Log.Info('[?] episode_json: {}'.format(episode_json)) Log.Info( '[X] {:>7} s{:0>2}e{:0>3} anidbid: {:>7} air_date: {} abs_number: {}, title: {}' .format(numbering, season, episode, anidbid, Dict(episode_json, 'FirstAired'), abs_number, Dict(episode_json, 'episodeName'))) if not anidb_numbering: SaveDict(abs_number, TheTVDB_dict, 'seasons', season, 'episodes', episode, 'absolute_index') SaveDict(Dict(serie_json, 'rating'), TheTVDB_dict, 'seasons', season, 'episodes', episode, 'content_rating') SaveDict(Dict(serie_json, 'runtime'), TheTVDB_dict, 'seasons', season, 'episodes', episode, 'duration') SaveDict(Dict(episode_json, 'overview'), TheTVDB_dict, 'seasons', season, 'episodes', episode, 'summary') Log.Info('[ ] summary: {}'.format( Dict(episode_json, 'overview'))) SaveDict(Dict(episode_json, 'firstAired'), TheTVDB_dict, 'seasons', season, 'episodes', episode, 'originally_available_at') # Title from serie page if Dict(episode_json, 'episodeName'): rank = language_episodes.index( lang) if lang in language_episodes else len( language_episodes) title = Dict(episode_json, 'episodeName') else: rank, title = len(language_episodes) + 1, '' Log.Info("[1] rank: {:>1}, language: {:>4}, title: {}".format( rank, Dict(episode_json, 'language', 'episodeName'), title)) ### Ep advance information ### ep_count += 1 for lang2 in language_episodes: if lang2 not in (lang, ''): break else: lang2 = 'en' episode_details_json = Dict( common.LoadFile(filename='episode_{}_{}.json'.format( Dict(episode_json, 'id'), lang2), relativeDirectory="TheTVDB/json/" + TVDBid, url=TVDB_EPISODE_DETAILS_URL + str(Dict(episode_json, 'id')), cache=CACHE_1DAY, headers={ 'Content-type': 'application/json', 'Accept-Language': lang2 }), 'data') if episode_details_json: # Std ep info loaded for Library language ten details for 1st language, loading other languages if needed if lang2 in language_episodes and language_episodes.index( lang2) < rank and Dict(episode_details_json, 'language', 'episodeName') == lang2: rank = language_episodes.index(lang2) title = Dict(episode_details_json, 'episodeName') Log.Info( '[2] language_rank: {:>1}, language: {:>4}, title: {}' .format(rank, lang2, title)) SaveDict(Dict(episode_details_json, 'writers'), TheTVDB_dict, 'seasons', season, 'episodes', episode, 'writers') SaveDict(Dict(episode_details_json, 'directors'), TheTVDB_dict, 'seasons', season, 'episodes', episode, 'directors') SaveDict(Dict(episode_details_json, 'guestStars'), TheTVDB_dict, 'seasons', season, 'episodes', episode, 'guest_stars') SaveDict(Dict(episode_details_json, 'siteRating'), TheTVDB_dict, 'seasons', season, 'episodes', episode, 'rating') # Episode screenshoT/Thumbnail if Dict(episode_details_json, 'filename'): SaveDict((str("TheTVDB/episodes/" + os.path.basename( Dict(episode_details_json, 'filename'))), 1, None), TheTVDB_dict, 'seasons', season, 'episodes', episode, 'thumbs', str(TVDB_IMG_ROOT + Dict(episode_details_json, 'filename'))) else: Log.Info('[!] episode_details_json: {}'.format( episode_details_json)) Log.Info('[ ] thumb: {}'.format( TVDB_IMG_ROOT + Dict(episode_details_json, 'filename') if Dict(episode_details_json, 'filename') else '')) # for lang_rank, language in enumerate( language_episodes[1:rank - 1] if len(language_episodes) > 1 and rank >= 2 else []): if not language: continue episode_details_json = Dict(common.LoadFile( filename='episode_{}_{}.json'.format( Dict(episode_json, 'id'), language), relativeDirectory="TheTVDB/json/" + TVDBid, url=TVDB_EPISODE_DETAILS_URL + str(Dict(episode_json, 'id')), cache=CACHE_1DAY, headers={ 'Content-type': 'application/json', 'Accept-Language': lang }), 'data', default={}) if Dict(episode_details_json, 'episodeName'): title = Dict(episode_details_json, 'episodeName') rank = lang_rank Log.Info('[3] language_rank: {}, title: {}'.format( rank, title)) break else: Log.Info('no ep title in language: {}'.format( language_episodes[lang_rank])) SaveDict(title, TheTVDB_dict, 'seasons', season, 'episodes', episode, 'title') SaveDict(rank, TheTVDB_dict, 'seasons', season, 'episodes', episode, 'language_rank') #Log.Info('[?] numbering: {} => s{:>1}e{:>3} language_rank: {:>1}, title: "{}"'.format(numbering, season, episode, rank, title)) Log.Info('-------------') ### Collection ### # get all anidbids sharing the same tvdbids if not movie: ### Logging ### if summary_missing: error_log['Missing Episode Summaries'].append( "TVDBid: %s | Title: '%s' | Missing Episode Summaries: %s" % (common.WEB_LINK % (common.TVDB_SERIE_URL + TVDBid, TVDBid), Dict(TheTVDB_dict, 'title'), str(summary_missing))) if summary_missing_special: error_log['Missing Special Summaries'].append( "TVDBid: %s | Title: '%s' | Missing Special Summaries: %s" % (common.WEB_LINK % (common.TVDB_SERIE_URL + TVDBid, TVDBid), Dict(TheTVDB_dict, 'title'), str(summary_missing_special))) if episode_missing: error_log['Missing Episodes'].append( "TVDBid: %s | Title: '%s' | Missing Episodes: %s" % (common.WEB_LINK % (common.TVDB_SERIE_URL + TVDBid, TVDBid), Dict(TheTVDB_dict, 'title'), str(episode_missing))) if tvdb_special_missing: error_log['Missing Specials'].append( "TVDBid: %s | Title: '%s' | Missing Specials: %s" % (common.WEB_LINK % (common.TVDB_SERIE_URL + TVDBid, TVDBid), Dict(TheTVDB_dict, 'title'), str(tvdb_special_missing))) #Log.Info("abs_manual_placement_info: " + str(abs_manual_placement_info)) #Log.Debug("Episodes without Summary: " + str(sorted(summary_missing, key=natural_sort_key))) ### Picture types JSON download ### language_posters = [ language.strip() for language in Prefs['PosterLanguagePriority'].split(',') ] priority_posters = [ source.strip() for source in Prefs['posters'].split(',') ] Log.Info('language_posters: {}'.format(language_posters)) Log.Info('==========================') for language in language_posters: try: if not language == 'ja': bannerTypes = Dict(common.LoadFile( filename='images_{}.json'.format(language), relativeDirectory="TheTVDB/json/" + TVDBid, url=(TVDB_SERIES_IMG_INFO_URL % TVDBid), cache=0, headers={ 'Content-type': 'application/json', 'Accept-Language': language }), 'data', default={}) except: Log.Info("Invalid image JSON from url: " + TVDB_SERIES_IMG_INFO_URL % TVDBid) else: #JSON format = {"fanart", "poster", "season", "seasonwide", "series"} metanames = { 'fanart': "art", 'poster': "posters", 'series': "banners", 'season': "seasons", 'seasonwide': 'seasonwide' } # count_valid = {key: 0 for key in metanames} anidb_offset = sorted( (Dict(mappingList, 'poster_id_array', TVDBid) or {}).keys()).index(AniDBid) if AniDBid and AniDBid in Dict( mappingList, 'poster_id_array', TVDBid) else 0 language_priority = [ item.strip() for item in Prefs['EpisodeLanguagePriority'].split(',') ] Log.Info( "bannerTypes: {}, anidb_offset: {}, AniDBid: {}, anidb_array: {}" .format( bannerTypes, anidb_offset, AniDBid, str((Dict(mappingList, 'poster_id_array', TVDBid) or {}).keys()))) #Loop per banner type ("fanart", "poster", "season", "series") skip 'seasonwide' - Load bannerType images list JSON for bannerType in bannerTypes or []: if bannerTypes[bannerType] == 0 or bannerType in ( 'seasonwide', 'series' ) or movie and not bannerType in ('fanart', 'poster'): continue #Loop if no images #if anidb_numbering and Dict(mappingList, 'defaulttvdbseason') not in ('a', '1') and bannerType=='poster': continue #skip if anidb numbered serie mapping to season 0 or 2+ Log.Info('------------------------------') try: images = Dict(common.LoadFile( filename='images_{}_{}.json'.format( bannerType, language), relativeDirectory="TheTVDB/json/" + TVDBid, url=TVDB_SERIES_IMG_QUERY_URL.format( TVDBid, bannerType), cache=CACHE_1DAY, headers={'Accept-Language': language}), 'data', default={}) except: images = {} Log("Bad image type query data for TVDB id: %s (bannerType: %s)" % (TVDBid, bannerType)) else: images = sorted( images, key=lambda x: Dict( x, "ratingsInfo", "average", default=0), reverse=True) for image in images: #JSON format = {"data": [{"id", "keyType", "subKey"(season/graphical/text), "fileName", "resolution", "ratingsInfo": {"average", "count"}, "thumbnail"}]} #rank rank = 1 if bannerType == 'poster' and anidb_offset == divmod( count_valid['poster'], Dict(bannerTypes, 'poster', default=0) )[1] else count_valid[bannerType] + 2 if language in language_posters: rank = (rank // 30) * 30 * language_posters.index( language) + rank % 30 if 'TheTVDB' in priority_posters: rank = rank + 6 * priority_posters.index( 'TheTVDB') rank = rank + language_posters.index(language) * 20 if AniDB_movie: rank = rank + Dict( bannerTypes, 'poster', default=0) if rank + Dict( bannerTypes, 'poster', default=0) < 99 else 99 ### Adding picture ### thumbnail = TVDB_IMG_ROOT + image[ 'thumbnail'] if Dict(image, 'thumbnail') else None Log.Info( "[!] bannerType: {:>7} subKey: {:>9} rank: {:>3} filename: {} thumbnail: {} resolution: {} average: {} count: {}" .format( metanames[bannerType], Dict(image, 'subKey'), rank, TVDB_IMG_ROOT + Dict(image, 'fileName'), TVDB_IMG_ROOT + Dict(image, 'thumbnail'), Dict(image, 'resolution'), Dict(image, 'ratingsInfo', 'average'), Dict(image, 'ratingsInfo', 'average', 'count'))) if bannerType == 'season': #tvdb season posters or anidb specials and defaulttvdb season ## season 0 et empty+ season ==defaulttvdbseason(a=1) if not anidb_numbering: SaveDict(('TheTVDB/' + image['fileName'], rank, thumbnail), TheTVDB_dict, 'seasons', str(image['subKey']), 'posters', TVDB_IMG_ROOT + image['fileName']) elif str(image['subKey'] or 0) in ('1' if Dict( mappingList, 'defaulttvdbseason') == 'a' else (mappingList, 'defaulttvdbseason'), '0'): SaveDict( ('TheTVDB/' + image['fileName'], 1 if rank == 3 else 3 if rank == 1 else rank, thumbnail), TheTVDB_dict, 'seasons', '0' if str(image['subKey']) == '0' else '1' if anidb_numbering else str( image['subKey']), 'posters', TVDB_IMG_ROOT + image['fileName']) SaveDict(('TheTVDB/' + image['fileName'], rank, thumbnail), TheTVDB_dict, 'posters', TVDB_IMG_ROOT + image['fileName']) elif bannerType == 'fanart' or not anidb_numbering or Dict( mappingList, 'defaulttvdbseason') in ('a', '1') or str( image['subKey'] or '1') == Dict( mappingList, 'defaulttvdbseason') or not Dict( bannerTypes, 'season' ) and bannerType == 'poster': SaveDict( ('TheTVDB/' + image['fileName'], rank, thumbnail), TheTVDB_dict, metanames[bannerType], TVDB_IMG_ROOT + image['fileName']) #use art + posters tvdb #if bannerType == 'season': # if anidb_numbering and ('1' if Dict(mappingList, 'defaulttvdbseason')=='a' else Dict(mappingList, 'defaulttvdbseason'))==str(image['subKey']): # SaveDict(('TheTVDB/'+image['fileName'], 1 if rank==3 else 3 if rank==1 else rank, thumbnail), TheTVDB_dict, 'seasons', '0' if str(image['subKey'])=='0' else '1', 'posters', TVDB_IMG_ROOT + image['fileName']) # elif not anidb_numbering: # season = str(int(image['subKey'])+(0 if Dict(mappingList, 'defaulttvdbseason')=="0" or not Dict(mappingList, 'defaulttvdbseason').isdigit() else int(Dict(mappingList, 'defaulttvdbseason'))-1)) # SaveDict( ('TheTVDB/'+image['fileName'], rank, thumbnail), TheTVDB_dict, 'seasons', season, 'posters', TVDB_IMG_ROOT + image['fileName']) #if bannerType != 'season': # SaveDict(('TheTVDB/'+image['fileName'], rank, thumbnail), TheTVDB_dict, metanames[bannerType], TVDB_IMG_ROOT + image['fileName']) count_valid[bannerType] = count_valid[ bannerType] + 1 #Otherwise with += SyntaxError: Line 142: Augmented assignment of object items and slices is not allowed #Log.Info("Posters : {}/{}, Season posters: {}/{}, Art: {}/{}".format(count_valid['poster'], Dict(bannerTypes, 'poster'), count_valid['season'], Dict(bannerTypes, 'season') or 0, count_valid['fanart'], Dict(bannerTypes, 'fanart'))) if not Dict(bannerTypes, 'poster'): error_log['TVDB posters missing'].append( "TVDBid: %s | Title: '%s'" % (common.WEB_LINK % (common.TVDB_SERIE_URL + TVDBid, TVDBid), Dict(TheTVDB_dict, 'title'))) if not Dict(bannerTypes, 'season'): error_log['TVDB season posters missing'].append( "TVDBid: %s | Title: '%s'" % (common.WEB_LINK % (common.TVDB_SERIE_URL + TVDBid, TVDBid), Dict(TheTVDB_dict, 'title'))) Log.Info('==========================') Log.Info( "url: '{}', IMDbid: {}, Present episodes: {}, Missing: {}".format( TVDB_SERIES_URL % TVDBid, IMDbid, ep_count, sorted(episode_missing, key=natural_sort_key))) Log.Info('TheTVDB_dict: {}'.format(TheTVDB_dict)) return TheTVDB_dict, IMDbid
def GetMetadata(myanimelistId, type, media): Log.Info("=== MyAnimeList.GetMetadata() ===".ljust(157, '=')) detailUrl = MYANIMELIST_URL_DETAILS.format(id=myanimelistId) Log.Info("URL : " + str(detailUrl)) try: json = JSON.ObjectFromString( HTTP.Request(detailUrl, sleep=2.0, cacheTime=MYANIMELIST_CACHE_TIME).content) except Exception as e: Log.Error("No Detail Information were available " + str(e)) return result = {} if json: if Dict(json, "id"): SaveDict(str(Dict(json, "id")), result, "id") Log.Debug("ID: " + str(Dict(json, "id"))) if Dict(json, "title"): SaveDict(str(Dict(json, "title")), result, "title") Log.Debug("Title: " + str(Dict(json, "title"))) if Dict(json, "synopsis"): SaveDict( str(re.sub(re.compile('<.*?>'), '', Dict(json, "synopsis"))), result, "summary") Log.Debug("Summary: " + str(Dict(json, "synopsis"))) if Dict(json, "members_score"): SaveDict(float(Dict(json, "members_score")), result, "rating") Log.Debug("Rating: " + str(Dict(json, "members_score"))) if Dict(json, "classification"): SaveDict(str(Dict(json, "classification")), result, "content_rating") Log.Debug("Content Rating: " + str(Dict(json, "classification"))) if Dict(json, "duration"): SaveDict(int(Dict(json, "duration") * 60000), result, "duration") Log.Debug("Duration: " + str(Dict(json, "duration") * 60000)) if Dict(json, "start_date"): SaveDict(Dict(json, "start_date"), result, "originally_available_at") Log.Debug("Release date: " + str(Dict(json, "start_date"))) if Dict(json, "image_url"): SaveDict((Dict(json, "image_url"), 1, None), result, 'poster', Dict(json, "image_url")) Log.Debug("Cover: " + str(Dict(json, "image_url"))) for genre in Dict(json, "genres") if Dict( json, "genres") and len(Dict(json, "genres")) > 0 else []: SaveDict([str(genre)], result, "genres") Log.Debug("Genres: " + str(Dict(json, "genres"))) ### TODO: Switch to Studios when they are available in the API (or add Producers to metadata when this is possible in Plex) #if Dict(json, "producers") and len(Dict(json, "producers")) > 0: # apiAnimeProducers = "" # for idx, producer in enumerate(Dict(json, "producers")): apiAnimeProducers += str(producer) + ", " # SaveDict(str(apiAnimeProducers[:-2]), result, "studio") # Log.Debug("Producers: " + str(Dict(json, "producers"))) if type == "tvshow": Log.Debug("Adding TV-Show specific data") Log.Debug("Episodes: " + str(Dict(json, "episodes"))) # metadata.seasons[1].episode_count = int( Dict(json, "episodes") or len(media.seasons[1].episodes)) # pages = int(math.ceil(float(metadata.seasons[1].episode_count) / 100)) # fetch the episodes in 100 chunks # if pages is not None: # for page in range(1, pages + 1): # episodesUrl = MYANIMELIST_URL_EPISODES.format(id=metadata.id,page=page) # try: # Log.Info("Fetching URL " + str(episodesUrl)) # episodeResult = JSON.ObjectFromString(HTTP.Request(episodesUrl, sleep=2.0, cacheTime=MYANIMELIST_CACHE_TIME).content) # except Exception as e: Log.Info("episode results could not be requested " + str(e)); return # if "error" in episodeResult: # Log.Warn("Episode Information are not available (" + str(episodeResult["error"]) + ") (might want to add them to MyAnimeList.net)!") # break # # for episode in episodeResult: # apiEpisodeNumber = Dict(json, "number" ) # apiEpisodeTitle = Dict(json, "title" ) # apiEpisodeAirDate = Dict(json, "air_date") # if apiEpisodeNumber is not None: # plexEpisode = metadata.seasons[1].episodes[int(apiEpisodeNumber)] # plexEpisode.title = str(apiEpisodeTitle) if apiEpisodeTitle else "Episode: #" + str(apiEpisodeNumber) # plexEpisode.originally_available_at = datetime.strptime(str(apiEpisodeAirDate), "%Y-%m-%d") if apiEpisodeAirDate else datetime.now() # Log.Debug("Episode " + str(apiEpisodeNumber) + ": " + str(apiEpisodeTitle) + " - " + str(apiEpisodeAirDate)) # if type == "movie": Log.Debug("Adding Movie specific data, nothing specific to add") Log.Info("MyAnimeList_dict: {}".format(DictString(result, 4))) Log.Info("--- return ---".ljust(157, '-')) return result