Skip to content

Commit

Permalink
Merge pull request #211 from meisnate12/develop
Browse files Browse the repository at this point in the history
v1.8.0
  • Loading branch information
meisnate12 authored Apr 23, 2021
2 parents d633681 + 342625d commit 3550d17
Show file tree
Hide file tree
Showing 15 changed files with 901 additions and 643 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Plex Meta Manager
#### Version 1.7.2
#### Version 1.8.0

The original concept for Plex Meta Manager is [Plex Auto Collections](https://github.com/mza921/Plex-Auto-Collections), but this is rewritten from the ground up to be able to include a scheduler, metadata edits, multiple libraries, and logging. Plex Meta Manager is a Python 3 script that can be continuously run using YAML configuration files to update on a schedule the metadata of the movies, shows, and collections in your libraries as well as automatically build collections based on various methods all detailed in the wiki. Some collection examples that the script can automatically build and update daily include Plex Based Searches like actor, genre, or studio collections or Collections based on TMDb, IMDb, Trakt, TVDb, AniDB, or MyAnimeList lists and various other services.

Expand Down
7 changes: 3 additions & 4 deletions config/config.yml.template
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
## This file is a template remove the .template to use the file

libraries:
libraries: # Library mappings must have a colon (:) placed after them
Movies:
library_type: movie
TV Shows:
library_type: show
Anime:
library_type: show
settings: # Can be individually specified per library as well
cache: true
cache_expiration: 60
asset_directory: config/assets
asset_folders: true
assets_for_all: false
sync_mode: append
show_unmanaged: true
show_filtered: false
Expand Down
38 changes: 8 additions & 30 deletions modules/anidb.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,21 +16,9 @@ def __init__(self, config):
"popular": "https://anidb.net/latest/anime/popular/?h=1",
"relation": "/relation/graph"
}
self.id_list = html.fromstring(requests.get("https://raw.githubusercontent.com/Anime-Lists/anime-lists/master/anime-list-master.xml").content)

def convert_anidb_to_tvdb(self, anidb_id): return self.convert_anidb(anidb_id, "anidbid", "tvdbid")
def convert_anidb_to_imdb(self, anidb_id): return self.convert_anidb(anidb_id, "anidbid", "imdbid")
def convert_tvdb_to_anidb(self, tvdb_id): return self.convert_anidb(tvdb_id, "tvdbid", "anidbid")
def convert_imdb_to_anidb(self, imdb_id): return self.convert_anidb(imdb_id, "imdbid", "anidbid")
def convert_anidb(self, input_id, from_id, to_id):
ids = self.id_list.xpath(f"//anime[contains(@{from_id}, '{input_id}')]/@{to_id}")
if len(ids) > 0:
if from_id == "tvdbid": return [int(i) for i in ids]
if len(ids[0]) > 0:
try: return ids[0].split(",") if to_id == "imdbid" else int(ids[0])
except ValueError: raise Failed(f"AniDB Error: No {util.pretty_ids[to_id]} ID found for {util.pretty_ids[from_id]} ID: {input_id}")
else: raise Failed(f"AniDB Error: No {util.pretty_ids[to_id]} ID found for {util.pretty_ids[from_id]} ID: {input_id}")
else: raise Failed(f"AniDB Error: {util.pretty_ids[from_id]} ID: {input_id} not found")
def get_AniDB_IDs(self):
return html.fromstring(requests.get("https://raw.githubusercontent.com/Anime-Lists/anime-lists/master/anime-list-master.xml").content)

@retry(stop_max_attempt_number=6, wait_fixed=10000)
def send_request(self, url, language):
Expand Down Expand Up @@ -66,29 +54,19 @@ 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}")
anime_ids = []
anidb_ids = []
if method == "anidb_popular":
if status_message:
logger.info(f"Processing {pretty}: {data} Anime")
anime_ids.extend(self.get_popular(language)[:data])
anidb_ids.extend(self.get_popular(language)[:data])
else:
if status_message: logger.info(f"Processing {pretty}: {data}")
if method == "anidb_id": anime_ids.append(data)
elif method == "anidb_relation": anime_ids.extend(self.get_anidb_relations(data, language))
if method == "anidb_id": anidb_ids.append(data)
elif method == "anidb_relation": anidb_ids.extend(self.get_anidb_relations(data, language))
else: raise Failed(f"AniDB Error: Method {method} not supported")
show_ids = []
movie_ids = []
for anidb_id in anime_ids:
try:
for imdb_id in self.convert_anidb_to_imdb(anidb_id):
tmdb_id, _ = self.config.convert_from_imdb(imdb_id, language)
if tmdb_id: movie_ids.append(tmdb_id)
else: raise Failed
except Failed:
try: show_ids.append(self.convert_anidb_to_tvdb(anidb_id))
except Failed: logger.error(f"AniDB Error: No TVDb ID or IMDb ID found for AniDB ID: {anidb_id}")
movie_ids, show_ids = self.config.Arms.anidb_to_ids(anidb_ids, language)
if status_message:
logger.debug(f"AniDB IDs Found: {anime_ids}")
logger.debug(f"AniDB IDs Found: {anidb_ids}")
logger.debug(f"TMDb IDs Found: {movie_ids}")
logger.debug(f"TVDb IDs Found: {show_ids}")
return movie_ids, show_ids
98 changes: 44 additions & 54 deletions modules/anilist.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,14 +52,14 @@ def send_request(self, query, variables):
return json_obj

def anilist_id(self, anilist_id):
query = "query ($id: Int) {Media(id: $id) {idMal title{romaji english}}}"
query = "query ($id: Int) {Media(id: $id) {id title{romaji english}}}"
media = self.send_request(query, {"id": anilist_id})["data"]["Media"]
if media["idMal"]:
return media["idMal"], media["title"]["english" if media["title"]["english"] else "romaji"]
raise Failed(f"AniList Error: No MyAnimeList ID found for {anilist_id}")
if media["id"]:
return media["id"], media["title"]["english" if media["title"]["english"] else "romaji"]
raise Failed(f"AniList Error: No AniList ID found for {anilist_id}")

def get_pagenation(self, query, limit=0, variables=None):
mal_ids = []
anilist_ids = []
count = 0
page_num = 0
if variables is None:
Expand All @@ -71,21 +71,21 @@ def get_pagenation(self, query, limit=0, variables=None):
json_obj = self.send_request(query, variables)
next_page = json_obj["data"]["Page"]["pageInfo"]["hasNextPage"]
for media in json_obj["data"]["Page"]["media"]:
if media["idMal"]:
mal_ids.append(media["idMal"])
if media["id"]:
anilist_ids.append(media["id"])
count += 1
if 0 < limit == count:
break
if 0 < limit == count:
break
return mal_ids
return anilist_ids

def top_rated(self, limit):
query = """
query ($page: Int) {
Page(page: $page) {
pageInfo {hasNextPage}
media(averageScore_greater: 3, sort: SCORE_DESC, type: ANIME) {idMal}
media(averageScore_greater: 3, sort: SCORE_DESC, type: ANIME) {id}
}
}
"""
Expand All @@ -96,7 +96,7 @@ def popular(self, limit):
query ($page: Int) {
Page(page: $page) {
pageInfo {hasNextPage}
media(popularity_greater: 1000, sort: POPULARITY_DESC, type: ANIME) {idMal}
media(popularity_greater: 1000, sort: POPULARITY_DESC, type: ANIME) {id}
}
}
"""
Expand All @@ -107,7 +107,7 @@ def season(self, season, year, sort, limit):
query ($page: Int, $season: MediaSeason, $year: Int, $sort: [MediaSort]) {
Page(page: $page){
pageInfo {hasNextPage}
media(season: $season, seasonYear: $year, type: ANIME, sort: $sort){idMal}
media(season: $season, seasonYear: $year, type: ANIME, sort: $sort){id}
}
}
"""
Expand All @@ -119,7 +119,7 @@ def genre(self, genre, sort, limit):
query ($page: Int, $genre: String, $sort: [MediaSort]) {
Page(page: $page){
pageInfo {hasNextPage}
media(genre: $genre, sort: $sort){idMal}
media(genre: $genre, sort: $sort){id}
}
}
"""
Expand All @@ -131,7 +131,7 @@ def tag(self, tag, sort, limit):
query ($page: Int, $tag: String, $sort: [MediaSort]) {
Page(page: $page){
pageInfo {hasNextPage}
media(tag: $tag, sort: $sort){idMal}
media(tag: $tag, sort: $sort){id}
}
}
"""
Expand All @@ -144,13 +144,13 @@ def studio(self, studio_id):
Studio(id: $id) {
name
media(page: $page) {
nodes {idMal type}
nodes {id type}
pageInfo {hasNextPage}
}
}
}
"""
mal_ids = []
anilist_ids = []
page_num = 0
next_page = True
name = None
Expand All @@ -161,43 +161,43 @@ def studio(self, studio_id):
name = json_obj["data"]["Studio"]["name"]
next_page = json_obj["data"]["Studio"]["media"]["pageInfo"]["hasNextPage"]
for media in json_obj["data"]["Studio"]["media"]["nodes"]:
if media["idMal"] and media["type"] == "ANIME":
mal_ids.append(media["idMal"])
return mal_ids, name
if media["id"] and media["type"] == "ANIME":
anilist_ids.append(media["id"])
return anilist_ids, name

def relations(self, anilist_id, ignore_ids=None):
query = """
query ($id: Int) {
Media(id: $id) {
idMal
id
relations {
edges {node{id idMal type} relationType}
nodes {id idMal type}
edges {node{id type} relationType}
nodes {id type}
}
}
}
"""
new_anilist_ids = []
anilist_ids = []
mal_ids = []
name = ""
if not ignore_ids:
ignore_ids = [anilist_id]
mal_id, name = self.anilist_id(anilist_id)
mal_ids.append(mal_id)
anilist_id, name = self.anilist_id(anilist_id)
anilist_ids.append(anilist_id)
json_obj = self.send_request(query, {"id": anilist_id})
edges = [media["node"]["id"] for media in json_obj["data"]["Media"]["relations"]["edges"]
if media["relationType"] not in ["CHARACTER", "OTHER"] and media["node"]["type"] == "ANIME"]
for media in json_obj["data"]["Media"]["relations"]["nodes"]:
if media["idMal"] and media["id"] not in ignore_ids and media["id"] in edges and media["type"] == "ANIME":
anilist_ids.append(media["id"])
if media["id"] and media["id"] not in ignore_ids and media["id"] in edges and media["type"] == "ANIME":
new_anilist_ids.append(media["id"])
ignore_ids.append(media["id"])
mal_ids.append(media["idMal"])
anilist_ids.append(media["id"])

for next_id in anilist_ids:
new_mal_ids, ignore_ids, _ = self.relations(next_id, ignore_ids=ignore_ids)
mal_ids.extend(new_mal_ids)
for next_id in new_anilist_ids:
new_relation_ids, ignore_ids, _ = self.relations(next_id, ignore_ids=ignore_ids)
anilist_ids.extend(new_relation_ids)

return mal_ids, ignore_ids, name
return anilist_ids, ignore_ids, name

def validate_genre(self, genre):
if genre.lower() in self.genres:
Expand All @@ -213,7 +213,7 @@ def validate_anilist_ids(self, anilist_ids, studio=False):
anilist_values = []
for anilist_id in anilist_ids:
if studio: query = "query ($id: Int) {Studio(id: $id) {name}}"
else: query = "query ($id: Int) {Media(id: $id) {idMal}}"
else: query = "query ($id: Int) {Media(id: $id) {id}}"
try:
self.send_request(query, {"id": anilist_id})
anilist_values.append(anilist_id)
Expand All @@ -222,51 +222,41 @@ def validate_anilist_ids(self, anilist_ids, studio=False):
return anilist_values
raise Failed(f"AniList Error: No valid AniList IDs in {anilist_ids}")

def get_items(self, method, data, status_message=True):
def get_items(self, method, data, language, status_message=True):
if status_message:
logger.debug(f"Data: {data}")
pretty = util.pretty_names[method] if method in util.pretty_names else method
if method == "anilist_id":
mal_id, name = self.anilist_id(data)
mal_ids = [mal_id]
anilist_id, name = self.anilist_id(data)
anilist_ids = [anilist_id]
if status_message:
logger.info(f"Processing {pretty}: ({data}) {name}")
elif method in ["anilist_popular", "anilist_top_rated"]:
mal_ids = self.popular(data) if method == "anilist_popular" else self.top_rated(data)
anilist_ids = self.popular(data) if method == "anilist_popular" else self.top_rated(data)
if status_message:
logger.info(f"Processing {pretty}: {data} Anime")
elif method == "anilist_season":
mal_ids = self.season(data["season"], data["year"], data["sort_by"], data["limit"])
anilist_ids = self.season(data["season"], data["year"], data["sort_by"], data["limit"])
if status_message:
logger.info(f"Processing {pretty}: {data['limit'] if data['limit'] > 0 else 'All'} Anime from {util.pretty_seasons[data['season']]} {data['year']} sorted by {pretty_names[data['sort_by']]}")
elif method == "anilist_genre":
mal_ids = self.genre(data["genre"], data["sort_by"], data["limit"])
anilist_ids = self.genre(data["genre"], data["sort_by"], data["limit"])
if status_message:
logger.info(f"Processing {pretty}: {data['limit'] if data['limit'] > 0 else 'All'} Anime from the Genre: {data['genre']} sorted by {pretty_names[data['sort_by']]}")
elif method == "anilist_tag":
mal_ids = self.tag(data["tag"], data["sort_by"], data["limit"])
anilist_ids = self.tag(data["tag"], data["sort_by"], data["limit"])
if status_message:
logger.info(f"Processing {pretty}: {data['limit'] if data['limit'] > 0 else 'All'} Anime from the Tag: {data['tag']} sorted by {pretty_names[data['sort_by']]}")
elif method in ["anilist_studio", "anilist_relations"]:
if method == "anilist_studio": mal_ids, name = self.studio(data)
else: mal_ids, _, name = self.relations(data)
if method == "anilist_studio": anilist_ids, name = self.studio(data)
else: anilist_ids, _, name = self.relations(data)
if status_message:
logger.info(f"Processing {pretty}: ({data}) {name} ({len(mal_ids)} Anime)")
logger.info(f"Processing {pretty}: ({data}) {name} ({len(anilist_ids)} Anime)")
else:
raise Failed(f"AniList Error: Method {method} not supported")
show_ids = []
movie_ids = []
for mal_id in mal_ids:
try:
ids = self.config.MyAnimeListIDList.find_mal_ids(mal_id)
if "thetvdb_id" in ids and int(ids["thetvdb_id"]) > 0: show_ids.append(int(ids["thetvdb_id"]))
elif "themoviedb_id" in ids and int(ids["themoviedb_id"]) > 0: movie_ids.append(int(ids["themoviedb_id"]))
else: raise Failed(f"MyAnimeList Error: MyAnimeList ID: {mal_id} has no other IDs associated with it")
except Failed as e:
if status_message:
logger.error(e)
movie_ids, show_ids = self.config.Arms.anilist_to_ids(anilist_ids, language)
if status_message:
logger.debug(f"MyAnimeList IDs Found: {mal_ids}")
logger.debug(f"AniList IDs Found: {anilist_ids}")
logger.debug(f"Shows Found: {show_ids}")
logger.debug(f"Movies Found: {movie_ids}")
return movie_ids, show_ids
Loading

0 comments on commit 3550d17

Please sign in to comment.