def Start(): Log.Info("".ljust(157, '=')) Log.Info("HTTP Anidb Metadata Agent by ZeroQI (Forked from Atomicstrawberry's v0.4, AnimeLists XMLs by SdudLee) - CPU: {}, OS: {}".format(Platform.CPU, Platform.OS)) #HTTP.CacheTime = CACHE_1DAY # in sec: CACHE_1MINUTE, CACHE_1HOUR, CACHE_1DAY, CACHE_1WEEK, CACHE_1MONTH HTTP.CacheTime = CACHE_1MINUTE*30 ValidatePrefs() # Load core files AnimeLists.GetAniDBTVDBMap() AnimeLists.GetAniDBMovieSets() AniDB.GetAniDBTitlesDB()
def Update(metadata, media, lang, force, movie): from common import Log #Import here for startup logging to go to the plex pms log Log.Open(media=media, movie=movie, search=False) source = metadata.id.split('-', 1)[0] error_log = { 'AniDB summaries missing' :[], 'AniDB posters missing' :[], 'anime-list AniDBid missing':[], 'anime-list studio logos' :[], 'TVDB posters missing' :[], 'TVDB season posters missing':[], 'anime-list TVDBid missing' :[], 'Plex themes missing' :[], 'Missing Episodes' :[], 'Missing Specials' :[], 'Missing Episode Summaries' :[], 'Missing Special Summaries':[]} Log.Info('=== Update() ==='.ljust(157, '=')) Log.Info("id: {}, title: {}, lang: {}, force: {}, movie: {}".format(metadata.id, metadata.title, lang, force, movie)) Log.Info("start: {}".format(datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S,%f"))) dict_AnimeLists, AniDBid, TVDBid, TMDbid, IMDbid, mappingList = AnimeLists.GetMetadata(media, movie, error_log, metadata.id) dict_TheTVDB, IMDbid = TheTVDBv2.GetMetadata(media, movie, error_log, lang, source, AniDBid, TVDBid, IMDbid, mappingList, Dict(AniDB, 'movie')) dict_tvdb4 = common.GetMetadata(media, movie, source, TVDBid, mappingList) dict_AniDB, ANNid, MALid = AniDB.GetMetadata(media, movie, error_log, source, AniDBid, TVDBid, AnimeLists.AniDBMovieSets, mappingList) dict_TheMovieDb, TSDbid, TMDbid, IMDbid = TheMovieDb.GetMetadata(media, movie, TVDBid, TMDbid, IMDbid) dict_FanartTV = FanartTV.GetMetadata( movie, TVDBid, TMDbid, IMDbid) dict_Plex = Plex.GetMetadata(metadata, error_log, TVDBid, Dict(dict_TheTVDB, 'title')) dict_TVTunes = TVTunes.GetMetadata(metadata, Dict(dict_TheTVDB, 'title'), Dict(mappingList, AniDBid, 'name')) #Sources[m:eval('dict_'+m)] dict_OMDb = OMDb.GetMetadata(movie, IMDbid) #TVDBid=='hentai' dict_MyAnimeList = MyAnimeList.GetMetadata(movie, MALid ) dict_Local = Local.GetMetadata(media, movie) if common.AdjustMapping(source, mappingList, dict_AniDB, dict_TheTVDB): dict_AniDB, ANNid, MALid = AniDB.GetMetadata(media, movie, error_log, source, AniDBid, TVDBid, AnimeLists.AniDBMovieSets, mappingList) Log.Info('=== Update() ==='.ljust(157, '=')) Log.Info("AniDBid: '{}', TVDBid: '{}', TMDbid: '{}', IMDbid: '{}', ANNid:'{}', MALid: '{}'".format(AniDBid, TVDBid, TMDbid, IMDbid, ANNid, MALid)) common.write_logs(media, movie, error_log, source, AniDBid, TVDBid) common.UpdateMeta(metadata, media, movie, {'AnimeLists': dict_AnimeLists, 'AniDB': dict_AniDB, 'TheTVDB': dict_TheTVDB, 'TheMovieDb': dict_TheMovieDb, 'FanartTV': dict_FanartTV, 'tvdb4': dict_tvdb4, 'Plex': dict_Plex, 'TVTunes': dict_TVTunes, 'OMDb': dict_OMDb, 'MyAnimeList': dict_MyAnimeList, 'Local': dict_Local}, mappingList) Log.Info('=== Update() ==='.ljust(157, '=')) Log.Info("end: {}".format(datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S,%f"))) Log.Close()
def Update(metadata, media, lang, force, movie): log = common.PlexLog(media=media, movie=movie, search=False) source = metadata.id.split('-', 1)[0] error_log = { 'AniDB summaries missing' :[], 'AniDB posters missing' :[], 'anime-list AniDBid missing':[], 'anime-list studio logos' :[], 'TVDB posters missing' :[], 'TVDB season posters missing':[], 'anime-list TVDBid missing' :[], 'Plex themes missing' :[], 'Missing Episodes' :[], 'Missing Specials' :[], 'Missing Episode Summaries' :[], 'Missing Special Summaries':[]} Log.Info('=== Update ==='.ljust(157, '=')) Log.Info("id: {}, title: {}, lang: {}, force: {}, movie: {}".format(metadata.id, metadata.title, lang, force, movie)) dict_AnimeLists, AniDBid, TVDBid, TMDbid, IMDbid, mappingList = AnimeLists.GetMetadata(media, movie, error_log, metadata.id, AniDBMovieSets) #, AniDBTVDBMap dict_AniDB, ANNid, MALid = AniDB.GetMetadata(media, movie, error_log, source, AniDBid, TVDBid, AniDBMovieSets, mappingList) dict_TheTVDB, IMDbid = TheTVDBv2.GetMetadata(media, movie, error_log, lang, source, AniDBid, TVDBid, IMDbid, mappingList, Dict(AniDB, 'movie')) dict_TheMovieDb, TSDbid, TMDbid, IMDbid = TheMovieDb.GetMetadata(media, movie, TVDBid, TMDbid, IMDbid) dict_FanartTV = FanartTV.GetMetadata( movie, TVDBid, TMDbid, IMDbid) dict_tvdb4 = common.GetMetadata(media, movie, source, TVDBid) dict_Plex = Plex.GetMetadata(metadata, error_log, TVDBid, Dict(dict_TheTVDB, 'title')) dict_TVTunes = TVTunes.GetMetadata(metadata, Dict(dict_TheTVDB, 'title'), Dict(mappingList, AniDBid, 'name')) #Sources[m:eval('dict_'+m)] dict_OMDb = OMDb.GetMetadata(movie, IMDbid) if Prefs['OMDbApiKey'] not in ('None', '', 'N/A') else {} #TVDBid=='hentai' dict_MyAnimeList = MyAnimeList.GetMetadata(movie, MALid ) if MALid else {} # dict_Local = Local.GetMetadata(media, movie) Log.Info("".ljust(157, '-')) Log.Info("Update() - AniDBid: '{}', TVDBid: '{}', TMDbid: '{}', IMDbid: '{}', ANNid:'{}', MALid: '{}'".format(AniDBid, TVDBid, TMDbid, IMDbid, ANNid, MALid)) common.write_logs(media, movie, error_log, source, id, AniDBid, TVDBid) common.UpdateMeta(metadata, media, movie, {'AnimeLists': dict_AnimeLists, 'TheTVDB': dict_TheTVDB, 'AniDB': dict_AniDB, 'Plex': dict_Plex, 'MyAnimeList': dict_MyAnimeList, 'TheMovieDb': dict_TheMovieDb, 'TVTunes': dict_TVTunes, 'tvdb4': dict_tvdb4, 'OMDb': dict_OMDb, 'FanartTV': dict_FanartTV}, mappingList) log.stop()
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(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
import AniDB # Functions: GetMetadata, Search, GetAniDBTitlesDB, GetAniDBTitle Variables: ANIDB_SERIE_URL import TheTVDBv2 # Functions: GetMetadata, Search Variables: TVDB_SERIE_URL, TVDB_IMAGES_URL import TheMovieDb # Functions: GetMetadata, Search Variables: None import MyAnimeList # Functions: GetMetadata Variables: None import OMDb # Functions: GetMetadata Variables: None import FanartTV # Functions: GetMetadata Variables: None import Plex # Functions: GetMetadata Variables: None import TVTunes # Functions: GetMetadata Variables: None import Local # Functions: GetMetadata Variables: None import re import os from io import open ### Variables ### AniDBMovieSets = AnimeLists.GetAniDBMovieSets() ### Pre-Defined ValidatePrefs function Values in "DefaultPrefs.json", accessible in Settings>Tab:Plex Media Server>Sidebar:Agents>Tab:Movies/TV Shows>Tab:HamaTV ####### def ValidatePrefs(): Log.Info("".ljust(157, '=')) Log.Info ("ValidatePrefs(), PlexRoot: "+common.PlexRoot) PrefsFieldList = list(set(common.FieldListMovies + common.FieldListSeries + common.FieldListEpisodes + common.DefaultPrefs)) # set is un-ordered lsit so order is lost filename = os.path.join(common.PlexRoot, 'Plug-ins', 'Hama.bundle', 'Contents', 'DefaultPrefs.json') if os.path.isfile(filename): try: ### Load 'DefaultPrefs.json' to have access to default settings ### with open(filename, 'r') as file: json = JSON.ObjectFromString(file.read(), encoding=None) except Exception as e: json = None; Log.Info("Error :"+str(e)+", filename: "+filename) if json: Log.Info ("Loaded: "+filename) Pref_list={} for entry in json: #Build Pref_list dict from json file
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' 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 source in ("tvdb", "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 = 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 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 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, 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)) ### 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(',')]) 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')) 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, 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') 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