Exemple #1
0
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
Exemple #2
0
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
Exemple #3
0
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
Exemple #4
0
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
Exemple #5
0
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 &#39;%s&#39; 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
Exemple #6
0
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 &#39;%s&#39; 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
Exemple #7
0
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 &#39;%s&#39; 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
Exemple #8
0
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
Exemple #9
0
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