Skip to content

Commit

Permalink
Merge pull request #282 from meisnate12/develop
Browse files Browse the repository at this point in the history
v1.10.0
  • Loading branch information
meisnate12 committed May 30, 2021
2 parents d782ff2 + 850bc41 commit e92b7fc
Show file tree
Hide file tree
Showing 12 changed files with 712 additions and 782 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.9.3
#### Version 1.10.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
981 changes: 487 additions & 494 deletions modules/builder.py

Large diffs are not rendered by default.

18 changes: 10 additions & 8 deletions modules/cache.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ def __init__(self, config_path, expiration):
else:
logger.info(f"Using cache database at {cache}")
cursor.execute("DROP TABLE IF EXISTS guids")
cursor.execute("DROP TABLE IF EXISTS imdb_to_tvdb_map")
cursor.execute("DROP TABLE IF EXISTS tmdb_to_tvdb_map")
cursor.execute("DROP TABLE IF EXISTS imdb_map")
cursor.execute(
"""CREATE TABLE IF NOT EXISTS guid_map (
Expand All @@ -34,17 +36,17 @@ def __init__(self, config_path, expiration):
expiration_date TEXT)"""
)
cursor.execute(
"""CREATE TABLE IF NOT EXISTS imdb_to_tvdb_map (
"""CREATE TABLE IF NOT EXISTS imdb_to_tvdb_map2 (
INTEGER PRIMARY KEY,
imdb_id TEXT UNIQUE,
tvdb_id TEXT UNIQUE,
tvdb_id TEXT,
expiration_date TEXT)"""
)
cursor.execute(
"""CREATE TABLE IF NOT EXISTS tmdb_to_tvdb_map (
"""CREATE TABLE IF NOT EXISTS tmdb_to_tvdb_map2 (
INTEGER PRIMARY KEY,
tmdb_id TEXT UNIQUE,
tvdb_id TEXT UNIQUE,
tvdb_id TEXT,
expiration_date TEXT)"""
)
cursor.execute(
Expand Down Expand Up @@ -110,18 +112,18 @@ def update_imdb_to_tmdb_map(self, media_type, expired, imdb_id, tmdb_id):
def query_imdb_to_tvdb_map(self, _id, imdb=True):
from_id = "imdb_id" if imdb else "tvdb_id"
to_id = "tvdb_id" if imdb else "imdb_id"
return self._query_map("imdb_to_tvdb_map", _id, from_id, to_id)
return self._query_map("imdb_to_tvdb_map2", _id, from_id, to_id)

def update_imdb_to_tvdb_map(self, expired, imdb_id, tvdb_id):
self._update_map("imdb_to_tvdb_map", "imdb_id", imdb_id, "tvdb_id", tvdb_id, expired)
self._update_map("imdb_to_tvdb_map2", "imdb_id", imdb_id, "tvdb_id", tvdb_id, expired)

def query_tmdb_to_tvdb_map(self, _id, tmdb=True):
from_id = "tmdb_id" if tmdb else "tvdb_id"
to_id = "tvdb_id" if tmdb else "tmdb_id"
return self._query_map("tmdb_to_tvdb_map", _id, from_id, to_id)
return self._query_map("tmdb_to_tvdb_map2", _id, from_id, to_id)

def update_tmdb_to_tvdb_map(self, expired, tmdb_id, tvdb_id):
self._update_map("tmdb_to_tvdb_map", "tmdb_id", tmdb_id, "tvdb_id", tvdb_id, expired)
self._update_map("tmdb_to_tvdb_map2", "tmdb_id", tmdb_id, "tvdb_id", tvdb_id, expired)

def query_letterboxd_map(self, letterboxd_id):
return self._query_map("letterboxd_map", letterboxd_id, "letterboxd_id", "tmdb_id")
Expand Down
21 changes: 12 additions & 9 deletions modules/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,9 @@ def check_for_attribute(data, attribute, parent=None, test_list=None, default=No
elif data[attribute] is None:
if default_is_none is True: return None
else: message = f"{text} is blank"
elif var_type == "url":
if data[attribute].endswith(("\\", "/")): return data[attribute][:-1]
else: return data[attribute]
elif var_type == "bool":
if isinstance(data[attribute], bool): return data[attribute]
else: message = f"{text} must be either true or false"
Expand Down Expand Up @@ -279,15 +282,15 @@ def check_for_attribute(data, attribute, parent=None, test_list=None, default=No
logger.info("Connecting to Plex Libraries...")

self.general["plex"] = {}
self.general["plex"]["url"] = check_for_attribute(self.data, "url", parent="plex", default_is_none=True)
self.general["plex"]["url"] = check_for_attribute(self.data, "url", parent="plex", var_type="url", default_is_none=True)
self.general["plex"]["token"] = check_for_attribute(self.data, "token", parent="plex", default_is_none=True)
self.general["plex"]["timeout"] = check_for_attribute(self.data, "timeout", parent="plex", var_type="int", default=60)
self.general["plex"]["clean_bundles"] = check_for_attribute(self.data, "clean_bundles", parent="plex", var_type="bool", default=False)
self.general["plex"]["empty_trash"] = check_for_attribute(self.data, "empty_trash", parent="plex", var_type="bool", default=False)
self.general["plex"]["optimize"] = check_for_attribute(self.data, "optimize", parent="plex", var_type="bool", default=False)

self.general["radarr"] = {}
self.general["radarr"]["url"] = check_for_attribute(self.data, "url", parent="radarr", default_is_none=True)
self.general["radarr"]["url"] = check_for_attribute(self.data, "url", parent="radarr", var_type="url", default_is_none=True)
self.general["radarr"]["token"] = check_for_attribute(self.data, "token", parent="radarr", default_is_none=True)
self.general["radarr"]["version"] = check_for_attribute(self.data, "version", parent="radarr", test_list=radarr_versions, default="v3")
self.general["radarr"]["add"] = check_for_attribute(self.data, "add", parent="radarr", var_type="bool", default=False)
Expand All @@ -299,7 +302,7 @@ def check_for_attribute(data, attribute, parent=None, test_list=None, default=No
self.general["radarr"]["search"] = check_for_attribute(self.data, "search", parent="radarr", var_type="bool", default=False)

self.general["sonarr"] = {}
self.general["sonarr"]["url"] = check_for_attribute(self.data, "url", parent="sonarr", default_is_none=True)
self.general["sonarr"]["url"] = check_for_attribute(self.data, "url", parent="sonarr", var_type="url", default_is_none=True)
self.general["sonarr"]["token"] = check_for_attribute(self.data, "token", parent="sonarr", default_is_none=True)
self.general["sonarr"]["version"] = check_for_attribute(self.data, "version", parent="sonarr", test_list=sonarr_versions, default="v3")
self.general["sonarr"]["add"] = check_for_attribute(self.data, "add", parent="sonarr", var_type="bool", default=False)
Expand All @@ -314,7 +317,7 @@ def check_for_attribute(data, attribute, parent=None, test_list=None, default=No
self.general["sonarr"]["cutoff_search"] = check_for_attribute(self.data, "cutoff_search", parent="sonarr", var_type="bool", default=False)

self.general["tautulli"] = {}
self.general["tautulli"]["url"] = check_for_attribute(self.data, "url", parent="tautulli", default_is_none=True)
self.general["tautulli"]["url"] = check_for_attribute(self.data, "url", parent="tautulli", var_type="url", default_is_none=True)
self.general["tautulli"]["apikey"] = check_for_attribute(self.data, "apikey", parent="tautulli", default_is_none=True)

self.libraries = []
Expand Down Expand Up @@ -440,13 +443,13 @@ def check_for_attribute(data, attribute, parent=None, test_list=None, default=No
params["metadata_path"] = [("File", os.path.join(default_dir, f"{library_name}.yml"))]
params["default_dir"] = default_dir
params["plex"] = {}
params["plex"]["url"] = check_for_attribute(lib, "url", parent="plex", default=self.general["plex"]["url"], req_default=True, save=False)
params["plex"]["url"] = check_for_attribute(lib, "url", parent="plex", var_type="url", default=self.general["plex"]["url"], req_default=True, save=False)
params["plex"]["token"] = check_for_attribute(lib, "token", parent="plex", default=self.general["plex"]["token"], req_default=True, save=False)
params["plex"]["timeout"] = check_for_attribute(lib, "timeout", parent="plex", var_type="int", default=self.general["plex"]["timeout"], save=False)
params["plex"]["clean_bundles"] = check_for_attribute(lib, "clean_bundles", parent="plex", var_type="bool", default=self.general["plex"]["clean_bundles"], save=False)
params["plex"]["empty_trash"] = check_for_attribute(lib, "empty_trash", parent="plex", var_type="bool", default=self.general["plex"]["empty_trash"], save=False)
params["plex"]["optimize"] = check_for_attribute(lib, "optimize", parent="plex", var_type="bool", default=self.general["plex"]["optimize"], save=False)
library = PlexAPI(params, self.TMDb, self.TVDb)
library = PlexAPI(params)
logger.info("")
logger.info(f"{display_name} Library Connection Successful")
except Failed as e:
Expand All @@ -462,7 +465,7 @@ def check_for_attribute(data, attribute, parent=None, test_list=None, default=No
logger.info("")
radarr_params = {}
try:
radarr_params["url"] = check_for_attribute(lib, "url", parent="radarr", default=self.general["radarr"]["url"], req_default=True, save=False)
radarr_params["url"] = check_for_attribute(lib, "url", parent="radarr", var_type="url", default=self.general["radarr"]["url"], req_default=True, save=False)
radarr_params["token"] = check_for_attribute(lib, "token", parent="radarr", default=self.general["radarr"]["token"], req_default=True, save=False)
radarr_params["version"] = check_for_attribute(lib, "version", parent="radarr", test_list=radarr_versions, default=self.general["radarr"]["version"], save=False)
radarr_params["add"] = check_for_attribute(lib, "add", parent="radarr", var_type="bool", default=self.general["radarr"]["add"], save=False)
Expand All @@ -486,7 +489,7 @@ def check_for_attribute(data, attribute, parent=None, test_list=None, default=No
logger.info("")
sonarr_params = {}
try:
sonarr_params["url"] = check_for_attribute(lib, "url", parent="sonarr", default=self.general["sonarr"]["url"], req_default=True, save=False)
sonarr_params["url"] = check_for_attribute(lib, "url", parent="sonarr", var_type="url", default=self.general["sonarr"]["url"], req_default=True, save=False)
sonarr_params["token"] = check_for_attribute(lib, "token", parent="sonarr", default=self.general["sonarr"]["token"], req_default=True, save=False)
sonarr_params["version"] = check_for_attribute(lib, "version", parent="sonarr", test_list=sonarr_versions, default=self.general["sonarr"]["version"], save=False)
sonarr_params["add"] = check_for_attribute(lib, "add", parent="sonarr", var_type="bool", default=self.general["sonarr"]["add"], save=False)
Expand Down Expand Up @@ -516,7 +519,7 @@ def check_for_attribute(data, attribute, parent=None, test_list=None, default=No
logger.info("")
tautulli_params = {}
try:
tautulli_params["url"] = check_for_attribute(lib, "url", parent="tautulli", default=self.general["tautulli"]["url"], req_default=True, save=False)
tautulli_params["url"] = check_for_attribute(lib, "url", parent="tautulli", var_type="url", default=self.general["tautulli"]["url"], req_default=True, save=False)
tautulli_params["apikey"] = check_for_attribute(lib, "apikey", parent="tautulli", default=self.general["tautulli"]["apikey"], req_default=True, save=False)
library.Tautulli = TautulliAPI(tautulli_params)
except Failed as e:
Expand Down
8 changes: 4 additions & 4 deletions modules/convert.py
Original file line number Diff line number Diff line change
Expand Up @@ -248,7 +248,7 @@ def imdb_to_tvdb(self, imdb_id, fail=False):
self.config.Cache.update_imdb_to_tvdb_map(expired, imdb_id, tvdb_id)
return tvdb_id

def get_id(self, item, library, length):
def get_id(self, item, library):
expired = None
if self.config.Cache:
cache_id, media_type, expired = self.config.Cache.query_guid_map(item.guid)
Expand Down Expand Up @@ -344,7 +344,7 @@ def get_id(self, item, library, length):
def update_cache(cache_ids, id_type, guid_type):
if self.config.Cache:
cache_ids = util.compile_list(cache_ids)
logger.info(util.adjust_space(length, f" Cache | {'^' if expired else '+'} | {item.guid:<46} | {id_type} ID: {cache_ids:<6} | {item.title}"))
logger.info(util.adjust_space(f" Cache | {'^' if expired else '+'} | {item.guid:<46} | {id_type} ID: {cache_ids:<6} | {item.title}"))
self.config.Cache.update_guid_map(guid_type, item.guid, cache_ids, expired)

if tmdb_id and library.is_movie:
Expand All @@ -359,8 +359,8 @@ def update_cache(cache_ids, id_type, guid_type):
else:
raise Failed(f"No ID to convert")
except Failed as e:
logger.info(util.adjust_space(length, f"Mapping Error | {item.guid:<46} | {e} for {item.title}"))
logger.info(util.adjust_space(f"Mapping Error | {item.guid:<46} | {e} for {item.title}"))
except BadRequest:
util.print_stacktrace()
logger.info(util.adjust_space(length, f"Mapping Error | {item.guid:<46} | Bad Request for {item.title}"))
logger.info(util.adjust_space(f"Mapping Error | {item.guid:<46} | Bad Request for {item.title}"))
return None, None
10 changes: 4 additions & 6 deletions modules/imdb.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,6 @@ def _ids_from_url(self, imdb_url, language, limit):
current_url = self._fix_url(imdb_url)
total, item_count = self._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)
Expand All @@ -74,7 +73,7 @@ def _ids_from_url(self, imdb_url, language, limit):
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}")
util.print_return(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._request(f"{current_url}&page={i}", header)
else:
Expand All @@ -83,7 +82,7 @@ def _ids_from_url(self, imdb_url, language, limit):
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)
util.print_end()
if imdb_ids: return imdb_ids
else: raise Failed(f"IMDb Error: No IMDb IDs Found at {imdb_url}")

Expand Down Expand Up @@ -111,11 +110,10 @@ def run_convert(imdb_id):
logger.info(f"Processing {pretty}: {status}{data['url']}")
imdb_ids = self._ids_from_url(data["url"], language, data["limit"])
total_ids = len(imdb_ids)
length = 0
for i, imdb in enumerate(imdb_ids, 1):
length = util.print_return(length, f"Converting IMDb ID {i}/{total_ids}")
util.print_return(f"Converting IMDb ID {i}/{total_ids}")
run_convert(imdb)
logger.info(util.adjust_space(length, f"Processed {total_ids} IMDb IDs"))
logger.info(util.adjust_space(f"Processed {total_ids} IMDb IDs"))
else:
raise Failed(f"IMDb Error: Method {method} not supported")
logger.debug("")
Expand Down
5 changes: 2 additions & 3 deletions modules/letterboxd.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,10 +49,9 @@ def get_items(self, method, data, language):
items = self._parse_list(data, language)
total_items = len(items)
if total_items > 0:
length = 0
for i, item in enumerate(items, 1):
letterboxd_id, slug = item
length = util.print_return(length, f"Finding TMDb ID {i}/{total_items}")
util.print_return(f"Finding TMDb ID {i}/{total_items}")
tmdb_id = None
expired = None
if self.config.Cache:
Expand All @@ -66,7 +65,7 @@ def get_items(self, method, data, language):
if self.config.Cache:
self.config.Cache.update_letterboxd_map(expired, letterboxd_id, tmdb_id)
movie_ids.append(tmdb_id)
logger.info(util.adjust_space(length, f"Processed {total_items} TMDb IDs"))
logger.info(util.adjust_space(f"Processed {total_items} TMDb IDs"))
else:
logger.error(f"Letterboxd Error: No List Items found in {data}")
logger.debug("")
Expand Down

0 comments on commit e92b7fc

Please sign in to comment.