add sync_to_user to playlists

pull/541/head
meisnate12 3 years ago
parent e3e0ef116a
commit 6871587bb6

@ -86,7 +86,7 @@ scheduled_boolean = ["visible_library", "visible_home", "visible_shared"]
string_details = ["sort_title", "content_rating", "name_mapping"] string_details = ["sort_title", "content_rating", "name_mapping"]
ignored_details = [ ignored_details = [
"smart_filter", "smart_label", "smart_url", "run_again", "schedule", "sync_mode", "template", "test", "delete_not_scheduled", "smart_filter", "smart_label", "smart_url", "run_again", "schedule", "sync_mode", "template", "test", "delete_not_scheduled",
"tmdb_person", "build_collection", "collection_order", "collection_level", "validate_builders", "collection_name", "sort_by", "libraries" "tmdb_person", "build_collection", "collection_order", "collection_level", "validate_builders", "collection_name", "sort_by", "libraries", "sync_to_users"
] ]
details = ["ignore_ids", "ignore_imdb_ids", "server_preroll", "collection_changes_webhooks", "collection_mode", details = ["ignore_ids", "ignore_imdb_ids", "server_preroll", "collection_changes_webhooks", "collection_mode",
"collection_minimum", "label"] + boolean_details + scheduled_boolean + string_details "collection_minimum", "label"] + boolean_details + scheduled_boolean + string_details
@ -2136,6 +2136,37 @@ class CollectionBuilder:
self.library.moveItem(self.obj, item, previous) self.library.moveItem(self.obj, item, previous)
previous = item previous = item
def delete_user_playlist(self, title, user):
user_server = self.library.PlexServer.switchUser(user)
user_playlist = user_server.playlist(title)
user_playlist.delete()
def delete_playlist(self, users):
if self.obj:
self.library.query(self.obj.delete)
logger.info("")
logger.info(f"Playlist {self.obj.title} deleted")
if users:
for user in users:
try:
self.delete_user_playlist(self.obj.title, user)
logger.info(f"Playlist {self.obj.title} deleted on User {user}")
except NotFound:
logger.error(f"Playlist {self.obj.title} not found on User {user}")
def sync_playlist(self, users):
if self.obj and users:
logger.info("")
util.separator(f"Syncing Playlist to Users", space=False, border=False)
logger.info("")
for user in users:
try:
self.delete_user_playlist(self.obj.title, user)
except NotFound:
pass
self.obj.copyToUser(user)
logger.info(f"Playlist: {self.name} synced to {user}")
def send_notifications(self, playlist=False): def send_notifications(self, playlist=False):
if self.obj and self.details["collection_changes_webhooks"] and \ if self.obj and self.details["collection_changes_webhooks"] and \
(self.created or len(self.notification_additions) > 0 or len(self.notification_removals) > 0): (self.created or len(self.notification_additions) > 0 or len(self.notification_removals) > 0):

@ -241,6 +241,7 @@ class ConfigFile:
"tvdb_language": check_for_attribute(self.data, "tvdb_language", parent="settings", default="default"), "tvdb_language": check_for_attribute(self.data, "tvdb_language", parent="settings", default="default"),
"ignore_ids": check_for_attribute(self.data, "ignore_ids", parent="settings", var_type="int_list", default_is_none=True), "ignore_ids": check_for_attribute(self.data, "ignore_ids", parent="settings", var_type="int_list", default_is_none=True),
"ignore_imdb_ids": check_for_attribute(self.data, "ignore_imdb_ids", parent="settings", var_type="list", default_is_none=True), "ignore_imdb_ids": check_for_attribute(self.data, "ignore_imdb_ids", parent="settings", var_type="list", default_is_none=True),
"playlist_sync_to_user": check_for_attribute(self.data, "playlist_sync_to_user", parent="settings", default="all"),
"assets_for_all": check_for_attribute(self.data, "assets_for_all", parent="settings", var_type="bool", default=False, save=False, do_print=False) "assets_for_all": check_for_attribute(self.data, "assets_for_all", parent="settings", var_type="bool", default=False, save=False, do_print=False)
} }
self.webhooks = { self.webhooks = {

@ -252,6 +252,7 @@ class Plex(Library):
else: else:
raise Failed(f"Plex Error: Plex Library must be a Movies or TV Shows library") raise Failed(f"Plex Error: Plex Library must be a Movies or TV Shows library")
self._users = []
self.agent = self.Plex.agent self.agent = self.Plex.agent
self.is_movie = self.type == "Movie" self.is_movie = self.type == "Movie"
self.is_show = self.type == "Show" self.is_show = self.type == "Show"
@ -412,6 +413,16 @@ class Plex(Library):
else: method = None else: method = None
return self.Plex._server.query(key, method=method) return self.Plex._server.query(key, method=method)
@property
def users(self):
if not self._users:
users = []
for user in self.PlexServer.myPlexAccount().users():
if self.PlexServer.machineIdentifier in [s.machineIdentifier for s in user.servers]:
users.append(user.title)
self._users = users
return self._users
def alter_collection(self, item, collection, smart_label_collection=False, add=True): def alter_collection(self, item, collection, smart_label_collection=False, add=True):
if smart_label_collection: if smart_label_collection:
self.query_data(item.addLabel if add else item.removeLabel, collection) self.query_data(item.addLabel if add else item.removeLabel, collection)

@ -66,14 +66,10 @@ class Webhooks:
def error_hooks(self, text, server=None, library=None, collection=None, playlist=None, critical=True): def error_hooks(self, text, server=None, library=None, collection=None, playlist=None, critical=True):
if self.error_webhooks: if self.error_webhooks:
json = {"error": str(text), "critical": critical} json = {"error": str(text), "critical": critical}
if server: if server: json["server_name"] = str(server)
json["server_name"] = str(server) if library: json["library_name"] = str(library)
if library: if collection: json["collection"] = str(collection)
json["library_name"] = str(library) if playlist: json["playlist"] = str(playlist)
if collection:
json["collection"] = str(collection)
if playlist:
json["playlist"] = str(playlist)
self._request(self.error_webhooks, json) self._request(self.error_webhooks, json)
def collection_hooks(self, webhooks, collection, poster_url=None, background_url=None, created=False, deleted=False, additions=None, removals=None, playlist=False): def collection_hooks(self, webhooks, collection, poster_url=None, background_url=None, created=False, deleted=False, additions=None, removals=None, playlist=False):

@ -287,10 +287,7 @@ def update_libraries(config):
util.print_stacktrace() util.print_stacktrace()
util.print_multiline(e, critical=True) util.print_multiline(e, critical=True)
if config.playlist_files: if config.playlist_files:
library_map = {_l.original_mapping_name: _l for _l in config.libraries}
os.makedirs(os.path.join(default_dir, "logs", "playlists"), exist_ok=True) os.makedirs(os.path.join(default_dir, "logs", "playlists"), exist_ok=True)
pf_file_logger = os.path.join(default_dir, "logs", "playlists", "playlists.log") pf_file_logger = os.path.join(default_dir, "logs", "playlists", "playlists.log")
should_roll_over = os.path.isfile(pf_file_logger) should_roll_over = os.path.isfile(pf_file_logger)
@ -299,292 +296,7 @@ def update_libraries(config):
if should_roll_over: if should_roll_over:
playlists_handler.doRollover() playlists_handler.doRollover()
logger.addHandler(playlists_handler) logger.addHandler(playlists_handler)
run_playlists(config)
logger.info("")
util.separator("Playlists")
logger.info("")
for playlist_file in config.playlist_files:
for mapping_name, playlist_attrs in playlist_file.playlists.items():
playlist_start = datetime.now()
if config.test_mode and ("test" not in playlist_attrs or playlist_attrs["test"] is not True):
no_template_test = True
if "template" in playlist_attrs and playlist_attrs["template"]:
for data_template in util.get_list(playlist_attrs["template"], split=False):
if "name" in data_template \
and data_template["name"] \
and playlist_file.templates \
and data_template["name"] in playlist_file.templates \
and playlist_file.templates[data_template["name"]] \
and "test" in playlist_file.templates[data_template["name"]] \
and playlist_file.templates[data_template["name"]]["test"] is True:
no_template_test = False
if no_template_test:
continue
if "name_mapping" in playlist_attrs and playlist_attrs["name_mapping"]:
playlist_log_name, output_str = util.validate_filename(playlist_attrs["name_mapping"])
else:
playlist_log_name, output_str = util.validate_filename(mapping_name)
playlist_log_folder = os.path.join(default_dir, "logs", "playlists", playlist_log_name)
os.makedirs(playlist_log_folder, exist_ok=True)
ply_file_logger = os.path.join(playlist_log_folder, "playlist.log")
should_roll_over = os.path.isfile(ply_file_logger)
playlist_handler = RotatingFileHandler(ply_file_logger, delay=True, mode="w", backupCount=3, encoding="utf-8")
util.apply_formatter(playlist_handler)
if should_roll_over:
playlist_handler.doRollover()
logger.addHandler(playlist_handler)
server_name = None
library_names = None
try:
util.separator(f"{mapping_name} Playlist")
logger.info("")
if output_str:
logger.info(output_str)
logger.info("")
if "libraries" not in playlist_attrs or not playlist_attrs["libraries"]:
raise Failed("Playlist Error: libraries attribute is required and cannot be blank")
pl_libraries = []
for pl_library in util.get_list(playlist_attrs["libraries"]):
if str(pl_library) in library_map:
pl_libraries.append(library_map[pl_library])
else:
raise Failed(f"Playlist Error: Library: {pl_library} not defined")
server_check = None
for pl_library in pl_libraries:
if server_check:
if pl_library.PlexServer.machineIdentifier != server_check:
raise Failed("Playlist Error: All defined libraries must be on the same server")
else:
server_check = pl_library.PlexServer.machineIdentifier
util.separator(f"Validating {mapping_name} Attributes", space=False, border=False)
builder = CollectionBuilder(config, pl_libraries[0], playlist_file, mapping_name, no_missing, playlist_attrs, playlist=True)
logger.info("")
util.separator(f"Running {mapping_name} Playlist", space=False, border=False)
if len(builder.schedule) > 0:
util.print_multiline(builder.schedule, info=True)
items_added = 0
items_removed = 0
logger.info("")
logger.info(f"Sync Mode: {'sync' if builder.sync else 'append'}")
if builder.filters or builder.tmdb_filters:
logger.info("")
for filter_key, filter_value in builder.filters:
logger.info(f"Playlist Filter {filter_key}: {filter_value}")
for filter_key, filter_value in builder.tmdb_filters:
logger.info(f"Playlist Filter {filter_key}: {filter_value}")
method, value = builder.builders[0]
logger.debug("")
logger.debug(f"Builder: {method}: {value}")
logger.info("")
items = []
ids = builder.gather_ids(method, value)
if len(ids) > 0:
total_ids = len(ids)
logger.debug("")
logger.debug(f"{total_ids} IDs Found: {ids}")
for i, input_data in enumerate(ids, 1):
input_id, id_type = input_data
util.print_return(f"Parsing ID {i}/{total_ids}")
if id_type == "tvdb_season":
show_id, season_num = input_id.split("_")
show_id = int(show_id)
found = False
for pl_library in pl_libraries:
if show_id in pl_library.show_map:
found = True
show_item = pl_library.fetchItem(pl_library.show_map[show_id][0])
try:
items.extend(show_item.season(season=int(season_num)).episodes())
except NotFound:
builder.missing_parts.append(f"{show_item.title} Season: {season_num} Missing")
break
if not found and show_id not in builder.missing_shows:
builder.missing_shows.append(show_id)
elif id_type == "tvdb_episode":
show_id, season_num, episode_num = input_id.split("_")
show_id = int(show_id)
found = False
for pl_library in pl_libraries:
if show_id in pl_library.show_map:
found = True
show_item = pl_library.fetchItem(pl_library.show_map[show_id][0])
try:
items.append(show_item.episode(season=int(season_num), episode=int(episode_num)))
except NotFound:
builder.missing_parts.append(f"{show_item.title} Season: {season_num} Episode: {episode_num} Missing")
break
if not found and show_id not in builder.missing_shows:
builder.missing_shows.append(show_id)
else:
rating_keys = []
if id_type == "ratingKey":
rating_keys = input_id
elif id_type == "tmdb":
if input_id not in builder.ignore_ids:
found = False
for pl_library in pl_libraries:
if input_id in pl_library.movie_map:
found = True
rating_keys = pl_library.movie_map[input_id]
break
if not found and input_id not in builder.missing_movies:
builder.missing_movies.append(input_id)
elif id_type in ["tvdb", "tmdb_show"]:
if id_type == "tmdb_show":
try:
input_id = config.Convert.tmdb_to_tvdb(input_id, fail=True)
except Failed as e:
logger.error(e)
continue
if input_id not in builder.ignore_ids:
found = False
for pl_library in pl_libraries:
if input_id in pl_library.show_map:
found = True
rating_keys = pl_library.show_map[input_id]
break
if not found and input_id not in builder.missing_shows:
builder.missing_shows.append(input_id)
elif id_type == "imdb":
if input_id not in builder.ignore_imdb_ids:
found = False
for pl_library in pl_libraries:
if input_id in pl_library.imdb_map:
found = True
rating_keys = pl_library.imdb_map[input_id]
break
if not found:
try:
_id, tmdb_type = config.Convert.imdb_to_tmdb(input_id, fail=True)
if tmdb_type == "episode":
tmdb_id, season_num, episode_num = _id.split("_")
show_id = config.Convert.tmdb_to_tvdb(tmdb_id, fail=True)
show_id = int(show_id)
found = False
for pl_library in pl_libraries:
if show_id in pl_library.show_map:
found = True
show_item = pl_library.fetchItem(pl_library.show_map[show_id][0])
try:
items.append(show_item.episode(season=int(season_num), episode=int(episode_num)))
except NotFound:
builder.missing_parts.append(f"{show_item.title} Season: {season_num} Episode: {episode_num} Missing")
break
if not found and show_id not in builder.missing_shows:
builder.missing_shows.append(show_id)
elif tmdb_type == "movie" and builder.do_missing:
if _id not in builder.missing_movies:
builder.missing_movies.append(_id)
elif tmdb_type == "show" and builder.do_missing:
tvdb_id = config.Convert.tmdb_to_tvdb(_id, fail=True)
if tvdb_id not in builder.missing_shows:
builder.missing_shows.append(tvdb_id)
except Failed as e:
logger.error(e)
continue
if not isinstance(rating_keys, list):
rating_keys = [rating_keys]
for rk in rating_keys:
try:
item = builder.fetch_item(rk)
if isinstance(item, (Show, Season)):
items.extend(item.episodes())
else:
items.append(item)
except Failed as e:
logger.error(e)
util.print_end()
if len(items) > 0:
builder.filter_and_save_items(items)
if len(builder.added_items) >= builder.minimum:
logger.info("")
util.separator(f"Adding to {mapping_name} Playlist", space=False, border=False)
logger.info("")
items_added = builder.add_to_collection()
stats["added"] += items_added
items_removed = 0
if builder.sync:
items_removed = builder.sync_collection()
stats["removed"] += items_removed
elif len(builder.added_items) < builder.minimum:
logger.info("")
logger.info(f"Playlist Minimum: {builder.minimum} not met for {mapping_name} Playlist")
if builder.details["delete_below_minimum"] and builder.obj:
builder.delete_collection()
builder.deleted = True
logger.info("")
logger.info(f"Playlist {builder.obj.title} deleted")
if builder.do_missing and (len(builder.missing_movies) > 0 or len(builder.missing_shows) > 0):
if builder.details["show_missing"] is True:
logger.info("")
util.separator(f"Missing from Library", space=False, border=False)
logger.info("")
radarr_add, sonarr_add = builder.run_missing()
stats["radarr"] += radarr_add
stats["sonarr"] += sonarr_add
run_item_details = True
try:
builder.load_collection()
if builder.created:
stats["created"] += 1
elif items_added > 0 or items_removed > 0:
stats["modified"] += 1
except Failed:
util.print_stacktrace()
run_item_details = False
logger.info("")
util.separator("No Playlist to Update", space=False, border=False)
else:
builder.update_details()
if builder.deleted:
stats["deleted"] += 1
if (builder.item_details or builder.custom_sort) and run_item_details and builder.builders:
try:
builder.load_collection_items()
except Failed:
logger.info("")
util.separator("No Items Found", space=False, border=False)
else:
if builder.item_details:
builder.update_item_details()
if builder.custom_sort:
builder.sort_collection()
builder.send_notifications(playlist=True)
except NotScheduled as e:
util.print_multiline(e, info=True)
except Failed as e:
config.notify(e, server=server_name, library=library_names, playlist=mapping_name)
util.print_stacktrace()
util.print_multiline(e, error=True)
except Exception as e:
config.notify(f"Unknown Error: {e}", server=server_name, library=library_names, playlist=mapping_name)
util.print_stacktrace()
logger.error(f"Unknown Error: {e}")
logger.info("")
util.separator(f"Finished {mapping_name} Playlist\nPlaylist Run Time: {str(datetime.now() - playlist_start).split('.')[0]}")
logger.removeHandler(playlist_handler)
logger.removeHandler(playlists_handler) logger.removeHandler(playlists_handler)
has_run_again = False has_run_again = False
@ -906,94 +618,416 @@ def library_operations(config, library):
util.separator(f"No Unmanaged Collections in {library.name} Library", space=False, border=False) util.separator(f"No Unmanaged Collections in {library.name} Library", space=False, border=False)
logger.info("") logger.info("")
if library.assets_for_all and len(unmanaged_collections) > 0: if library.assets_for_all and len(unmanaged_collections) > 0:
logger.info("") logger.info("")
util.separator(f"Unmanaged Collection Assets Check for {library.name} Library", space=False, border=False) util.separator(f"Unmanaged Collection Assets Check for {library.name} Library", space=False, border=False)
logger.info("")
for col in unmanaged_collections:
library.find_assets(col)
def run_collection(config, library, metadata, requested_collections):
global stats
logger.info("")
for mapping_name, collection_attrs in requested_collections.items():
collection_start = datetime.now()
if config.test_mode and ("test" not in collection_attrs or collection_attrs["test"] is not True):
no_template_test = True
if "template" in collection_attrs and collection_attrs["template"]:
for data_template in util.get_list(collection_attrs["template"], split=False):
if "name" in data_template \
and data_template["name"] \
and metadata.templates \
and data_template["name"] in metadata.templates \
and metadata.templates[data_template["name"]] \
and "test" in metadata.templates[data_template["name"]] \
and metadata.templates[data_template["name"]]["test"] is True:
no_template_test = False
if no_template_test:
continue
if config.resume_from and config.resume_from != mapping_name:
continue
elif config.resume_from == mapping_name:
config.resume_from = None
logger.info("")
util.separator(f"Resuming Collections")
if "name_mapping" in collection_attrs and collection_attrs["name_mapping"]:
collection_log_name, output_str = util.validate_filename(collection_attrs["name_mapping"])
else:
collection_log_name, output_str = util.validate_filename(mapping_name)
collection_log_folder = os.path.join(default_dir, "logs", library.mapping_name, "collections", collection_log_name)
os.makedirs(collection_log_folder, exist_ok=True)
col_file_logger = os.path.join(collection_log_folder, "collection.log")
should_roll_over = os.path.isfile(col_file_logger)
collection_handler = RotatingFileHandler(col_file_logger, delay=True, mode="w", backupCount=3, encoding="utf-8")
util.apply_formatter(collection_handler)
if should_roll_over:
collection_handler.doRollover()
logger.addHandler(collection_handler)
try:
util.separator(f"{mapping_name} Collection")
logger.info("")
if output_str:
logger.info(output_str)
logger.info("")
util.separator(f"Validating {mapping_name} Attributes", space=False, border=False)
builder = CollectionBuilder(config, library, metadata, mapping_name, no_missing, collection_attrs)
logger.info("")
util.separator(f"Running {mapping_name} Collection", space=False, border=False)
if len(builder.schedule) > 0:
util.print_multiline(builder.schedule, info=True)
if len(builder.smart_filter_details) > 0:
logger.info("")
util.print_multiline(builder.smart_filter_details, info=True)
items_added = 0
items_removed = 0
valid = True
if not builder.smart_url and builder.builders:
logger.info("")
logger.info(f"Sync Mode: {'sync' if builder.sync else 'append'}")
if builder.filters or builder.tmdb_filters:
logger.info("")
for filter_key, filter_value in builder.filters:
logger.info(f"Collection Filter {filter_key}: {filter_value}")
for filter_key, filter_value in builder.tmdb_filters:
logger.info(f"Collection Filter {filter_key}: {filter_value}")
builder.find_rating_keys()
if len(builder.added_items) >= builder.minimum and builder.build_collection:
logger.info("")
util.separator(f"Adding to {mapping_name} Collection", space=False, border=False)
logger.info("")
items_added = builder.add_to_collection()
stats["added"] += items_added
items_removed = 0
if builder.sync:
items_removed = builder.sync_collection()
stats["removed"] += items_removed
elif len(builder.added_items) < builder.minimum and builder.build_collection:
logger.info("")
logger.info(f"Collection Minimum: {builder.minimum} not met for {mapping_name} Collection")
valid = False
if builder.details["delete_below_minimum"] and builder.obj:
builder.delete_collection()
builder.deleted = True
logger.info("")
logger.info(f"Collection {builder.obj.title} deleted")
if builder.do_missing and (len(builder.missing_movies) > 0 or len(builder.missing_shows) > 0):
if builder.details["show_missing"] is True:
logger.info("")
util.separator(f"Missing from Library", space=False, border=False)
logger.info("")
radarr_add, sonarr_add = builder.run_missing()
stats["radarr"] += radarr_add
stats["sonarr"] += sonarr_add
run_item_details = True
if valid and builder.build_collection and (builder.builders or builder.smart_url):
try:
builder.load_collection()
if builder.created:
stats["created"] += 1
elif items_added > 0 or items_removed > 0:
stats["modified"] += 1
except Failed:
util.print_stacktrace()
run_item_details = False
logger.info("")
util.separator("No Collection to Update", space=False, border=False)
else:
builder.update_details()
if builder.deleted:
stats["deleted"] += 1
if builder.server_preroll is not None:
library.set_server_preroll(builder.server_preroll)
logger.info("")
logger.info(f"Plex Server Movie pre-roll video updated to {builder.server_preroll}")
if (builder.item_details or builder.custom_sort) and run_item_details and builder.builders:
try:
builder.load_collection_items()
except Failed:
logger.info("")
util.separator("No Items Found", space=False, border=False)
else:
if builder.item_details:
builder.update_item_details()
if builder.custom_sort:
library.run_sort.append(builder)
# builder.sort_collection()
builder.send_notifications()
if builder.run_again and (len(builder.run_again_movies) > 0 or len(builder.run_again_shows) > 0):
library.run_again.append(builder)
except NotScheduled as e:
util.print_multiline(e, info=True)
except Failed as e:
library.notify(e, collection=mapping_name)
util.print_stacktrace()
util.print_multiline(e, error=True)
except Exception as e:
library.notify(f"Unknown Error: {e}", collection=mapping_name)
util.print_stacktrace()
logger.error(f"Unknown Error: {e}")
logger.info("")
util.separator(f"Finished {mapping_name} Collection\nCollection Run Time: {str(datetime.now() - collection_start).split('.')[0]}")
logger.removeHandler(collection_handler)
def run_playlists(config):
logger.info("") logger.info("")
for col in unmanaged_collections: util.separator("Playlists")
library.find_assets(col)
def run_collection(config, library, metadata, requested_collections):
global stats
logger.info("") logger.info("")
for mapping_name, collection_attrs in requested_collections.items(): library_map = {_l.original_mapping_name: _l for _l in config.libraries}
collection_start = datetime.now() for playlist_file in config.playlist_files:
if config.test_mode and ("test" not in collection_attrs or collection_attrs["test"] is not True): for mapping_name, playlist_attrs in playlist_file.playlists.items():
playlist_start = datetime.now()
if config.test_mode and ("test" not in playlist_attrs or playlist_attrs["test"] is not True):
no_template_test = True no_template_test = True
if "template" in collection_attrs and collection_attrs["template"]: if "template" in playlist_attrs and playlist_attrs["template"]:
for data_template in util.get_list(collection_attrs["template"], split=False): for data_template in util.get_list(playlist_attrs["template"], split=False):
if "name" in data_template \ if "name" in data_template \
and data_template["name"] \ and data_template["name"] \
and metadata.templates \ and playlist_file.templates \
and data_template["name"] in metadata.templates \ and data_template["name"] in playlist_file.templates \
and metadata.templates[data_template["name"]] \ and playlist_file.templates[data_template["name"]] \
and "test" in metadata.templates[data_template["name"]] \ and "test" in playlist_file.templates[data_template["name"]] \
and metadata.templates[data_template["name"]]["test"] is True: and playlist_file.templates[data_template["name"]]["test"] is True:
no_template_test = False no_template_test = False
if no_template_test: if no_template_test:
continue continue
if config.resume_from and config.resume_from != mapping_name: if "name_mapping" in playlist_attrs and playlist_attrs["name_mapping"]:
continue playlist_log_name, output_str = util.validate_filename(playlist_attrs["name_mapping"])
elif config.resume_from == mapping_name:
config.resume_from = None
logger.info("")
util.separator(f"Resuming Collections")
if "name_mapping" in collection_attrs and collection_attrs["name_mapping"]:
collection_log_name, output_str = util.validate_filename(collection_attrs["name_mapping"])
else: else:
collection_log_name, output_str = util.validate_filename(mapping_name) playlist_log_name, output_str = util.validate_filename(mapping_name)
collection_log_folder = os.path.join(default_dir, "logs", library.mapping_name, "collections", collection_log_name) playlist_log_folder = os.path.join(default_dir, "logs", "playlists", playlist_log_name)
os.makedirs(collection_log_folder, exist_ok=True) os.makedirs(playlist_log_folder, exist_ok=True)
col_file_logger = os.path.join(collection_log_folder, "collection.log") ply_file_logger = os.path.join(playlist_log_folder, "playlist.log")
should_roll_over = os.path.isfile(col_file_logger) should_roll_over = os.path.isfile(ply_file_logger)
collection_handler = RotatingFileHandler(col_file_logger, delay=True, mode="w", backupCount=3, encoding="utf-8") playlist_handler = RotatingFileHandler(ply_file_logger, delay=True, mode="w", backupCount=3,
util.apply_formatter(collection_handler) encoding="utf-8")
util.apply_formatter(playlist_handler)
if should_roll_over: if should_roll_over:
collection_handler.doRollover() playlist_handler.doRollover()
logger.addHandler(collection_handler) logger.addHandler(playlist_handler)
server_name = None
library_names = None
try: try:
util.separator(f"{mapping_name} Collection") util.separator(f"{mapping_name} Playlist")
logger.info("") logger.info("")
if output_str: if output_str:
logger.info(output_str) logger.info(output_str)
logger.info("") logger.info("")
if "libraries" not in playlist_attrs or not playlist_attrs["libraries"]:
raise Failed("Playlist Error: libraries attribute is required and cannot be blank")
pl_libraries = []
for pl_library in util.get_list(playlist_attrs["libraries"]):
if str(pl_library) in library_map:
pl_libraries.append(library_map[pl_library])
else:
raise Failed(f"Playlist Error: Library: {pl_library} not defined")
server_check = None
for pl_library in pl_libraries:
if server_check:
if pl_library.PlexServer.machineIdentifier != server_check:
raise Failed("Playlist Error: All defined libraries must be on the same server")
else:
server_check = pl_library.PlexServer.machineIdentifier
sync_to_users = config.general["playlist_sync_to_user"]
if "sync_to_users" not in playlist_attrs:
logger.warning(f"Playlist Error: sync_to_users attribute not found defaulting to playlist_sync_to_user: {sync_to_users}")
elif not playlist_attrs["sync_to_users"]:
logger.warning(f"Playlist Error: sync_to_users attribute is blank defaulting to playlist_sync_to_user: {sync_to_users}")
else:
sync_to_users = playlist_attrs["sync_to_users"]
valid_users = []
plex_users = pl_libraries[0].users
if str(sync_to_users) == "all":
valid_users = plex_users
else:
for user in util.get_list(sync_to_users):
if user in plex_users:
valid_users.append(user)
else:
raise Failed(f"Playlist Error: User: {user} not found in plex\nOptions: {plex_users}")
util.separator(f"Validating {mapping_name} Attributes", space=False, border=False) util.separator(f"Validating {mapping_name} Attributes", space=False, border=False)
builder = CollectionBuilder(config, library, metadata, mapping_name, no_missing, collection_attrs) builder = CollectionBuilder(config, pl_libraries[0], playlist_file, mapping_name, no_missing,
playlist_attrs, playlist=True)
logger.info("") logger.info("")
util.separator(f"Running {mapping_name} Collection", space=False, border=False) util.separator(f"Running {mapping_name} Playlist", space=False, border=False)
if len(builder.schedule) > 0: if len(builder.schedule) > 0:
util.print_multiline(builder.schedule, info=True) util.print_multiline(builder.schedule, info=True)
if len(builder.smart_filter_details) > 0:
logger.info("")
util.print_multiline(builder.smart_filter_details, info=True)
items_added = 0 items_added = 0
items_removed = 0 items_removed = 0
valid = True valid = True
if not builder.smart_url and builder.builders:
logger.info("") logger.info("")
logger.info(f"Sync Mode: {'sync' if builder.sync else 'append'}") logger.info(f"Sync Mode: {'sync' if builder.sync else 'append'}")
if builder.filters or builder.tmdb_filters: if builder.filters or builder.tmdb_filters:
logger.info("") logger.info("")
for filter_key, filter_value in builder.filters: for filter_key, filter_value in builder.filters:
logger.info(f"Collection Filter {filter_key}: {filter_value}") logger.info(f"Playlist Filter {filter_key}: {filter_value}")
for filter_key, filter_value in builder.tmdb_filters: for filter_key, filter_value in builder.tmdb_filters:
logger.info(f"Collection Filter {filter_key}: {filter_value}") logger.info(f"Playlist Filter {filter_key}: {filter_value}")
builder.find_rating_keys() method, value = builder.builders[0]
logger.debug("")
logger.debug(f"Builder: {method}: {value}")
logger.info("")
items = []
ids = builder.gather_ids(method, value)
if len(builder.added_items) >= builder.minimum and builder.build_collection: if len(ids) > 0:
total_ids = len(ids)
logger.debug("")
logger.debug(f"{total_ids} IDs Found: {ids}")
for i, input_data in enumerate(ids, 1):
input_id, id_type = input_data
util.print_return(f"Parsing ID {i}/{total_ids}")
if id_type == "tvdb_season":
show_id, season_num = input_id.split("_")
show_id = int(show_id)
found = False
for pl_library in pl_libraries:
if show_id in pl_library.show_map:
found = True
show_item = pl_library.fetchItem(pl_library.show_map[show_id][0])
try:
items.extend(show_item.season(season=int(season_num)).episodes())
except NotFound:
builder.missing_parts.append(f"{show_item.title} Season: {season_num} Missing")
break
if not found and show_id not in builder.missing_shows:
builder.missing_shows.append(show_id)
elif id_type == "tvdb_episode":
show_id, season_num, episode_num = input_id.split("_")
show_id = int(show_id)
found = False
for pl_library in pl_libraries:
if show_id in pl_library.show_map:
found = True
show_item = pl_library.fetchItem(pl_library.show_map[show_id][0])
try:
items.append(
show_item.episode(season=int(season_num), episode=int(episode_num)))
except NotFound:
builder.missing_parts.append(
f"{show_item.title} Season: {season_num} Episode: {episode_num} Missing")
break
if not found and show_id not in builder.missing_shows:
builder.missing_shows.append(show_id)
else:
rating_keys = []
if id_type == "ratingKey":
rating_keys = input_id
elif id_type == "tmdb":
if input_id not in builder.ignore_ids:
found = False
for pl_library in pl_libraries:
if input_id in pl_library.movie_map:
found = True
rating_keys = pl_library.movie_map[input_id]
break
if not found and input_id not in builder.missing_movies:
builder.missing_movies.append(input_id)
elif id_type in ["tvdb", "tmdb_show"]:
if id_type == "tmdb_show":
try:
input_id = config.Convert.tmdb_to_tvdb(input_id, fail=True)
except Failed as e:
logger.error(e)
continue
if input_id not in builder.ignore_ids:
found = False
for pl_library in pl_libraries:
if input_id in pl_library.show_map:
found = True
rating_keys = pl_library.show_map[input_id]
break
if not found and input_id not in builder.missing_shows:
builder.missing_shows.append(input_id)
elif id_type == "imdb":
if input_id not in builder.ignore_imdb_ids:
found = False
for pl_library in pl_libraries:
if input_id in pl_library.imdb_map:
found = True
rating_keys = pl_library.imdb_map[input_id]
break
if not found:
try:
_id, tmdb_type = config.Convert.imdb_to_tmdb(input_id, fail=True)
if tmdb_type == "episode":
tmdb_id, season_num, episode_num = _id.split("_")
show_id = config.Convert.tmdb_to_tvdb(tmdb_id, fail=True)
show_id = int(show_id)
found = False
for pl_library in pl_libraries:
if show_id in pl_library.show_map:
found = True
show_item = pl_library.fetchItem(
pl_library.show_map[show_id][0])
try:
items.append(show_item.episode(season=int(season_num),
episode=int(episode_num)))
except NotFound:
builder.missing_parts.append(
f"{show_item.title} Season: {season_num} Episode: {episode_num} Missing")
break
if not found and show_id not in builder.missing_shows:
builder.missing_shows.append(show_id)
elif tmdb_type == "movie" and builder.do_missing:
if _id not in builder.missing_movies:
builder.missing_movies.append(_id)
elif tmdb_type == "show" and builder.do_missing:
tvdb_id = config.Convert.tmdb_to_tvdb(_id, fail=True)
if tvdb_id not in builder.missing_shows:
builder.missing_shows.append(tvdb_id)
except Failed as e:
logger.error(e)
continue
if not isinstance(rating_keys, list):
rating_keys = [rating_keys]
for rk in rating_keys:
try:
item = builder.fetch_item(rk)
if isinstance(item, (Show, Season)):
items.extend(item.episodes())
else:
items.append(item)
except Failed as e:
logger.error(e)
util.print_end()
if len(items) > 0:
builder.filter_and_save_items(items)
if len(builder.added_items) >= builder.minimum:
logger.info("") logger.info("")
util.separator(f"Adding to {mapping_name} Collection", space=False, border=False) util.separator(f"Adding to {mapping_name} Playlist", space=False, border=False)
logger.info("") logger.info("")
items_added = builder.add_to_collection() items_added = builder.add_to_collection()
stats["added"] += items_added stats["added"] += items_added
@ -1001,15 +1035,13 @@ def run_collection(config, library, metadata, requested_collections):
if builder.sync: if builder.sync:
items_removed = builder.sync_collection() items_removed = builder.sync_collection()
stats["removed"] += items_removed stats["removed"] += items_removed
elif len(builder.added_items) < builder.minimum and builder.build_collection: elif len(builder.added_items) < builder.minimum:
logger.info("") logger.info("")
logger.info(f"Collection Minimum: {builder.minimum} not met for {mapping_name} Collection") logger.info(f"Playlist Minimum: {builder.minimum} not met for {mapping_name} Playlist")
valid = False valid = False
if builder.details["delete_below_minimum"] and builder.obj: if builder.details["delete_below_minimum"] and builder.obj:
builder.delete_collection() builder.delete_playlist(valid_users)
builder.deleted = True builder.deleted = True
logger.info("")
logger.info(f"Collection {builder.obj.title} deleted")
if builder.do_missing and (len(builder.missing_movies) > 0 or len(builder.missing_shows) > 0): if builder.do_missing and (len(builder.missing_movies) > 0 or len(builder.missing_shows) > 0):
if builder.details["show_missing"] is True: if builder.details["show_missing"] is True:
@ -1021,7 +1053,6 @@ def run_collection(config, library, metadata, requested_collections):
stats["sonarr"] += sonarr_add stats["sonarr"] += sonarr_add
run_item_details = True run_item_details = True
if valid and builder.build_collection and (builder.builders or builder.smart_url):
try: try:
builder.load_collection() builder.load_collection()
if builder.created: if builder.created:
@ -1032,19 +1063,14 @@ def run_collection(config, library, metadata, requested_collections):
util.print_stacktrace() util.print_stacktrace()
run_item_details = False run_item_details = False
logger.info("") logger.info("")
util.separator("No Collection to Update", space=False, border=False) util.separator("No Playlist to Update", space=False, border=False)
else: else:
builder.update_details() builder.update_details()
if builder.deleted: if builder.deleted:
stats["deleted"] += 1 stats["deleted"] += 1
if builder.server_preroll is not None: if valid and run_item_details and builder.builders and (builder.item_details or builder.custom_sort):
library.set_server_preroll(builder.server_preroll)
logger.info("")
logger.info(f"Plex Server Movie pre-roll video updated to {builder.server_preroll}")
if (builder.item_details or builder.custom_sort) and run_item_details and builder.builders:
try: try:
builder.load_collection_items() builder.load_collection_items()
except Failed: except Failed:
@ -1054,28 +1080,28 @@ def run_collection(config, library, metadata, requested_collections):
if builder.item_details: if builder.item_details:
builder.update_item_details() builder.update_item_details()
if builder.custom_sort: if builder.custom_sort:
library.run_sort.append(builder) builder.sort_collection()
# builder.sort_collection()
builder.send_notifications()
if builder.run_again and (len(builder.run_again_movies) > 0 or len(builder.run_again_shows) > 0): if valid:
library.run_again.append(builder) builder.sync_playlist(valid_users)
builder.send_notifications(playlist=True)
except NotScheduled as e: except NotScheduled as e:
util.print_multiline(e, info=True) util.print_multiline(e, info=True)
except Failed as e: except Failed as e:
library.notify(e, collection=mapping_name) config.notify(e, server=server_name, library=library_names, playlist=mapping_name)
util.print_stacktrace() util.print_stacktrace()
util.print_multiline(e, error=True) util.print_multiline(e, error=True)
except Exception as e: except Exception as e:
library.notify(f"Unknown Error: {e}", collection=mapping_name) config.notify(f"Unknown Error: {e}", server=server_name, library=library_names, playlist=mapping_name)
util.print_stacktrace() util.print_stacktrace()
logger.error(f"Unknown Error: {e}") logger.error(f"Unknown Error: {e}")
logger.info("") logger.info("")
util.separator(f"Finished {mapping_name} Collection\nCollection Run Time: {str(datetime.now() - collection_start).split('.')[0]}") util.separator(
logger.removeHandler(collection_handler) f"Finished {mapping_name} Playlist\nPlaylist Run Time: {str(datetime.now() - playlist_start).split('.')[0]}")
logger.removeHandler(playlist_handler)
try: try:
if run or test or collections or libraries or resume: if run or test or collections or libraries or resume:

Loading…
Cancel
Save