def get_items(self, method, data, language, status_message=True): pretty = util.pretty_names[ method] if method in util.pretty_names else method movie_ids = [] if status_message: logger.info(f"Processing {pretty}: {data}") items = self.parse_list(data, language) total_items = len(items) if total_items == 0: raise Failed(f"Letterboxd Error: No List Items found in {data}") length = 0 for i, item in enumerate(items, 1): length = util.print_return(length, f"Finding TMDb ID {i}/{total_items}") tmdb_id = None expired = None if self.config.Cache: tmdb_id, expired = self.config.Cache.query_letterboxd_map( item[0]) if not tmdb_id or expired is not False: try: tmdb_id = self.get_tmdb_from_slug(item[1], language) except Failed as e: logger.error(e) continue if self.config.Cache: self.config.Cache.update_letterboxd( expired, item[0], tmdb_id) movie_ids.append(tmdb_id) util.print_end(length, f"Processed {total_items} TMDb IDs") if status_message: logger.debug(f"TMDb IDs Found: {movie_ids}") return movie_ids, []
def map_guids(self, library): movie_map = {} show_map = {} length = 0 count = 0 logger.info("Mapping {} Library: {}".format("Movie" if library.is_movie else "Show", library.name)) items = library.Plex.all() for i, item in enumerate(items, 1): length = util.print_return(length, "Processing: {}/{} {}".format(i, len(items), item.title)) try: id_type, main_id = self.get_id(item, library, length) except BadRequest: util.print_stacktrace() util.print_end(length, "{} {:<46} | {} for {}".format("Cache | ! |" if self.Cache else "Mapping Error:", item.guid, error_message, item.title)) continue if isinstance(main_id, list): if id_type == "movie": for m in main_id: movie_map[m] = item.ratingKey elif id_type == "show": for m in main_id: show_map[m] = item.ratingKey else: if id_type == "movie": movie_map[main_id] = item.ratingKey elif id_type == "show": show_map[main_id] = item.ratingKey util.print_end(length, "Processed {} {}".format(len(items), "Movies" if library.is_movie else "Shows")) return movie_map, show_map
def _genre(self, genre_id, limit): data = self._jiken_request(f"/genre/anime/{genre_id}") if "item_count" not in data: raise Failed( f"MyAnimeList Error: No MyAnimeList IDs for Genre ID: {genre_id}" ) total_items = data["item_count"] if total_items < limit or limit <= 0: limit = total_items mal_ids = [] num_of_pages = math.ceil(int(limit) / 100) current_page = 1 chances = 0 while current_page <= num_of_pages: if chances > 6: logger.debug(data) raise Failed("AniList Error: Connection Failed") start_num = (current_page - 1) * 100 + 1 util.print_return( f"Parsing Page {current_page}/{num_of_pages} {start_num}-{limit if current_page == num_of_pages else current_page * 100}" ) if current_page > 1: data = self._jiken_request( f"/genre/anime/{genre_id}/{current_page}") if "anime" in data: chances = 0 mal_ids.extend([anime["mal_id"] for anime in data["anime"]]) if len(mal_ids) > limit: return mal_ids[:limit] current_page += 1 else: chances += 1 util.print_end() return mal_ids
def get_imdb_ids_from_url(self, imdb_url, language, limit): imdb_url = imdb_url.strip() if not imdb_url.startswith( self.urls["list"]) and not imdb_url.startswith( self.urls["search"]): raise Failed( f"IMDb Error: {imdb_url} must begin with either:\n| {self.urls['list']} (For Lists)\n| {self.urls['search']} (For Searches)" ) if imdb_url.startswith(self.urls["list"]): try: list_id = re.search("(\\d+)", str(imdb_url)).group(1) except AttributeError: raise Failed( f"IMDb Error: Failed to parse List ID from {imdb_url}") current_url = f"{self.urls['search']}lists=ls{list_id}" else: current_url = imdb_url header = {"Accept-Language": language} length = 0 imdb_ids = [] try: results = self.send_request( current_url, header).xpath("//div[@class='desc']/span/text()")[0].replace( ",", "") except IndexError: raise Failed(f"IMDb Error: Failed to parse URL: {imdb_url}") try: total = int(re.findall("(\\d+) title", results)[0]) except IndexError: raise Failed(f"IMDb Error: No Results at URL: {imdb_url}") if "&start=" in current_url: current_url = re.sub("&start=\\d+", "", current_url) if "&count=" in current_url: current_url = re.sub("&count=\\d+", "", current_url) if limit < 1 or total < limit: limit = total remainder = limit % 250 if remainder == 0: remainder = 250 num_of_pages = math.ceil(int(limit) / 250) for i in range(1, num_of_pages + 1): start_num = (i - 1) * 250 + 1 length = util.print_return( length, f"Parsing Page {i}/{num_of_pages} {start_num}-{limit if i == num_of_pages else i * 250}" ) response = self.send_request( f"{current_url}&count={remainder if i == num_of_pages else 250}&start={start_num}", header) imdb_ids.extend( response.xpath( "//div[contains(@class, 'lister-item-image')]//a/img//@data-tconst" )) util.print_end(length) if imdb_ids: return imdb_ids else: raise Failed(f"IMDb Error: No Movies Found at {imdb_url}")
def add_to_collection(self, collection, items, filters, map={}): name = collection.title if isinstance(collection, Collections) else collection collection_items = collection.children if isinstance(collection, Collections) else [] total = len(items) max_length = len(str(total)) length = 0 for i, item in enumerate(items, 1): current = self.get_item(item) match = True if filters: length = util.print_return(length, "Filtering {}/{} {}".format((" " * (max_length - len(str(i)))) + str(i), total, current.title)) for f in filters: modifier = f[0][-4:] method = util.filter_alias[f[0][:-4]] if modifier in [".not", ".lte", ".gte"] else util.filter_alias[f[0]] if method == "max_age": threshold_date = datetime.now() - timedelta(days=f[1]) attr = getattr(current, "originallyAvailableAt") if attr is None or attr < threshold_date: match = False break elif modifier in [".gte", ".lte"]: if method == "originallyAvailableAt": threshold_date = datetime.strptime(f[1], "%m/%d/%y") attr = getattr(current, "originallyAvailableAt") if (modifier == ".lte" and attr > threshold_date) or (modifier == ".gte" and attr < threshold_date): match = False break elif method in ["year", "rating"]: attr = getattr(current, method) if (modifier == ".lte" and attr > f[1]) or (modifier == ".gte" and attr < f[1]): match = False break else: terms = f[1] if isinstance(f[1], list) else str(f[1]).split(", ") if method in ["video_resolution", "audio_language", "subtitle_language"]: for media in current.media: if method == "video_resolution": attrs = [media.videoResolution] for part in media.parts: if method == "audio_language": attrs = ([a.language for a in part.audioStreams()]) if method == "subtitle_language": attrs = ([s.language for s in part.subtitleStreams()]) elif method in ["contentRating", "studio", "year", "rating", "originallyAvailableAt"]: attrs = [str(getattr(current, method))] elif method in ["actors", "countries", "directors", "genres", "writers", "collections"]: attrs = [getattr(x, "tag") for x in getattr(current, method)] if (not list(set(terms) & set(attrs)) and modifier != ".not") or (list(set(terms) & set(attrs)) and modifier == ".not"): match = False break length = util.print_return(length, "Filtering {}/{} {}".format((" " * (max_length - len(str(i)))) + str(i), total, current.title)) if match: util.print_end(length, "{} Collection | {} | {}".format(name, "=" if current in collection_items else "+", current.title)) if current in collection_items: map[current.ratingKey] = None else: current.addCollection(name) media_type = "{}{}".format("Movie" if self.is_movie else "Show", "s" if total > 1 else "") util.print_end(length, "{} {} Processed".format(total, media_type)) return map
def _ids_from_url(self, imdb_url, language, limit): total, item_count = self._total(imdb_url, language) headers = util.header(language) imdb_ids = [] parsed_url = urlparse(imdb_url) params = parse_qs(parsed_url.query) imdb_base = parsed_url._replace(query=None).geturl() params.pop("start", None) # noqa params.pop("count", None) # noqa params.pop("page", None) # noqa if self.config.trace_mode: logger.debug(f"URL: {imdb_base}") logger.debug(f"Params: {params}") search_url = imdb_base.startswith(urls["searches"]) if limit < 1 or total < limit: limit = total remainder = limit % item_count if remainder == 0: remainder = item_count num_of_pages = math.ceil(int(limit) / item_count) for i in range(1, num_of_pages + 1): start_num = (i - 1) * item_count + 1 util.print_return( f"Parsing Page {i}/{num_of_pages} {start_num}-{limit if i == num_of_pages else i * item_count}" ) if search_url: params[ "count"] = remainder if i == num_of_pages else item_count # noqa params["start"] = start_num # noqa else: params["page"] = i # noqa response = self.config.get_html(imdb_base, headers=headers, params=params) ids_found = response.xpath( "//div[contains(@class, 'lister-item-image')]//a/img//@data-tconst" ) if not search_url and i == num_of_pages: ids_found = ids_found[:remainder] imdb_ids.extend(ids_found) time.sleep(2) util.print_end() if len(imdb_ids) > 0: logger.debug(f"{len(imdb_ids)} IMDb IDs Found: {imdb_ids}") return imdb_ids raise Failed(f"IMDb Error: No IMDb IDs Found at {imdb_url}")
def get_imdb_ids_from_url(self, imdb_url, language, limit): current_url = self.fix_url(imdb_url) total, item_count = self.get_total(current_url, language) header = {"Accept-Language": language} length = 0 imdb_ids = [] if "&start=" in current_url: current_url = re.sub("&start=\\d+", "", current_url) if "&count=" in current_url: current_url = re.sub("&count=\\d+", "", current_url) if "&page=" in current_url: current_url = re.sub("&page=\\d+", "", current_url) if limit < 1 or total < limit: limit = total remainder = limit % item_count if remainder == 0: remainder = item_count num_of_pages = math.ceil(int(limit) / item_count) for i in range(1, num_of_pages + 1): start_num = (i - 1) * item_count + 1 length = util.print_return( length, f"Parsing Page {i}/{num_of_pages} {start_num}-{limit if i == num_of_pages else i * item_count}" ) if imdb_url.startswith(self.urls["keyword"]): response = self.send_request(f"{current_url}&page={i}", header) else: response = self.send_request( f"{current_url}&count={remainder if i == num_of_pages else item_count}&start={start_num}", header) if imdb_url.startswith(self.urls["keyword"]) and i == num_of_pages: imdb_ids.extend( response.xpath( "//div[contains(@class, 'lister-item-image')]//a/img//@data-tconst" )[:remainder]) else: imdb_ids.extend( response.xpath( "//div[contains(@class, 'lister-item-image')]//a/img//@data-tconst" )) util.print_end(length) if imdb_ids: return imdb_ids else: raise Failed(f"IMDb Error: No IMDb IDs Found at {imdb_url}")
def get_items(self, method, data, language, status_message=True): pretty = util.pretty_names[ method] if method in util.pretty_names else method if status_message: logger.debug(f"Data: {data}") show_ids = [] movie_ids = [] if method == "imdb_id": if status_message: logger.info(f"Processing {pretty}: {data}") tmdb_id, tvdb_id = self.config.convert_from_imdb(data, language) if tmdb_id: movie_ids.append(tmdb_id) if tvdb_id: show_ids.append(tvdb_id) elif method == "imdb_list": if status_message: status = f"{data['limit']} Items at " if data[ 'limit'] > 0 else '' logger.info(f"Processing {pretty}: {status}{data['url']}") imdb_ids = self.get_imdb_ids_from_url(data["url"], language, data["limit"]) total_ids = len(imdb_ids) length = 0 for i, imdb_id in enumerate(imdb_ids, 1): length = util.print_return( length, f"Converting IMDb ID {i}/{total_ids}") try: tmdb_id, tvdb_id = self.config.convert_from_imdb( imdb_id, language) if tmdb_id: movie_ids.append(tmdb_id) if tvdb_id: show_ids.append(tvdb_id) except Failed as e: logger.warning(e) util.print_end(length, f"Processed {total_ids} IMDb IDs") else: raise Failed(f"IMDb Error: Method {method} not supported") if status_message: logger.debug(f"TMDb IDs Found: {movie_ids}") logger.debug(f"TVDb IDs Found: {show_ids}") return movie_ids, show_ids
def get_items(self, method, data, language, status_message=True): pretty = util.pretty_names[ method] if method in util.pretty_names else method if status_message: logger.debug("Data: {}".format(data)) show_ids = [] movie_ids = [] if method == "imdb_id": if status_message: logger.info("Processing {}: {}".format(pretty, data)) tmdb_id, tvdb_id = self.convert_from_imdb(data, language) if tmdb_id: movie_ids.append(tmdb_id) if tvdb_id: show_ids.append(tvdb_id) elif method == "imdb_list": if status_message: logger.info("Processing {}: {}".format( pretty, "{} Items at {}".format(data["limit"], data["url"]) if data["limit"] > 0 else data["url"])) imdb_ids = self.get_imdb_ids_from_url(data["url"], language, data["limit"]) total_ids = len(imdb_ids) length = 0 for i, imdb_id in enumerate(imdb_ids, 1): length = util.print_return( length, "Converting IMDb ID {}/{}".format(i, total_ids)) try: tmdb_id, tvdb_id = self.convert_from_imdb( imdb_id, language) if tmdb_id: movie_ids.append(tmdb_id) if tvdb_id: show_ids.append(tvdb_id) except Failed as e: logger.warning(e) util.print_end(length, "Processed {} IMDb IDs".format(total_ids)) else: raise Failed("IMDb Error: Method {} not supported".format(method)) if status_message: logger.debug("TMDb IDs Found: {}".format(movie_ids)) logger.debug("TVDb IDs Found: {}".format(show_ids)) return movie_ids, show_ids
def get_items(self, method, data, language, status_message=True): pretty = util.pretty_names[ method] if method in util.pretty_names else method movie_ids = [] if status_message: logger.info(f"Processing {pretty}: {data}") slugs = self.parse_list_for_slugs(data, language) total_slugs = len(slugs) if total_slugs == 0: raise Failed(f"Letterboxd Error: No List Items found in {data}") length = 0 for i, slug in enumerate(slugs, 1): length = util.print_return(length, f"Finding TMDb ID {i}/{total_slugs}") try: movie_ids.append(self.get_tmdb(slug, language)) except Failed as e: logger.error(e) util.print_end(length, f"Processed {total_slugs} TMDb IDs") if status_message: logger.debug(f"TMDb IDs Found: {movie_ids}") return movie_ids, []
def add_to_collection(self, collection, items, filters, show_filtered, rating_key_map, movie_map, show_map): name = collection.title if isinstance(collection, Collections) else collection collection_items = collection.items() if isinstance( collection, Collections) else [] total = len(items) max_length = len(str(total)) length = 0 for i, item in enumerate(items, 1): try: current = self.fetchItem( item.ratingKey if isinstance(item, (Movie, Show)) else int(item)) except (BadRequest, NotFound): logger.error(f"Plex Error: Item {item} not found") continue match = True if filters: length = util.print_return( length, f"Filtering {(' ' * (max_length - len(str(i)))) + str(i)}/{total} {current.title}" ) for filter_method, filter_data in filters: modifier = filter_method[-4:] method = filter_method[:-4] if modifier in [ ".not", ".lte", ".gte" ] else filter_method if method in util.method_alias: method_name = util.method_alias[method] logger.warning( f"Collection Warning: {method} attribute will run as {method_name}" ) else: method_name = method if method_name == "max_age": threshold_date = datetime.now() - timedelta( days=filter_data) if current.originallyAvailableAt is None or current.originallyAvailableAt < threshold_date: match = False break elif method_name == "original_language": movie = None for key, value in movie_map.items(): if current.ratingKey == value: try: movie = self.TMDb.get_movie(key) break except Failed: pass if movie is None: logger.warning( f"Filter Error: No TMDb ID found for {current.title}" ) continue if (modifier == ".not" and movie.original_language in filter_data) or (modifier != ".not" and movie.original_language not in filter_data): match = False break elif method_name == "audio_track_title": jailbreak = False for media in current.media: for part in media.parts: for audio in part.audioStreams(): for check_title in filter_data: title = audio.title if audio.title else "" if check_title.lower() in title.lower( ): jailbreak = True break if jailbreak: break if jailbreak: break if jailbreak: break if (jailbreak and modifier == ".not") or ( not jailbreak and modifier != ".not"): match = False break elif modifier in [".gte", ".lte"]: if method_name == "vote_count": tmdb_item = None for key, value in movie_map.items(): if current.ratingKey == value: try: tmdb_item = self.TMDb.get_movie( key ) if self.is_movie else self.TMDb.get_show( key) break except Failed: pass if tmdb_item is None: logger.warning( f"Filter Error: No TMDb ID found for {current.title}" ) continue attr = tmdb_item.vote_count else: attr = getattr( current, method_name ) / 60000 if method_name == "duration" else getattr( current, method_name) if (modifier == ".lte" and attr > filter_data) or ( modifier == ".gte" and attr < filter_data): match = False break else: attrs = [] if method_name in [ "video_resolution", "audio_language", "subtitle_language" ]: for media in current.media: if method_name == "video_resolution": attrs.extend([media.videoResolution]) for part in media.parts: if method_name == "audio_language": attrs.extend([ a.language for a in part.audioStreams() ]) if method_name == "subtitle_language": attrs.extend([ s.language for s in part.subtitleStreams() ]) elif method_name in [ "contentRating", "studio", "year", "rating", "originallyAvailableAt" ]: attrs = [str(getattr(current, method_name))] elif method_name in [ "actors", "countries", "directors", "genres", "writers", "collections" ]: attrs = [ getattr(x, "tag") for x in getattr(current, method_name) ] if (not list(set(filter_data) & set(attrs)) and modifier != ".not") or ( list(set(filter_data) & set(attrs)) and modifier == ".not"): match = False break length = util.print_return( length, f"Filtering {(' ' * (max_length - len(str(i)))) + str(i)}/{total} {current.title}" ) if match: util.print_end( length, f"{name} Collection | {'=' if current in collection_items else '+'} | {current.title}" ) if current in collection_items: rating_key_map[current.ratingKey] = None else: current.addCollection(name) elif show_filtered is True: logger.info(f"{name} Collection | X | {current.title}") media_type = f"{'Movie' if self.is_movie else 'Show'}{'s' if total > 1 else ''}" util.print_end(length, f"{total} {media_type} Processed") return rating_key_map
def get_items(self, method, data, status_message=True): if status_message: logger.debug(f"Data: {data}") pretty = util.pretty_names[method] if method in util.pretty_names else method media_type = "Movie" if self.is_movie else "Show" items = [] if method == "plex_all": if status_message: logger.info(f"Processing {pretty} {media_type}s") items = self.get_all() elif method == "plex_collection": if status_message: logger.info(f"Processing {pretty} {data}") items = data.items() elif method == "plex_search": search_terms = {} has_processed = False search_limit = None search_sort = None for search_method, search_data in data.items(): if search_method == "limit": search_limit = search_data elif search_method == "sort_by": search_sort = search_data else: search, modifier = os.path.splitext(str(search_method).lower()) final_search = search_translation[search] if search in search_translation else search if search in ["added", "originally_available"] and modifier == "": final_mod = ">>" elif search in ["added", "originally_available"] and modifier == ".not": final_mod = "<<" elif search in ["critic_rating", "audience_rating"] and modifier == ".greater": final_mod = "__gte" elif search in ["critic_rating", "audience_rating"] and modifier == ".less": final_mod = "__lt" else: final_mod = modifiers[modifier] if modifier in modifiers else "" final_method = f"{final_search}{final_mod}" if search == "duration": search_terms[final_method] = search_data * 60000 elif search in ["added", "originally_available"] and modifier in ["", ".not"]: search_terms[final_method] = f"{search_data}d" else: search_terms[final_method] = search_data if status_message: if search in ["added", "originally_available"] or modifier in [".greater", ".less", ".before", ".after"]: ors = f"{search_method}({search_data}" else: ors = "" conjunction = " AND " if final_mod == "&" else " OR " for o, param in enumerate(search_data): or_des = conjunction if o > 0 else f"{search_method}(" ors += f"{or_des}{param}" if has_processed: logger.info(f"\t\t AND {ors})") else: logger.info(f"Processing {pretty}: {ors})") has_processed = True if status_message: if search_sort: logger.info(f"\t\t SORT BY {search_sort})") if search_limit: logger.info(f"\t\t LIMIT {search_limit})") logger.debug(f"Search: {search_terms}") return self.search(sort=sorts[search_sort], maxresults=search_limit, **search_terms) elif method == "plex_collectionless": good_collections = [] for col in self.get_all_collections(): keep_collection = True for pre in data["exclude_prefix"]: if col.title.startswith(pre) or (col.titleSort and col.titleSort.startswith(pre)): keep_collection = False break if keep_collection: for ext in data["exclude"]: if col.title == ext or (col.titleSort and col.titleSort == ext): keep_collection = False break if keep_collection: good_collections.append(col.index) all_items = self.get_all() length = 0 for i, item in enumerate(all_items, 1): length = util.print_return(length, f"Processing: {i}/{len(all_items)} {item.title}") add_item = True item.reload() for collection in item.collections: if collection.id in good_collections: add_item = False break if add_item: items.append(item) util.print_end(length, f"Processed {len(all_items)} {'Movies' if self.is_movie else 'Shows'}") else: raise Failed(f"Plex Error: Method {method} not supported") if len(items) > 0: return items else: raise Failed("Plex Error: No Items found in Plex")
def get_imdb_ids_from_url(self, imdb_url, language, limit): imdb_url = imdb_url.strip() if not imdb_url.startswith( "https://www.imdb.com/list/ls") and not imdb_url.startswith( "https://www.imdb.com/search/title/?"): raise Failed( "IMDb Error: {} must begin with either:\n| https://www.imdb.com/list/ls (For Lists)\n| https://www.imdb.com/search/title/? (For Searches)" .format(imdb_url)) if imdb_url.startswith("https://www.imdb.com/list/ls"): try: list_id = re.search("(\\d+)", str(imdb_url)).group(1) except AttributeError: raise Failed( "IMDb Error: Failed to parse List ID from {}".format( imdb_url)) current_url = "https://www.imdb.com/search/title/?lists=ls{}".format( list_id) else: current_url = imdb_url header = {"Accept-Language": language} length = 0 imdb_ids = [] try: results = self.send_request( current_url, header).xpath("//div[@class='desc']/span/text()")[0].replace( ",", "") except IndexError: raise Failed( "IMDb Error: Failed to parse URL: {}".format(imdb_url)) try: total = int(re.findall("(\\d+) title", results)[0]) except IndexError: raise Failed("IMDb Error: No Results at URL: {}".format(imdb_url)) if "&start=" in current_url: current_url = re.sub("&start=\d+", "", current_url) if "&count=" in current_url: current_url = re.sub("&count=\d+", "", current_url) if limit < 1 or total < limit: limit = total remainder = limit % 250 if remainder == 0: remainder = 250 num_of_pages = math.ceil(int(limit) / 250) for i in range(1, num_of_pages + 1): start_num = (i - 1) * 250 + 1 length = util.print_return( length, "Parsing Page {}/{} {}-{}".format( i, num_of_pages, start_num, limit if i == num_of_pages else i * 250)) response = self.send_request( "{}&count={}&start={}".format( current_url, remainder if i == num_of_pages else 250, start_num), header) imdb_ids.extend( response.xpath( "//div[contains(@class, 'lister-item-image')]//a/img//@data-tconst" )) util.print_end(length) if imdb_ids: return imdb_ids else: raise Failed("IMDb Error: No Movies Found at {}".format(imdb_url))
def get_id(self, item, library, length): expired = None tmdb_id = None imdb_id = None tvdb_id = None anidb_id = None mal_id = None error_message = None if self.Cache: if library.is_movie: tmdb_id, expired = self.Cache.get_tmdb_id("movie", plex_guid=item.guid) else: tvdb_id, expired = self.Cache.get_tvdb_id("show", plex_guid=item.guid) if not tvdb_id and library.is_show: tmdb_id, expired = self.Cache.get_tmdb_id("show", plex_guid=item.guid) anidb_id, expired = self.Cache.get_anidb_id("show", plex_guid=item.guid) if expired or (not tmdb_id and library.is_movie) or (not tvdb_id and not tmdb_id and library.is_show): guid = requests.utils.urlparse(item.guid) item_type = guid.scheme.split(".")[-1] check_id = guid.netloc if item_type == "plex" and library.is_movie: for guid_tag in item.guids: url_parsed = requests.utils.urlparse(guid_tag.id) if url_parsed.scheme == "tmdb": tmdb_id = int(url_parsed.netloc) elif url_parsed.scheme == "imdb": imdb_id = url_parsed.netloc elif item_type == "imdb": imdb_id = check_id elif item_type == "thetvdb": tvdb_id = int(check_id) elif item_type == "themoviedb": tmdb_id = int(check_id) elif item_type == "hama": if check_id.startswith("tvdb"): tvdb_id = int(re.search("-(.*)", check_id).group(1)) elif check_id.startswith("anidb"): anidb_id = re.search("-(.*)", check_id).group(1) else: error_message = "Hama Agent ID: {} not supported".format(check_id) elif item_type == "myanimelist": mal_id = check_id elif item_type == "local": error_message = "No match in Plex" else: error_message = "Agent {} not supported".format(item_type) if not error_message: if anidb_id and not tvdb_id: try: tvdb_id = self.AniDB.convert_anidb_to_tvdb(anidb_id) except Failed: pass if anidb_id and not imdb_id: try: imdb_id = self.AniDB.convert_anidb_to_imdb(anidb_id) except Failed: pass if mal_id: try: ids = self.MyAnimeListIDList.find_mal_ids(mal_id) if "thetvdb_id" in ids and int(ids["thetvdb_id"]) > 0: tvdb_id = int(ids["thetvdb_id"]) elif "themoviedb_id" in ids and int(ids["themoviedb_id"]) > 0: tmdb_id = int(ids["themoviedb_id"]) else: raise Failed("MyAnimeList Error: MyAnimeList ID: {} has no other IDs associated with it".format(mal_id)) except Failed: pass if mal_id and not tvdb_id: try: tvdb_id = self.MyAnimeListIDList.convert_mal_to_tvdb(mal_id) except Failed: pass if mal_id and not tmdb_id: try: tmdb_id = self.MyAnimeListIDList.convert_mal_to_tmdb(mal_id) except Failed: pass if not tmdb_id and imdb_id and isinstance(imdb_id, list) and self.TMDb: tmdb_id = [] new_imdb_id = [] for imdb in imdb_id: try: temp_tmdb_id = self.TMDb.convert_imdb_to_tmdb(imdb) tmdb_id.append(temp_tmdb_id) new_imdb_id.append(imdb) except Failed: continue imdb_id = new_imdb_id if not tmdb_id and imdb_id and self.TMDb: try: tmdb_id = self.TMDb.convert_imdb_to_tmdb(imdb_id) except Failed: pass if not tmdb_id and imdb_id and self.Trakt: try: tmdb_id = self.Trakt.convert_imdb_to_tmdb(imdb_id) except Failed: pass if not tmdb_id and tvdb_id and self.TMDb: try: tmdb_id = self.TMDb.convert_tvdb_to_tmdb(tvdb_id) except Failed: pass if not tmdb_id and tvdb_id and self.Trakt: try: tmdb_id = self.Trakt.convert_tvdb_to_tmdb(tvdb_id) except Failed: pass if not imdb_id and tmdb_id and self.TMDb: try: imdb_id = self.TMDb.convert_tmdb_to_imdb(tmdb_id) except Failed: pass if not imdb_id and tmdb_id and self.Trakt: try: imdb_id = self.Trakt.convert_tmdb_to_imdb(tmdb_id) except Failed: pass if not imdb_id and tvdb_id and self.Trakt: try: imdb_id = self.Trakt.convert_tmdb_to_imdb(tmdb_id) except Failed: pass if not tvdb_id and tmdb_id and self.TMDb and library.is_show: try: tvdb_id = self.TMDb.convert_tmdb_to_tvdb(tmdb_id) except Failed: pass if not tvdb_id and tmdb_id and self.Trakt and library.is_show: try: tvdb_id = self.Trakt.convert_tmdb_to_tvdb(tmdb_id) except Failed: pass if not tvdb_id and imdb_id and self.Trakt and library.is_show: try: tvdb_id = self.Trakt.convert_imdb_to_tvdb(imdb_id) except Failed: pass if (not tmdb_id and library.is_movie) or (not tvdb_id and not ((anidb_id or mal_id) and tmdb_id) and library.is_show): service_name = "TMDb ID" if library.is_movie else "TVDb ID" if self.TMDb and self.Trakt: api_name = "TMDb or Trakt" elif self.TMDb: api_name = "TMDb" elif self.Trakt: api_name = "Trakt" else: api_name = None if tmdb_id and imdb_id: id_name = "TMDb ID: {} or IMDb ID: {}".format(tmdb_id, imdb_id) elif imdb_id and tvdb_id: id_name = "IMDb ID: {} or TVDb ID: {}".format(imdb_id, tvdb_id) elif tmdb_id: id_name = "TMDb ID: {}".format(tmdb_id) elif imdb_id: id_name = "IMDb ID: {}".format(imdb_id) elif tvdb_id: id_name = "TVDb ID: {}".format(tvdb_id) else: id_name = None if anidb_id and not tmdb_id and not tvdb_id: error_message = "Unable to convert AniDb ID: {} to TMDb ID or TVDb ID".format(anidb_id) elif mal_id and not tmdb_id and not tvdb_id: error_message = "Unable to convert MyAnimeList ID: {} to TMDb ID or TVDb ID".format(mal_id) elif id_name and api_name: error_message = "Unable to convert {} to {} using {}".format(id_name, service_name, api_name) elif id_name: error_message = "Configure TMDb or Trakt to covert {} to {}".format(id_name, service_name) else: error_message = "No ID to convert to {}".format(service_name) if self.Cache and (tmdb_id and library.is_movie) or ((tvdb_id or ((anidb_id or mal_id) and tmdb_id)) and library.is_show): if isinstance(tmdb_id, list): for i in range(len(tmdb_id)): util.print_end(length, "Cache | {} | {:<46} | {:<6} | {:<10} | {:<6} | {:<5} | {:<5} | {}".format("^" if expired is True else "+", item.guid, tmdb_id[i] if tmdb_id[i] else "None", imdb_id[i] if imdb_id[i] else "None", tvdb_id if tvdb_id else "None", anidb_id if anidb_id else "None", mal_id if mal_id else "None", item.title)) self.Cache.update_guid("movie" if library.is_movie else "show", item.guid, tmdb_id[i], imdb_id[i], tvdb_id, anidb_id, mal_id, expired) else: util.print_end(length, "Cache | {} | {:<46} | {:<6} | {:<10} | {:<6} | {:<5} | {:<5} | {}".format("^" if expired is True else "+", item.guid, tmdb_id if tmdb_id else "None", imdb_id if imdb_id else "None", tvdb_id if tvdb_id else "None", anidb_id if anidb_id else "None", mal_id if mal_id else "None", item.title)) self.Cache.update_guid("movie" if library.is_movie else "show", item.guid, tmdb_id, imdb_id, tvdb_id, anidb_id, mal_id, expired) if tmdb_id and library.is_movie: return "movie", tmdb_id elif tvdb_id and library.is_show: return "show", tvdb_id elif (anidb_id or mal_id) and tmdb_id: return "movie", tmdb_id else: util.print_end(length, "{} {:<46} | {} for {}".format("Cache | ! |" if self.Cache else "Mapping Error:", item.guid, error_message, item.title)) return None, None
def add_to_collection(self, collection, items, filters, show_filtered, map, movie_map, show_map): name = collection.title if isinstance(collection, Collections) else collection collection_items = collection.items() if isinstance( collection, Collections) else [] total = len(items) max_length = len(str(total)) length = 0 for i, item in enumerate(items, 1): try: current = self.fetchItem( item.ratingKey if isinstance(item, (Movie, Show)) else int(item)) except (BadRequest, NotFound): logger.error("Plex Error: Item {} not found".format(item)) continue match = True if filters: length = util.print_return( length, "Filtering {}/{} {}".format( (" " * (max_length - len(str(i)))) + str(i), total, current.title)) for f in filters: modifier = f[0][-4:] method = util.filter_alias[f[0][:-4]] if modifier in [ ".not", ".lte", ".gte" ] else util.filter_alias[f[0]] if method == "max_age": threshold_date = datetime.now() - timedelta(days=f[1]) attr = getattr(current, "originallyAvailableAt") if attr is None or attr < threshold_date: match = False break elif method == "original_language": terms = util.get_list(f[1], lower=True) tmdb_id = None movie = None for key, value in movie_map.items(): if current.ratingKey == value: try: movie = self.TMDb.get_movie(key) break except Failed: pass if movie is None: logger.warning( "Filter Error: No TMDb ID found for {}".format( current.title)) continue if (modifier == ".not" and movie.original_language in terms) or ( modifier != ".not" and movie.original_language not in terms): match = False break elif modifier in [".gte", ".lte"]: if method == "originallyAvailableAt": threshold_date = datetime.strptime( f[1], "%m/%d/%y") attr = getattr(current, "originallyAvailableAt") if (modifier == ".lte" and attr > threshold_date ) or (modifier == ".gte" and attr < threshold_date): match = False break elif method in ["year", "rating"]: attr = getattr(current, method) if (modifier == ".lte" and attr > f[1]) or (modifier == ".gte" and attr < f[1]): match = False break else: terms = util.get_list(f[1]) if method in [ "video_resolution", "audio_language", "subtitle_language" ]: for media in current.media: if method == "video_resolution": attrs = [media.videoResolution] for part in media.parts: if method == "audio_language": attrs = ([ a.language for a in part.audioStreams() ]) if method == "subtitle_language": attrs = ([ s.language for s in part.subtitleStreams() ]) elif method in [ "contentRating", "studio", "year", "rating", "originallyAvailableAt" ]: attrs = [str(getattr(current, method))] elif method in [ "actors", "countries", "directors", "genres", "writers", "collections" ]: attrs = [ getattr(x, "tag") for x in getattr(current, method) ] if (not list(set(terms) & set(attrs)) and modifier != ".not") or (list(set(terms) & set(attrs)) and modifier == ".not"): match = False break length = util.print_return( length, "Filtering {}/{} {}".format( (" " * (max_length - len(str(i)))) + str(i), total, current.title)) if match: util.print_end( length, "{} Collection | {} | {}".format( name, "=" if current in collection_items else "+", current.title)) if current in collection_items: map[current.ratingKey] = None else: current.addCollection(name) elif show_filtered is True: logger.info("{} Collection | X | {}".format( name, current.title)) media_type = "{}{}".format("Movie" if self.is_movie else "Show", "s" if total > 1 else "") util.print_end(length, "{} {} Processed".format(total, media_type)) return map
def update_libraries(config): global stats for library in config.libraries: try: os.makedirs(os.path.join(default_dir, "logs", library.mapping_name, "collections"), exist_ok=True) col_file_logger = os.path.join(default_dir, "logs", library.mapping_name, "library.log") should_roll_over = os.path.isfile(col_file_logger) library_handler = RotatingFileHandler(col_file_logger, delay=True, mode="w", backupCount=3, encoding="utf-8") util.apply_formatter(library_handler) if should_roll_over: library_handler.doRollover() logger.addHandler(library_handler) plexapi.server.TIMEOUT = library.timeout logger.info("") util.separator(f"{library.name} Library") logger.debug("") logger.debug(f"Mapping Name: {library.original_mapping_name}") logger.debug(f"Folder Name: {library.mapping_name}") logger.debug(f"Missing Path: {library.missing_path}") for ad in library.asset_directory: logger.debug(f"Asset Directory: {ad}") logger.debug(f"Asset Folders: {library.asset_folders}") logger.debug(f"Create Asset Folders: {library.create_asset_folders}") logger.debug(f"Sync Mode: {library.sync_mode}") logger.debug(f"Collection Minimum: {library.collection_minimum}") logger.debug(f"Delete Below Minimum: {library.delete_below_minimum}") logger.debug(f"Delete Not Scheduled: {library.delete_not_scheduled}") logger.debug(f"Missing Only Released: {library.missing_only_released}") logger.debug(f"Only Filter Missing: {library.only_filter_missing}") logger.debug(f"Show Unmanaged: {library.show_unmanaged}") logger.debug(f"Show Filtered: {library.show_filtered}") logger.debug(f"Show Missing: {library.show_missing}") logger.debug(f"Show Missing Assets: {library.show_missing_assets}") logger.debug(f"Save Missing: {library.save_missing}") logger.debug(f"Assets For All: {library.assets_for_all}") logger.debug(f"Delete Collections With Less: {library.delete_collections_with_less}") logger.debug(f"Delete Unmanaged Collections: {library.delete_unmanaged_collections}") logger.debug(f"Mass Genre Update: {library.mass_genre_update}") logger.debug(f"Mass Audience Rating Update: {library.mass_audience_rating_update}") logger.debug(f"Mass Critic Rating Update: {library.mass_critic_rating_update}") logger.debug(f"Mass Trakt Rating Update: {library.mass_trakt_rating_update}") logger.debug(f"Split Duplicates: {library.split_duplicates}") logger.debug(f"Radarr Add All: {library.radarr_add_all}") logger.debug(f"Sonarr Add All: {library.sonarr_add_all}") logger.debug(f"TMDb Collections: {library.tmdb_collections}") logger.debug(f"Genre Mapper: {library.genre_mapper}") logger.debug(f"Clean Bundles: {library.clean_bundles}") logger.debug(f"Empty Trash: {library.empty_trash}") logger.debug(f"Optimize: {library.optimize}") logger.debug(f"Timeout: {library.timeout}") if not library.is_other: logger.info("") util.separator(f"Mapping {library.name} Library", space=False, border=False) logger.info("") library.map_guids() for metadata in library.metadata_files: logger.info("") util.separator(f"Running Metadata File\n{metadata.path}") if not config.test_mode and not config.resume_from and not collection_only: try: metadata.update_metadata() except Failed as e: library.notify(e) logger.error(e) collections_to_run = metadata.get_collections(config.requested_collections) if config.resume_from and config.resume_from not in collections_to_run: logger.info("") logger.warning(f"Collection: {config.resume_from} not in Metadata File: {metadata.path}") continue if collections_to_run and not library_only: logger.info("") util.separator(f"{'Test ' if config.test_mode else ''}Collections") logger.removeHandler(library_handler) run_collection(config, library, metadata, collections_to_run) logger.addHandler(library_handler) if library.run_sort: logger.info("") util.separator(f"Sorting {library.name} Library's Collections", space=False, border=False) logger.info("") for builder in library.run_sort: logger.info("") util.separator(f"Sorting {builder.name} Collection", space=False, border=False) logger.info("") builder.sort_collection() if not config.test_mode and not collection_only: library_operations(config, library) logger.removeHandler(library_handler) except Exception as e: library.notify(e) util.print_stacktrace() util.print_multiline(e, critical=True) has_run_again = False for library in config.libraries: if library.run_again: has_run_again = True break if has_run_again and not library_only: logger.info("") util.separator("Run Again") logger.info("") for x in range(1, config.general["run_again_delay"] + 1): util.print_return(f"Waiting to run again in {config.general['run_again_delay'] - x + 1} minutes") for y in range(60): time.sleep(1) util.print_end() for library in config.libraries: if library.run_again: try: col_file_logger = os.path.join(default_dir, "logs", library.mapping_name, f"library.log") library_handler = RotatingFileHandler(col_file_logger, mode="w", backupCount=3, encoding="utf-8") util.apply_formatter(library_handler) logger.addHandler(library_handler) library_handler.addFilter(fmt_filter) os.environ["PLEXAPI_PLEXAPI_TIMEOUT"] = str(library.timeout) logger.info("") util.separator(f"{library.name} Library Run Again") logger.info("") library.map_guids() for builder in library.run_again: logger.info("") util.separator(f"{builder.name} Collection") logger.info("") try: builder.run_collections_again() except Failed as e: library.notify(e, collection=builder.name, critical=False) util.print_stacktrace() util.print_multiline(e, error=True) logger.removeHandler(library_handler) except Exception as e: library.notify(e) util.print_stacktrace() util.print_multiline(e, critical=True) used_url = [] for library in config.libraries: if library.url not in used_url: used_url.append(library.url) if library.empty_trash: library.query(library.PlexServer.library.emptyTrash) if library.clean_bundles: library.query(library.PlexServer.library.cleanBundles) if library.optimize: library.query(library.PlexServer.library.optimize)