· 2 years ago · Apr 16, 2023, 07:30 AM
1### TheTVDB.com API v2 ###
2# https://thetvdb.com/api/A27AD9BE0DA63333/series/103291/all/en.xml
3
4### Imports ###
5# Python Modules #
6import os
7import time
8import re
9from urllib import quote
10# Plex Modules #
11#from collections import defaultdict
12# HAMA Modules #
13import common
14from common import Log, DictString, Dict, SaveDict, GetXml # Direct import of heavily used functions
15import AnimeLists
16
17### Variables ###
18TVDB_API_KEY = 'A27AD9BE0DA63333'
19TVDB_IMG_ROOT = 'https://thetvdb.plexapp.com/banners/'
20TVDB_BASE_URL = 'https://api.thetvdb.com' #'https://api-beta.thetvdb.com' #tvdb v2 plex proxy site'' # TODO Start using TVDB's production api (TVDB is behind CF) when available and possibly a plex proxy for it
21TVDB_LOGIN_URL = TVDB_BASE_URL + '/login'
22TVDB_LOGIN_REFRESH_URL = TVDB_BASE_URL + '/refresh_token'
23TVDB_SERIES_URL = TVDB_BASE_URL + '/series/{id}'
24TVDB_EPISODE_URL = TVDB_BASE_URL + '/episodes/{id}'
25TVDB_EPISODE_PAGE_URL = TVDB_SERIES_URL + '/episodes?page={page}'
26TVDB_ACTORS_URL = TVDB_SERIES_URL + '/actors'
27TVDB_SERIES_IMG_INFO_URL = TVDB_SERIES_URL + '/images'
28TVDB_SERIES_IMG_QUERY_URL = TVDB_SERIES_URL + '/images/query?keyType={type}'
29
30TVDB_SEARCH_URL = TVDB_BASE_URL + '/search/series?name=%s'
31TVDB_SERIE_SEARCH = 'https://thetvdb.com/api/GetSeries.php?seriesname='
32
33#THETVDB_LANGUAGES_CODE = { 'cs': '28', 'da': '10', 'de': '14', 'el': '20', 'en': '7', 'es': '16', 'fi': '11', 'fr': '17', 'he': '24',
34# 'hr': '31', 'hu': '19', 'it': '15', 'ja': '25', 'ko': '32', 'nl': '13', 'no': '9', 'pl': '18', 'pt': '26',
35# 'ru': '22', 'sv': '8', 'tr': '21', 'zh': '27', 'sl': '30'}
36TVDB_HEADERS = {}
37TVDB_AUTH_TIME = None
38netLocked = {}
39
40### Functions ###
41def LoadFileTVDB(id="", filename="", url="", headers={}):
42 """ Wrapper around "common.LoadFile()" to remove the need to consistently define arguments 'relativeDirectory'/'cache'/'headers'
43 """
44 global TVDB_AUTH_TIME
45
46 while 'LoadFileTVDB' in netLocked and netLocked['LoadFileTVDB'][0]:
47 Log.Root("TheTVDBv2.LoadFileTVDB() - Waiting for lock: 'LoadFileTVDB'"); time.sleep(1)
48 netLocked['LoadFileTVDB'] = (True, int(time.time())) #Log.Root("Lock acquired: 'LoadFile'")
49
50 # If no auth or auth is >12hrs old, authenticate from scratch
51 if 'Authorization' not in TVDB_HEADERS or (TVDB_AUTH_TIME and (time.time()-TVDB_AUTH_TIME) > CACHE_1DAY/2):
52 try:
53 TVDB_HEADERS['Authorization'] = 'Bearer ' + JSON.ObjectFromString(HTTP.Request(TVDB_LOGIN_URL, data=JSON.StringFromObject( {'apikey':TVDB_API_KEY} ), headers=common.UpdateDict(headers, common.COMMON_HEADERS), cacheTime=0).content)['token']
54 TVDB_AUTH_TIME = time.time()
55 except Exception as e: Log.Root('TheTVDBv2.LoadFileTVDB() - Authorization Error: {}'.format(e))
56 else: Log.Root('TheTVDBv2.LoadFileTVDB() - URL {}, headers: {}'.format(TVDB_LOGIN_URL, headers))
57
58 netLocked['LoadFileTVDB'] = (False, 0) #Log.Root("Lock released: 'LoadFile'")
59
60 return common.LoadFile(filename=filename, relativeDirectory=os.path.join("TheTVDB", "json", id), url=url, headers=common.UpdateDict(headers, TVDB_HEADERS))
61
62def GetMetadata(media, movie, error_log, lang, metadata_source, AniDBid, TVDBid, IMDbid, mappingList):
63 ''' TVDB - Load serie JSON
64 '''
65 Log.Info("=== TheTVDB.GetMetadata() ===".ljust(157, '='))
66 TheTVDB_dict = {}
67 max_season = 0
68 anidb_numbering = metadata_source=="anidb" and (movie or max(map(int, media.seasons.keys()))<=1)
69 anidb_prefered = anidb_numbering and Dict(mappingList, 'defaulttvdbseason') != '1'
70 language_series = [language.strip() for language in Prefs['SerieLanguagePriority' ].split(',') if language.strip() not in ('x-jat', 'zh-Hans', 'zh-Hant', 'zh-x-yue', 'zh-x-cmn', 'zh-x-nan', 'main')]
71 language_episodes = [language.strip() for language in Prefs['EpisodeLanguagePriority'].split(',') if language.strip() not in ('x-jat', 'zh-Hans', 'zh-Hant', 'zh-x-yue', 'zh-x-cmn', 'zh-x-nan', 'main')]
72 Log.Info("TVDBid: '{}', IMDbid: '{}', language_series : {}, language_episodes: {}".format(TVDBid, IMDbid, language_series , language_episodes))
73
74 if not TVDBid.isdigit(): Log.Info('TVDBid non-digit'); return TheTVDB_dict, IMDbid
75
76 ### TVDB Series JSON ###
77 Log.Info("--- series ---".ljust(157, '-'))
78 json = {}
79 if lang not in language_series: language_series.insert(0, lang) #for summary in lang (library setting) language
80 if 'en' not in language_series: language_series.insert(0, 'en') #for failover title
81 if lang not in language_episodes: language_episodes.append(lang) #for summary in lang (library setting) language
82 if 'en' not in language_episodes: language_episodes.append('en') #for failover title
83 for language in language_series:
84 json[language] = Dict(LoadFileTVDB(id=TVDBid, filename='series_{}.json'.format(language), url=TVDB_SERIES_URL.format(id=TVDBid)+'?'+language, headers={'Accept-Language': language}), 'data')
85 if Dict(json[language], 'seriesName'): # and not Dict(TheTVDB_dict, 'language_rank'):
86 SaveDict( language_series.index(language) if not anidb_prefered else len(language_series), TheTVDB_dict, 'language_rank')
87 Log.Info("[ ] language_rank: {}" .format(Dict(TheTVDB_dict, 'language_rank')))
88 Log.Info("[ ] title: {}" .format(SaveDict( Dict(json[language], 'seriesName') or Dict(serie2_json, 'seriesName'), TheTVDB_dict, 'title' )))
89 Log.Info("[ ] original_title: {}" .format(SaveDict( Dict(json[language], 'seriesName') or Dict(serie2_json, 'seriesName'), TheTVDB_dict, 'original_title' )))
90 if Dict(json, lang) and (Dict(json, lang, 'overview') or Dict(TheTVDB_dict, 'language_rank')): break #only need json in lang for summary, in 'en' for most things
91 if not anidb_prefered: SaveDict( Dict(json, lang, 'overview' ).strip(" \n\r") or Dict(json, 'en', 'overview').strip(" \n\r"), TheTVDB_dict, 'summary')
92 if Dict(json, lang):
93 #format: { "id","seriesId", "airsDayOfWeek", "imdbId", "zap2itId", "added", "addedBy", "lastUpdated", "seriesName", "aliases", "banner", "status",
94 # "firstAired", "network", "networkId", "runtime", "genre, "overview", "airsTime", "rating" , "siteRating", "siteRatingCount" }
95 Log.Info("[ ] IMDbid: {}" .format(SaveDict( Dict(json[lang], 'imdbId' or IMDbid), TheTVDB_dict, 'IMDbid' )))
96 Log.Info("[ ] zap2itId: {}" .format(SaveDict( Dict(json[lang], 'zap2it_id' ), TheTVDB_dict, 'zap2itId' )))
97 Log.Info("[ ] content_rating: {}" .format(SaveDict( Dict(json[lang], 'rating' ), TheTVDB_dict, 'content_rating' )))
98 Log.Info("[ ] originally_available_at: {}".format(SaveDict( Dict(json[lang], 'firstAired'), TheTVDB_dict, 'originally_available_at')))
99 Log.Info("[ ] studio: {}" .format(SaveDict( Dict(json[lang], 'network' ), TheTVDB_dict, 'studio' )))
100 Log.Info("[ ] rating: {}" .format(SaveDict( Dict(json[lang], 'siteRating'), TheTVDB_dict, 'rating' )))
101 Log.Info("[ ] status: {}" .format(SaveDict( Dict(json[lang], 'status' ), TheTVDB_dict, 'status' )))
102 Log.Info("[ ] genres: {}" .format(SaveDict( sorted(Dict(json[lang], 'genre')), TheTVDB_dict, 'genres' )))
103 if Dict(json[lang], 'runtime').isdigit():
104 Log.Info('[ ] duration: {}' .format(SaveDict( int(Dict(json[lang], 'runtime'))*60*1000, TheTVDB_dict, 'duration' ))) #in ms in plex
105
106 series_images = { # Pull the primary images used for the series entry
107 'poster': Dict(json[language], 'poster'),
108 'banner': Dict(json[language], 'banner'),
109 'fanart': Dict(json[language], 'fanart'),
110 'seasonwide': Dict(json[language], 'seasonwide'),
111 'series': Dict(json[language], 'series')}
112
113 ### TVDB Series Actors JSON ###
114 Log.Info("--- actors ---".ljust(157, '-'))
115 actor_json = Dict(LoadFileTVDB(id=TVDBid, filename='actors_{}.json'.format(lang), url=TVDB_ACTORS_URL.format(id=TVDBid), headers={'Accept-Language': lang}), 'data', default=[])
116 if actor_json: #JSON format: 'data': [{"seriesId", "name", "image", "lastUpdated", "imageAuthor", "role", "sortOrder", "id", "imageAdded", },...]
117 for role in actor_json:
118 try:
119 role_dict = {'role': Dict(role, 'role'), 'name': Dict(role, 'name'), 'photo': TVDB_IMG_ROOT + role['image'] if Dict(role, 'image') else ''}
120 SaveDict([role_dict], TheTVDB_dict, 'roles')
121 Log.Info("[ ] role: {:<50}, name: {:<20}, photo: {}".format(role_dict['role'], role_dict['name'], role_dict['photo']))
122 except Exception as e: Log.Info(" role: {}, error: '{}'".format(str(role), str(e)))
123 #common.DisplayDict(actor_json, ['role', 'name', 'image'])
124
125 ### Load pages of episodes ###
126 Log.Info("--- episodes ---".ljust(157, '-'))
127 episodes_json, sorted_episodes_json, next_page = [], {}, 1
128 while next_page not in (None, '', 'null'):
129 episodes_json_page = LoadFileTVDB(id=TVDBid, filename='episodes_page{}_{}.json'.format(next_page, lang), url=TVDB_EPISODE_PAGE_URL.format(id=TVDBid, page=next_page), headers={'Accept-Language': lang})
130 next_page = Dict(episodes_json_page, 'links', 'next')
131 episodes_json.extend(Dict(episodes_json_page, 'data'))
132 for episode_json in episodes_json: sorted_episodes_json['s{:02d}e{:03d}'.format(Dict(episode_json, 'airedSeason'), Dict(episode_json, 'airedEpisodeNumber'))] = episode_json
133
134 ### Build list_abs_eps for tvdb 3/4/5 ###
135 list_abs_eps, list_sp_eps={}, []
136 if metadata_source in ('tvdb3', 'tvdb4'):
137 for s in media.seasons:
138 for e in media.seasons[s].episodes:
139 if s=='0': list_sp_eps.append(e)
140 else: list_abs_eps[e]=s
141 Log.Info('Present abs eps: {}'.format(list_abs_eps))
142
143 ### episode loop ###
144 tvdb_special_missing, summary_missing_special, summary_missing, summary_present, episode_missing, episode_missing_season, episode_missing_season_all, abs_number, ep_count = [], [], [], [], [], [], True, 0, 0
145
146 # To avoid duplicate mapping we will remember episodes that have been mapped to a different value
147 mapped_episodes = []
148 for key in sorted(sorted_episodes_json):
149 episode_json = sorted_episodes_json[key]
150 episode = str(Dict(episode_json, 'airedEpisodeNumber'))
151 season = str(Dict(episode_json, 'airedSeason' ))
152 season, episode, anidbid = AnimeLists.anidb_ep(mappingList, season, episode)
153 if anidbid != 'xxxxxxx': mapped_episodes.append((season, episode))
154
155 for key in sorted(sorted_episodes_json):
156
157 # Episode and Absolute number calculation engine, episode translation
158 episode_json = sorted_episodes_json[key]
159 episode = str(Dict(episode_json, 'airedEpisodeNumber'))
160 season = str(Dict(episode_json, 'airedSeason' ))
161 numbering = "s{}e{}".format(season, episode)
162
163 # Replace all the individual episodes reported as missing with a single season 'sX' entry
164 if episode=="1":
165 if not episode_missing_season_all: episode_missing.extend(episode_missing_season)
166 elif episode_missing_season:
167 first_entry, last_entry = episode_missing_season[0], episode_missing_season[-1]
168 fm = re.match(r'((?P<abs>\d+) \()?s(?P<s>\d+)e(?P<e>\d+)\)?', first_entry).groupdict()
169 lm = re.match(r'((?P<abs>\d+) \()?s(?P<s>\d+)e(?P<e>\d+)\)?', last_entry ).groupdict()
170 episode_missing.append("s{}e{}-{}".format(fm['s'], fm['e'], lm['e']) if fm['abs'] is None else "{}-{} (s{}e{}-{})".format(fm['abs'], lm['abs'], fm['s'], fm['e'], lm['e']))
171 episode_missing_season, episode_missing_season_all = [], True
172
173 # Get the max season number from TVDB API
174 if int(season) > max_season: max_season = int(season)
175
176 ### ep translation [Thetvdb absolute numbering followed, including in specials to stay accurate with scudlee's mapping]
177 anidbid=""
178 abs_number = Dict(episode_json, 'absoluteNumber', default=0 if season=='0' else abs_number+1)
179 if anidb_numbering:
180 if Dict(mappingList, 'defaulttvdbseason_a'): season, episode = '1', str(abs_number)
181 else: season, episode, anidbid = AnimeLists.anidb_ep(mappingList, season, episode)
182 elif metadata_source in ('tvdb3', 'tvdb4'):
183 for s in media.seasons: #if abs id exists on disk, leave specials with no translation
184 if str(abs_number) in list_abs_eps and str(abs_number) in media.seasons[s].episodes and s!="0": season, episode = s, str(abs_number); break
185 elif metadata_source=='tvdb5':
186 if abs_number: season, episode = '1', str(abs_number)
187
188 # Record absolute number mapping for AniDB metadata pull
189 if metadata_source=='tvdb3': SaveDict((str(Dict(episode_json, 'airedSeason')), str(Dict(episode_json, 'airedEpisodeNumber'))), mappingList, 'absolute_map', str(abs_number))
190
191 ### Missing summaries logs ###
192 if Dict(episode_json, 'overview'): summary_present.append(numbering)
193 elif season!='0': summary_missing.append(numbering)
194 else: summary_missing_special.append(numbering)
195
196 ### Check for Missing Episodes ###
197 is_missing = False
198 if (not(str(Dict(episode_json, 'airedSeason'))=='0' and str(Dict(episode_json, 'airedEpisodeNumber')) in list_sp_eps) and
199 not(metadata_source in ('tvdb3', 'tvdb4') and str(abs_number) in list_abs_eps) and
200 not(not movie and season in media.seasons and episode in media.seasons[season].episodes)) or \
201 (not movie and season in media.seasons and episode in media.seasons[season].episodes and
202 anidbid == 'xxxxxxx' and (season, episode) in mapped_episodes):
203 is_missing = True
204 Log.Info('[ ] {:>7} s{:0>2}e{:0>3} anidbid: {:>7} air_date: {}'.format(numbering, season, episode, anidbid, Dict(episode_json, 'firstAired')))
205 air_date = Dict(episode_json, 'firstAired')
206 air_date = int(air_date.replace('-','')) if air_date.replace('-','').isdigit() and int(air_date.replace('-','')) > 10000000 else 99999999
207 if int(time.strftime("%Y%m%d")) <= air_date+1: pass #Log.Info("TVDB - Episode '{}' missing but not aired/missing '{}'".format(numbering, air_date))
208 elif season=='0': tvdb_special_missing.append(episode)
209 elif metadata_source!='tvdb6': episode_missing_season.append( str(abs_number)+" ("+numbering+")" if metadata_source in ('tvdb3', 'tvdb4') else numbering)
210
211 ### File present on disk
212 if not is_missing or Dict(mappingList, 'possible_anidb3') or metadata_source=="tvdb6": # Only pull all if anidb3(tvdb)/anidb4(tvdb6) usage for tvdb ep/season adjustments
213 episode_missing_season_all = False
214 if not is_missing: Log.Info('[X] {:>7} s{:0>2}e{:0>3} anidbid: {:>7} air_date: {} abs_number: {}, title: {}'.format(numbering, season, episode, anidbid, Dict(episode_json, 'firstAired'), abs_number, Dict(episode_json, 'episodeName')))
215 if not anidb_numbering: SaveDict( abs_number, TheTVDB_dict, 'seasons', season, 'episodes', episode, 'absolute_index')
216 SaveDict( Dict(json[lang] , 'rating' ), TheTVDB_dict, 'seasons', season, 'episodes', episode, 'content_rating' )
217 SaveDict( Dict(TheTVDB_dict, 'duration' ), TheTVDB_dict, 'seasons', season, 'episodes', episode, 'duration' )
218 SaveDict( Dict(episode_json, 'firstAired'), TheTVDB_dict, 'seasons', season, 'episodes', episode, 'originally_available_at')
219
220 # Title from serie page
221 rank, title = len(language_episodes)+1, ''
222 if Dict(episode_json, 'episodeName'):
223 rank = language_episodes.index(lang) if lang in language_episodes else len(language_episodes)
224 title = Dict(episode_json, 'episodeName')
225 Log.Info(" - [1] title: [{}] {}".format(language_episodes[rank], title))
226
227 #Summary from serie page
228 if Dict(episode_json, 'overview').strip(" \n\r"):
229 SaveDict( Dict(episode_json, 'overview').strip(" \n\r"), TheTVDB_dict, 'seasons', season, 'episodes', episode, 'summary' )
230 Log.Info(' - [1] summary: [{}] {}'.format(lang, Dict(TheTVDB_dict, 'seasons', season, 'episodes', episode, 'summary' )))
231
232 ### Ep advance information ###
233 ep_count += 1
234 lang2 = 'en' if len(language_episodes)<=1 else language_episodes[1]
235 episode_details_json = Dict(LoadFileTVDB(id=TVDBid, filename='episode_{}_{}.json'.format(Dict(episode_json, 'id'), lang2), url=TVDB_EPISODE_URL.format(id=str(Dict(episode_json, 'id'))), headers={'Accept-Language': lang2}), 'data')
236 if episode_details_json:
237
238 # Std ep info loaded for Library language ten details for 1st language, loading other languages if needed
239 if lang2 in language_episodes and language_episodes.index(lang2)<rank and Dict(episode_details_json, 'language', 'episodeName')==lang2 and Dict(episode_details_json, 'episodeName'):
240 rank = language_episodes.index(lang2)
241 title = Dict(episode_details_json, 'episodeName')
242 Log.Info(" - [2] title: [{}] {}".format(language_episodes[rank], title))
243
244 #Summary
245 if not Dict(TheTVDB_dict, 'seasons', season, 'episodes', episode, 'summary') and Dict(episode_details_json, 'overview'):
246 SaveDict( Dict(episode_details_json, 'overview').strip(" \n\r"), TheTVDB_dict, 'seasons', season, 'episodes', episode, 'summary')
247 Log.Info(' - [2] summary: [{}] {}'.format(lang2, Dict(TheTVDB_dict, 'seasons', season, 'episodes', episode, 'summary' )))
248
249 SaveDict( Dict(episode_details_json, 'writers' ), TheTVDB_dict, 'seasons', season, 'episodes', episode, 'writers' )
250 SaveDict( Dict(episode_details_json, 'directors' ), TheTVDB_dict, 'seasons', season, 'episodes', episode, 'directors' )
251 SaveDict( Dict(episode_details_json, 'siteRating' ), TheTVDB_dict, 'seasons', season, 'episodes', episode, 'rating' )
252 #SaveDict( Dict(episode_details_json, 'guestStars' ), TheTVDB_dict, 'seasons', season, 'episodes', episode, 'guest_stars')
253
254 # Episode screenshot/Thumbnail
255 if Dict(episode_details_json, 'filename'):
256 SaveDict((os.path.join("TheTVDB", "episodes", os.path.basename(Dict(episode_details_json, 'filename'))), 1, None), TheTVDB_dict, 'seasons', season, 'episodes', episode, 'thumbs', str(TVDB_IMG_ROOT+Dict(episode_details_json, 'filename')))
257 Log.Info(' - [ ] thumb: {}'.format(TVDB_IMG_ROOT+Dict(episode_details_json, 'filename') if Dict(episode_details_json, 'filename') else ''))
258
259 #Ep title fallback (first lang title come from ep list, second from ep details)
260 for lang_rank, language in enumerate(language_episodes[2:rank-1] if len(language_episodes)>1 and rank>=2 and not title else []):
261 if not language: continue
262 episode_details_json = Dict(LoadFileTVDB(id=TVDBid, filename='episode_{}_{}.json'.format(Dict(episode_json, 'id'), language), url=TVDB_EPISODE_URL.format(id=str(Dict(episode_json, 'id'))), headers={'Accept-Language': lang}), 'data', default={})
263 if Dict(episode_details_json, 'episodeName') :
264 title = Dict(episode_details_json, 'episodeName')
265 rank = lang_rank
266 Log.Info(" - [3] title: [{}] {}".format(language_episodes[rank], title))
267 if not Dict(TheTVDB_dict, 'seasons', season, 'episodes', episode, 'summary') and Dict(episode_details_json, 'overview'):
268 SaveDict( Dict(episode_details_json, 'overview')[:160].strip(" \n\r"), TheTVDB_dict, 'seasons', season, 'episodes', episode, 'summary')
269 Log.Info(' - [3] summary: [{}] {}'.format(language_episodes[lang_rank], Dict(TheTVDB_dict, 'seasons', season, 'episodes', episode, 'summary' )))
270 if title and Dict(TheTVDB_dict, 'seasons', season, 'episodes', episode, 'summary'): break
271 SaveDict( title, TheTVDB_dict, 'seasons', season, 'episodes', episode, 'title' )
272 SaveDict( rank , TheTVDB_dict, 'seasons', season, 'episodes', episode, 'language_rank')
273
274 # (last season) Replace all the individual episodes reported as missing with a single season 'sX' entry
275 if not episode_missing_season_all: episode_missing.extend(episode_missing_season)
276 elif episode_missing_season:
277 first_entry, last_entry = episode_missing_season[0], episode_missing_season[-1]
278 fm = re.match(r'((?P<abs>\d+) \()?s(?P<s>\d+)e(?P<e>\d+)\)?', first_entry).groupdict()
279 lm = re.match(r'((?P<abs>\d+) \()?s(?P<s>\d+)e(?P<e>\d+)\)?', last_entry ).groupdict()
280 episode_missing.append("s{}e{}-{}".format(fm['s'], fm['e'], lm['e']) if fm['abs'] is None else "{}-{} (s{}e{}-{})".format(fm['abs'], lm['abs'], fm['s'], fm['e'], lm['e']))
281
282 # Set the min/max season to ints & update max value to the next min-1 to handle multi tvdb season anidb entries
283 map_min_values = [int(Dict(mappingList, 'season_map')[x]['min']) for x in Dict(mappingList, 'season_map', default={}) for y in Dict(mappingList, 'season_map')[x] if y=='min']
284 for entry in Dict(mappingList, 'season_map', default={}):
285 entry_min, entry_max = int(mappingList['season_map'][entry]['min']), int(mappingList['season_map'][entry]['max'])
286 while entry_min!=0 and entry_max+1 not in map_min_values and entry_max < max_season: entry_max += 1
287 mappingList['season_map'][entry] = {'min': entry_min, 'max': entry_max}
288 SaveDict(max_season, mappingList, 'season_map', 'max_season')
289
290 ### Logging ###
291 if not movie:
292 if summary_missing: error_log['Missing Episode Summaries'].append("TVDBid: %s | Title: '%s' | Missing Episode Summaries: %s" % (common.WEB_LINK % (common.TVDB_SERIE_URL + TVDBid, TVDBid), Dict(TheTVDB_dict, 'title'), str(summary_missing )))
293 if summary_missing_special: error_log['Missing Special Summaries'].append("TVDBid: %s | Title: '%s' | Missing Special Summaries: %s" % (common.WEB_LINK % (common.TVDB_SERIE_URL + TVDBid, TVDBid), Dict(TheTVDB_dict, 'title'), str(summary_missing_special)))
294 if metadata_source.startswith("tvdb") or metadata_source.startswith("anidb") and not movie and max(map(int, media.seasons.keys()))>1:
295 if episode_missing: error_log['Missing Episodes' ].append("TVDBid: %s | Title: '%s' | Missing Episodes: %s" % (common.WEB_LINK % (common.TVDB_SERIE_URL + TVDBid, TVDBid), Dict(TheTVDB_dict, 'title'), str(episode_missing )))
296 if tvdb_special_missing: error_log['Missing Specials' ].append("TVDBid: %s | Title: '%s' | Missing Specials: %s" % (common.WEB_LINK % (common.TVDB_SERIE_URL + TVDBid, TVDBid), Dict(TheTVDB_dict, 'title'), str(tvdb_special_missing )))
297 #Log.Debug("Episodes without Summary: " + str(sorted(summary_missing, key=common.natural_sort_key)))
298
299 ### Picture types JSON download ###
300 Log.Info("--- images ---".ljust(157, '-'))
301 languages = [language.strip() for language in Prefs['PosterLanguagePriority'].split(',')]
302 Log.Info('languages: {}'.format(languages))
303 for language in languages:
304 try: bannerTypes = Dict(LoadFileTVDB(id=TVDBid, filename='images_{}.json'.format(language), url=TVDB_SERIES_IMG_INFO_URL.format(id=TVDBid), headers={'Accept-Language': language}), 'data', default={})
305 except: Log.Info("Invalid image JSON from url: " + TVDB_SERIES_IMG_INFO_URL % TVDBid)
306 else: #JSON format = {"fanart", "poster", "season", "seasonwide", "series"}
307 metanames = {'fanart': "art", 'poster': "posters", 'series': "banners", 'season': "seasons", 'seasonwide': 'seasonwide'}
308 count_valid = {key: 0 for key in metanames}
309 Log.Info("bannerTypes: {}".format(bannerTypes))
310
311 #Loop per banner type ("fanart", "poster", "season", "series") skip 'seasonwide' - Load bannerType images list JSON
312 for bannerType in bannerTypes:
313 if bannerTypes[bannerType]==0 or bannerType=='seasonwide' or movie and not bannerType in ('fanart', 'poster'): continue #Loop if no images
314 #if anidb_numbering and Dict(mappingList, 'defaulttvdbseason') != '1' and bannerType=='poster': continue #skip if anidb numbered serie mapping to season 0 or 2+
315
316 Log.Info(("--- images.%s ---" % bannerType).ljust(157, '-'))
317 try: images = Dict(LoadFileTVDB(id=TVDBid, filename='images_{}_{}.json'.format(bannerType, language), url=TVDB_SERIES_IMG_QUERY_URL.format(id=TVDBid, type=bannerType), headers={'Accept-Language': language}), 'data', default={})
318 except: images = {}; Log.Info("Bad image type query data for TVDB id: %s (bannerType: %s)" % (TVDBid, bannerType))
319 else:
320 images = sorted(images, key = lambda x: Dict(x, "ratingsInfo", "average", default=0), reverse=True)
321 for image in images: #JSON format = {"data": [{"id", "keyType", "subKey"(season/graphical/text), "fileName", "resolution", "ratingsInfo": {"average", "count"}, "thumbnail"}]}
322 if not Dict(image, 'fileName'): continue #Avod "IOError: [Errno 21] Is a directory: u'/var/lib/plexmediaserver/Library/Application Support/Plex Media Server/Plug-in Support/Data/com.plexapp.agents.hama/DataItems/TheTVDB'" if filename empty
323 count_valid[bannerType] = count_valid[bannerType] + 1
324
325 ### Adding picture ###
326 rank = common.poster_rank('TheTVDB', metanames[bannerType], language, 0 if Dict(image, 'fileName') == Dict(series_images, bannerType) else count_valid[bannerType])
327 fileNamePath = os.path.join('TheTVDB', Dict(image, 'fileName').replace('/', os.sep))
328 fileNameURL = TVDB_IMG_ROOT + Dict(image, 'fileName')
329 thumbnail = TVDB_IMG_ROOT + Dict(image, 'thumbnail') if Dict(image, 'thumbnail') else None
330 subKey = str(Dict(image, 'subKey')) # Convert to string once
331 if bannerType=='season': #tvdb season posters or anidb specials and defaulttvdb season ## season 0 et empty+ season ==defaulttvdbseason(a=1)
332 if not anidb_numbering: SaveDict((fileNamePath, rank, thumbnail), TheTVDB_dict, 'seasons', subKey, 'posters', fileNameURL)
333 else:
334 if subKey == Dict(mappingList, 'defaulttvdbseason'): # If the TVDB season is the AniDB default season, add season poster as series poster
335 SaveDict((fileNamePath, rank, thumbnail), TheTVDB_dict, 'posters', fileNameURL)
336 if subKey in ['0', Dict(mappingList, 'defaulttvdbseason')]: # If the TVDB season is the season 0 OR AniDB default season, add season poster
337 SaveDict((fileNamePath, rank, thumbnail), TheTVDB_dict, 'seasons', '0' if subKey=='0' else '1', 'posters', fileNameURL)
338 else:
339 if anidb_numbering and Dict(mappingList, 'defaulttvdbseason') != '1': rank = rank + 10
340 SaveDict((fileNamePath, rank, thumbnail), TheTVDB_dict, metanames[bannerType], fileNameURL)
341 Log.Info("[!] bannerType: {:>7} subKey: {:>9} rank: {:>3} filename: {} thumbnail: {} resolution: {} average: {} count: {}".format( metanames[bannerType], subKey, rank, fileNameURL, thumbnail, Dict(image, 'resolution'), Dict(image, 'ratingsInfo','average'), Dict(image, 'ratingsInfo', 'count') ))
342
343 #Log.Info("Posters : {}/{}, Season posters: {}/{}, Art: {}/{}".format(count_valid['poster'], Dict(bannerTypes, 'poster'), count_valid['season'], Dict(bannerTypes, 'season') or 0, count_valid['fanart'], Dict(bannerTypes, 'fanart')))
344 if not Dict(bannerTypes, 'poster'): error_log['TVDB posters missing' ].append("TVDBid: %s | Title: '%s'" % (common.WEB_LINK % (common.TVDB_SERIE_URL + TVDBid, TVDBid), Dict(TheTVDB_dict, 'title')))
345 if not Dict(bannerTypes, 'season'): error_log['TVDB season posters missing'].append("TVDBid: %s | Title: '%s'" % (common.WEB_LINK % (common.TVDB_SERIE_URL + TVDBid, TVDBid), Dict(TheTVDB_dict, 'title')))
346
347 Log.Info("--- final summary info ---".ljust(157, '-'))
348 Log.Info("url: '{}', IMDbid: {}, Present episodes: {}, Missing: {}".format(TVDB_SERIES_URL.format(id=TVDBid), IMDbid, ep_count, sorted(episode_missing, key=common.natural_sort_key)))
349
350 Log.Info("--- return ---".ljust(157, '-'))
351 Log.Info("absolute_map: {}".format(DictString(Dict(mappingList, 'absolute_map', default={}), 0)))
352 Log.Info("season_map: {}".format(DictString(Dict(mappingList, 'season_map', default={}), 0)))
353 Log.Info("TheTVDB_dict: {}".format(DictString(TheTVDB_dict, 4)))
354 return TheTVDB_dict, IMDbid
355
356def Search(results, media, lang, manual, movie): #if maxi<50: maxi = tvdb.Search_TVDB(results, media, lang, manual, movie)
357 '''search for TVDB id series
358 '''
359 Log.Info("=== TheTVDB.Search() ===".ljust(157, '='))
360 #series_data = JSON.ObjectFromString(GetResultFromNetwork(TVDB_SEARCH_URL % mediaShowYear, additionalHeaders={'Accept-Language': lang}))['data'][0]
361 orig_title = ( media.title if movie else media.show )
362 maxi = 0
363 try: TVDBsearchXml = XML.ElementFromURL( TVDB_SERIE_SEARCH + quote(orig_title), headers=common.COMMON_HEADERS, cacheTime=CACHE_1HOUR * 24)
364 except Exception as e: Log.Error("TVDB Loading search XML failed, Exception: '%s'" % e)
365 else:
366 for serie in TVDBsearchXml.xpath('Series'):
367 a, b = orig_title, GetXml(serie, 'SeriesName').encode('utf-8') #a, b = cleansedTitle, cleanse_title (serie.xpath('SeriesName')[0].text)
368 if b=='** 403: Series Not Permitted **': continue
369 score = 100 - 100*Util.LevenshteinDistance(a,b) / max(len(a),len(b)) if a!=b else 100
370 if maxi<score: maxi = score
371 Log.Info("TVDB - score: '%3d', id: '%6s', title: '%s'" % (score, GetXml(serie, 'seriesid'), GetXml(serie, 'SeriesName')))
372 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) )
373 return maxi
374