This commit is contained in:
meisnate12 2021-12-13 02:30:19 -05:00
parent af725b8672
commit 26779c566d
9 changed files with 342 additions and 295 deletions

View file

@ -12,7 +12,7 @@ The original concept for Plex Meta Manager is [Plex Auto Collections](https://gi
The script can update many metadata fields for movies, shows, collections, seasons, and episodes and can act as a backup if your plex DB goes down. It can even update metadata the plex UI can't like Season Names. If the time is put into the metadata configuration file you can have a way to recreate your library and all its metadata changes with the click of a button.
The script works with most Metadata agents including the new Plex Movie Agent, New Plex TV Agent, [Hama Anime Agent](https://github.com/ZeroQI/Hama.bundle), and [MyAnimeList Anime Agent](https://github.com/Fribb/MyAnimeList.bundle).
The script works with most Metadata agents including the New Plex Movie Agent, New Plex TV Agent, [Hama Anime Agent](https://github.com/ZeroQI/Hama.bundle), [MyAnimeList Anime Agent](https://github.com/Fribb/MyAnimeList.bundle), and [XBMC NFO Movie and TV Agents](https://github.com/gboudreau/XBMCnfoMoviesImporter.bundle).
## Getting Started

File diff suppressed because it is too large Load diff

View file

@ -31,7 +31,7 @@ logger = logging.getLogger("Plex Meta Manager")
sync_modes = {"append": "Only Add Items to the Collection", "sync": "Add & Remove Items from the Collection"}
mass_update_options = {"tmdb": "Use TMDb Metadata", "omdb": "Use IMDb Metadata through OMDb"}
class Config:
class ConfigFile:
def __init__(self, default_dir, attrs):
logger.info("Locating config...")
config_file = attrs["config_file"]

View file

@ -37,12 +37,31 @@ class IMDb:
if not isinstance(imdb_dict, dict):
imdb_dict = {"url": imdb_dict}
dict_methods = {dm.lower(): dm for dm in imdb_dict}
imdb_url = util.parse("url", imdb_dict, methods=dict_methods, parent="imdb_list").strip()
if "url" not in dict_methods:
raise Failed(f"Collection Error: imdb_list url attribute not found")
elif imdb_dict[dict_methods["url"]] is None:
raise Failed(f"Collection Error: imdb_list url attribute is blank")
else:
imdb_url = imdb_dict[dict_methods["url"]].strip()
if not imdb_url.startswith(tuple([v for k, v in urls.items()])):
fails = "\n".join([f"{v} (For {k.replace('_', ' ').title()})" for k, v in urls.items()])
raise Failed(f"IMDb Error: {imdb_url} must begin with either:{fails}")
self._total(imdb_url, language)
list_count = util.parse("limit", imdb_dict, datatype="int", methods=dict_methods, default=0, parent="imdb_list", minimum=0) if "limit" in dict_methods else 0
list_count = None
if "limit" in dict_methods:
if imdb_dict[dict_methods["limit"]] is None:
logger.warning(f"Collection Warning: imdb_list limit attribute is blank using 0 as default")
else:
try:
value = int(str(imdb_dict[dict_methods["limit"]]))
if 0 <= value:
list_count = value
except ValueError:
pass
if list_count is None:
logger.warning(f"Collection Warning: imdb_list limit attribute must be an integer 0 or greater using 0 as default")
if list_count is None:
list_count = 0
valid_lists.append({"url": imdb_url, "limit": list_count})
return valid_lists

View file

@ -1,7 +1,7 @@
import logging, os, requests, shutil, time
from abc import ABC, abstractmethod
from modules import util
from modules.meta import Metadata
from modules.meta import MetadataFile
from modules.util import Failed, ImageData
from PIL import Image
from ruamel import yaml
@ -92,7 +92,7 @@ class Library(ABC):
metadata.append((file_type, metadata_file))
for file_type, metadata_file in metadata:
try:
meta_obj = Metadata(config, self, file_type, metadata_file)
meta_obj = MetadataFile(config, self, file_type, metadata_file)
if meta_obj.collections:
self.collections.extend([c for c in meta_obj.collections])
if meta_obj.metadata:

View file

@ -9,7 +9,7 @@ logger = logging.getLogger("Plex Meta Manager")
github_base = "https://raw.githubusercontent.com/meisnate12/Plex-Meta-Manager-Configs/master/"
class Metadata:
class MetadataFile:
def __init__(self, config, library, file_type, path):
self.config = config
self.library = library
@ -22,11 +22,15 @@ class Metadata:
if attr_data[attribute]:
if isinstance(attr_data[attribute], dict):
new_dict = {}
for a_name, a_data in attr_data[attribute].items():
if a_name in check_list:
logger.error(f"Config Warning: Skipping duplicate {attribute[:-1] if attribute[-1] == 's' else attribute}: {a_name}")
for _name, _data in attr_data[attribute].items():
if _name in check_list:
logger.error(f"Config Warning: Skipping duplicate {attribute[:-1] if attribute[-1] == 's' else attribute}: {_name}")
elif _data is None:
logger.error(f"Config Warning: {attribute[:-1] if attribute[-1] == 's' else attribute}: {_name} has no data")
elif not isinstance(_data, dict):
logger.error(f"Config Warning: {attribute[:-1] if attribute[-1] == 's' else attribute}: {_name} must be a dictionary")
else:
new_dict[str(a_name)] = a_data
new_dict[str(_name)] = _data
return new_dict
else:
logger.warning(f"Config Warning: {attribute} must be a dictionary")
@ -97,7 +101,17 @@ class Metadata:
final_value = util.validate_date(value, name, return_as="%Y-%m-%d")
current = current[:-9]
elif var_type == "float":
final_value = util.parse(name, value, datatype="float", minimum=0, maximum=10)
if value is None:
raise Failed(f"Metadata Error: {name} attribute is blank")
final_value = None
try:
value = float(str(value))
if 0 <= value <= 10:
final_value = value
except ValueError:
pass
if final_value is None:
raise Failed(f"Metadata Error: {name} attribute must be a number between 0 and 10")
else:
final_value = value
if current != str(final_value):
@ -174,7 +188,17 @@ class Metadata:
logger.info("")
year = None
if "year" in methods:
year = util.parse("year", meta, datatype="int", methods=methods, minimum=1800, maximum=datetime.now().year + 1)
next_year = datetime.now().year + 1
if meta[methods["year"]] is None:
raise Failed("Metadata Error: year attribute is blank")
try:
year_value = int(str(meta[methods["year"]]))
if 1800 <= year_value <= next_year:
year = year_value
except ValueError:
pass
if year is None:
raise Failed(f"Metadata Error: year attribute must be an integer between 1800 and {next_year}")
title = mapping_name
if "title" in methods:

View file

@ -383,7 +383,7 @@ class Plex(Library):
return choices
except NotFound:
logger.debug(f"Search Attribute: {final_search}")
raise Failed(f"Collection Error: plex search attribute: {search_name} not supported")
raise Failed(f"Plex Error: plex_search attribute: {search_name} not supported")
@retry(stop_max_attempt_number=6, wait_fixed=10000, retry_on_exception=util.retry_if_not_plex)
def get_labels(self):

View file

@ -279,7 +279,7 @@ def time_window(time_window):
def glob_filter(filter_in):
filter_in = filter_in.translate({ord("["): "[[]", ord("]"): "[]]"}) if "[" in filter_in else filter_in
return glob.glob(filter_in, recursive=True)
return glob.glob(filter_in)
def is_date_filter(value, modifier, data, final, current_time):
if value is None:
@ -326,72 +326,3 @@ def is_string_filter(values, modifier, data):
if jailbreak: break
return (jailbreak and modifier in [".not", ".isnot"]) or (not jailbreak and modifier in ["", ".is", ".begins", ".ends", ".regex"])
def parse(attribute, data, datatype=None, methods=None, parent=None, default=None, options=None, translation=None, minimum=1, maximum=None, regex=None):
display = f"{parent + ' ' if parent else ''}{attribute} attribute"
if options is None and translation is not None:
options = [o for o in translation]
value = data[methods[attribute]] if methods and attribute in methods else data
if datatype == "list":
if value:
return [v for v in value if v] if isinstance(value, list) else [str(value)]
return []
elif datatype == "intlist":
if value:
try:
return [int(v) for v in value if v] if isinstance(value, list) else [int(value)]
except ValueError:
pass
return []
elif datatype == "dictlist":
final_list = []
for dict_data in get_list(value):
if isinstance(dict_data, dict):
final_list.append((dict_data, {dm.lower(): dm for dm in dict_data}))
else:
raise Failed(f"Collection Error: {display} {dict_data} is not a dictionary")
return final_list
elif methods and attribute not in methods:
message = f"{display} not found"
elif value is None:
message = f"{display} is blank"
elif regex is not None:
regex_str, example = regex
if re.compile(regex_str).match(str(value)):
return str(value)
else:
message = f"{display}: {value} must match pattern {regex_str} e.g. {example}"
elif datatype == "bool":
if isinstance(value, bool):
return value
elif isinstance(value, int):
return value > 0
elif str(value).lower() in ["t", "true"]:
return True
elif str(value).lower() in ["f", "false"]:
return False
else:
message = f"{display} must be either true or false"
elif datatype in ["int", "float"]:
try:
value = int(str(value)) if datatype == "int" else float(str(value))
if (maximum is None and minimum <= value) or (maximum is not None and minimum <= value <= maximum):
return value
except ValueError:
pass
pre = f"{display} {value} must {'an integer' if datatype == 'int' else 'a number'}"
if maximum is None:
message = f"{pre} {minimum} or greater"
else:
message = f"{pre} between {minimum} and {maximum}"
elif (translation is not None and str(value).lower() not in translation) or \
(options is not None and translation is None and str(value).lower() not in options):
message = f"{display} {value} must be in {', '.join([str(o) for o in options])}"
else:
return translation[value] if translation is not None else value
if default is None:
raise Failed(f"Collection Error: {message}")
else:
logger.warning(f"Collection Warning: {message} using {default} as default")
return translation[default] if translation is not None else default

View file

@ -5,8 +5,8 @@ try:
import plexapi, schedule
from modules import util
from modules.builder import CollectionBuilder
from modules.config import Config
from modules.meta import Metadata
from modules.config import ConfigFile
from modules.meta import MetadataFile
from modules.util import Failed, NotScheduled
except ModuleNotFoundError:
print("Requirements Error: Requirements are not installed")
@ -159,7 +159,7 @@ def start(attrs):
global stats
stats = {"created": 0, "modified": 0, "deleted": 0, "added": 0, "removed": 0, "radarr": 0, "sonarr": 0}
try:
config = Config(default_dir, attrs)
config = ConfigFile(default_dir, attrs)
except Exception as e:
util.print_stacktrace()
util.print_multiline(e, critical=True)
@ -535,7 +535,7 @@ def library_operations(config, library):
logger.info("")
util.separator(f"Starting TMDb Collections")
logger.info("")
metadata = Metadata(config, library, "Data", {
metadata = MetadataFile(config, library, "Data", {
"collections": {
_n.replace(library.tmdb_collections["remove_suffix"], "").strip() if library.tmdb_collections["remove_suffix"] else _n:
{"template": {"name": "TMDb Collection", "collection_id": _i}}