def Search(results, media, lang, manual, movie): #if maxi<50: maxi = tvdb.Search_TVDB(results, media, lang, manual, movie) '''search for TVDB id series ''' Log.Info("=== TheTVDB.Search() ===".ljust(157, '=')) #series_data = JSON.ObjectFromString(GetResultFromNetwork(TVDB_SEARCH_URL % mediaShowYear, additionalHeaders={'Accept-Language': lang}))['data'][0] orig_title = ( media.title if movie else media.show ) maxi = 0 try: TVDBsearchXml = XML.ElementFromURL( TVDB_SERIE_SEARCH + quote(orig_title), headers=common.COMMON_HEADERS, cacheTime=CACHE_1HOUR * 24) except Exception as e: Log.Error("TVDB Loading search XML failed, Exception: '%s'" % e) else: for serie in TVDBsearchXml.xpath('Series'): a, b = orig_title, GetXml(serie, 'SeriesName').encode('utf-8') #a, b = cleansedTitle, cleanse_title (serie.xpath('SeriesName')[0].text) if b=='** 403: Series Not Permitted **': continue score = 100 - 100*Util.LevenshteinDistance(a,b) / max(len(a),len(b)) if a!=b else 100 if maxi<score: maxi = score Log.Info("TVDB - score: '%3d', id: '%6s', title: '%s'" % (score, GetXml(serie, 'seriesid'), GetXml(serie, 'SeriesName'))) results.Append(MetadataSearchResult(id="%s-%s" % ("tvdb", GetXml(serie, 'seriesid')), name="%s [%s-%s]" % (GetXml(serie, 'SeriesName'), "tvdb", GetXml(serie, 'seriesid')), year=None, lang=lang, score=score) ) return maxi
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("=== 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 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, 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(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(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