Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add possibility to ignore shows based on Plex tags #85

Merged
merged 2 commits into from
Aug 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,10 @@ plexautolanguages:
# It is recommended to disable this parameter if you have a large TV Show library (10k+ episodes)
refresh_library_on_scan: true
# PlexAutoLanguages will ignore shows with any of the following Plex tags
ignore_tags:
- PAL_IGNORE
# Plex configuration
plex:
# A valid Plex URL (required)
Expand Down
2 changes: 2 additions & 0 deletions config/default.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ plexautolanguages:
trigger_on_scan: true
trigger_on_activity: false
refresh_library_on_scan: true
ignore_tags:
- PAL_IGNORE

plex:
url: ""
Expand Down
5 changes: 5 additions & 0 deletions plex_auto_languages/alerts/activity.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,11 @@ def process(self, plex: PlexServer):
if item is None or not isinstance(item, Episode):
return

# Skip if the show should be ignored
if plex.should_ignore_show(item.show()):
logger.debug(f"[Activity] Ignoring episode {item} due to Plex show tags")
return

# Skip if this item has already been seen in the last 3 seconds
activity_key = (self.user_id, self.item_key)
if activity_key in plex.cache.recent_activities and \
Expand Down
5 changes: 5 additions & 0 deletions plex_auto_languages/alerts/playing.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,11 @@ def process(self, plex: PlexServer):
if item is None or not isinstance(item, Episode):
return

# Skip if the show should be ignored
if plex.should_ignore_show(item.show()):
logger.debug(f"[Play Session] Ignoring episode {item} due to Plex show tags")
return

# Skip is the session state is unchanged
if self.session_key in plex.cache.session_states and plex.cache.session_states[self.session_key] == self.session_state:
return
Expand Down
8 changes: 8 additions & 0 deletions plex_auto_languages/alerts/status.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ def process(self, plex: PlexServer):
if len(added) > 0:
logger.debug(f"[Status] Found {len(added)} newly added episode(s)")
for item in added:
# Check if the item should be ignored
if plex.should_ignore_show(item.show()):
continue

# Check if the item has already been processed
if not plex.cache.should_process_recently_added(item.key, item.addedAt):
continue
Expand All @@ -47,6 +51,10 @@ def process(self, plex: PlexServer):
if len(updated) > 0:
logger.debug(f"[Status] Found {len(updated)} updated episode(s)")
for item in updated:
# Check if the item should be ignored
if plex.should_ignore_show(item.show()):
continue

# Check if the item has already been processed
if not plex.cache.should_process_recently_updated(item.key):
continue
Expand Down
5 changes: 5 additions & 0 deletions plex_auto_languages/alerts/timeline.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,11 @@ def process(self, plex: PlexServer):
if item is None or not isinstance(item, Episode):
return

# Skip if the show should be ignored
if plex.should_ignore_show(item.show()):
logger.debug(f"[Timeline] Ignoring episode {item} due to Plex show tags")
return

# Check if the item has been added recently
if item.addedAt < datetime.now() - timedelta(minutes=5):
return
Expand Down
10 changes: 10 additions & 0 deletions plex_auto_languages/plex_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,12 @@ def get_user_by_id(self, user_id: Union[int, str]):
return None
return matching_users[0]

def should_ignore_show(self, show: Show):
for label in show.labels:
if label.tag and label.tag in self.config.get("ignore_tags"):
return True
return False

def process_new_or_updated_episode(self, item_id: Union[int, str], event_type: EventType, new: bool):
track_changes = NewOrUpdatedTrackChanges(event_type, new)
for user_id in self.get_all_user_ids():
Expand Down Expand Up @@ -280,11 +286,15 @@ def start_deep_analysis(self):
# Scan library
added, updated = self.cache.refresh_library_cache()
for item in added:
if self.should_ignore_show(item.show()):
continue
if not self.cache.should_process_recently_added(item.key, item.addedAt):
continue
logger.info(f"[Scheduler] Processing newly added episode {self.get_episode_short_name(item)}")
self.process_new_or_updated_episode(item.key, EventType.SCHEDULER, True)
for item in updated:
if self.should_ignore_show(item.show()):
continue
if not self.cache.should_process_recently_updated(item.key):
continue
logger.info(f"[Scheduler] Processing updated episode {self.get_episode_short_name(item)}")
Expand Down
9 changes: 9 additions & 0 deletions plex_auto_languages/utils/configuration.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ def __init__(self, user_config_path: str):
self._override_from_config_file(user_config_path)
self._override_from_env()
self._override_plex_token_from_secret()
self._postprocess_config()
self._validate_config()
self._add_system_config()
if self.get("debug"):
Expand Down Expand Up @@ -104,6 +105,11 @@ def _override_plex_token_from_secret(self):
plex_token = stream.readline().strip()
self._config["plex"]["token"] = plex_token

def _postprocess_config(self):
ignore_tags_config = self.get("ignore_tags")
if isinstance(ignore_tags_config, str):
self._config["ignore_tags"] = ignore_tags_config.split(",")

def _validate_config(self):
if self.get("plex.url") == "":
logger.error("A Plex URL is required")
Expand All @@ -117,6 +123,9 @@ def _validate_config(self):
if self.get("update_strategy") not in ["all", "next"]:
logger.error("The 'update_strategy' parameter must be either 'all' or 'next'")
raise InvalidConfiguration
if not isinstance(self.get("ignore_tags"), list):
logger.error("The 'ignore_tags' parameter must be a list or a string-based comma separated list")
raise InvalidConfiguration
if self.get("scheduler.enable") and not re.match(r"^\d{2}:\d{2}$", self.get("scheduler.schedule_time")):
logger.error("A valid 'schedule_time' parameter with the format 'HH:MM' is required (ex: 02:30)")
raise InvalidConfiguration
Expand Down
10 changes: 10 additions & 0 deletions tests/test_alerts_activity.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,16 @@ def test_activity(plex, episode):
mocked_change_tracks.assert_called_once_with(plex.username, episode, EventType.PLAY_OR_ACTIVITY)
plex.cache.recent_activities.clear()

# Not called because the show is ignored
mocked_change_tracks.reset_mock()
plex.cache.recent_activities.clear()
plex.config._config["ignore_tags"] = ["PAL_IGNORE"]
episode.show().addLabel("PAL_IGNORE")
activity.process(plex)
mocked_change_tracks.assert_not_called()
episode.show().removeLabel("PAL_IGNORE")
activity._message = copy.deepcopy(activity_message)

# Not called because the event is 'started'
mocked_change_tracks.reset_mock()
activity._message["event"] = "started"
Expand Down
9 changes: 9 additions & 0 deletions tests/test_alerts_playing.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,16 @@ def test_playing(plex, episode):
plex.cache.user_clients["some_identifier"] = (plex.user_id, plex.username)

with patch.object(PlexServer, "change_tracks") as mocked_change_tracks:
# Not called because the show is ignored
mocked_change_tracks.reset_mock()
plex.config._config["ignore_tags"] = ["PAL_IGNORE"]
episode.show().addLabel("PAL_IGNORE")
playing.process(plex)
mocked_change_tracks.assert_not_called()
episode.show().removeLabel("PAL_IGNORE")

# Default behavior
mocked_change_tracks.reset_mock()
playing.process(plex)
mocked_change_tracks.assert_called_once_with(plex.username, episode, EventType.PLAY_OR_ACTIVITY)
plex.cache.default_streams.clear()
Expand Down
16 changes: 16 additions & 0 deletions tests/test_alerts_status.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,20 @@ def test_status(plex, episode):
status = PlexStatus(copy.deepcopy(status_message))
assert status.title == "Library scan complete"

plex.config._config["ignore_tags"] = ["PAL_IGNORE"]

with patch.object(PlexServer, "process_new_or_updated_episode") as mocked_process:

plex.config._config["refresh_library_on_scan"] = True

with patch.object(PlexServerCache, "refresh_library_cache", return_value=([episode], [])):
# Not called because the show should be ignored
mocked_process.reset_mock()
episode.show().addLabel("PAL_IGNORE")
status.process(plex)
mocked_process.assert_not_called()
episode.show().removeLabel("PAL_IGNORE")

# Default behavior for new episode
mocked_process.reset_mock()
status.process(plex)
Expand All @@ -31,6 +40,13 @@ def test_status(plex, episode):
plex.cache.newly_added.clear()

with patch.object(PlexServerCache, "refresh_library_cache", return_value=([], [episode])):
# Not called because the show should be ignored
mocked_process.reset_mock()
episode.show().addLabel("PAL_IGNORE")
status.process(plex)
mocked_process.assert_not_called()
episode.show().removeLabel("PAL_IGNORE")

# Default behavior for updated episode
mocked_process.reset_mock()
status.process(plex)
Expand Down
10 changes: 10 additions & 0 deletions tests/test_alerts_timeline.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,21 @@ def test_timeline(plex, episode):
assert timeline.state == 5
assert timeline.entry_type == 0

plex.config._config["ignore_tags"] = ["PAL_IGNORE"]

with patch.object(PlexServer, "process_new_or_updated_episode") as mocked_process:
fake_recent_episode = copy.deepcopy(episode)
fake_recent_episode.addedAt = datetime.now()
with patch.object(PlexServer, "fetch_item", return_value=fake_recent_episode):
# Not called because the show should be ignored
mocked_process.reset_mock()
episode.show().addLabel("PAL_IGNORE")
timeline.process(plex)
mocked_process.assert_not_called()
episode.show().removeLabel("PAL_IGNORE")

# Default behavior
mocked_process.reset_mock()
timeline.process(plex)
mocked_process.assert_called_once_with(item_id, EventType.NEW_EPISODE, True)

Expand Down
21 changes: 20 additions & 1 deletion tests/test_configuration.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,8 @@ def test_configuration_user_config():
"plex": {
"url": "http://localhost:32400",
"token": "token"
}
},
"ignore_tags": "TAG_1,TAG_2"
}
}
fd, path = tempfile.mkstemp()
Expand All @@ -120,6 +121,10 @@ def test_configuration_user_config():
config = Configuration(path)
assert config.get("plex.url") == "http://localhost:32400"
assert config.get("plex.token") == "token"
assert isinstance(config.get("ignore_tags"), list)
assert len(config.get("ignore_tags")) == 2
assert "TAG_1" in config.get("ignore_tags")
assert "TAG_2" in config.get("ignore_tags")
finally:
os.remove(path)

Expand Down Expand Up @@ -175,6 +180,20 @@ def test_configuration_unvalidated():
_ = Configuration(None)
del os.environ["UPDATE_STRATEGY"]

config_dict = {
"plexautolanguages": {
"ignore_tags": 12
}
}
fd, path = tempfile.mkstemp()
try:
with open(fd, "w", encoding="utf-8") as stream:
yaml.dump(config_dict, stream)
with pytest.raises(InvalidConfiguration):
_ = Configuration(path)
finally:
os.remove(path)

os.environ["SCHEDULER_ENABLE"] = "true"
os.environ["SCHEDULER_SCHEDULE_TIME"] = "12h30"
with pytest.raises(InvalidConfiguration):
Expand Down
10 changes: 10 additions & 0 deletions tests/test_plex_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,16 @@ def test_get_user_by_id(plex):
assert user is None


def test_should_ignore_show(plex, episode):
plex.config._config["ignore_tags"] = ["PAL_IGNORE"]

episode.show().addLabel("PAL_IGNORE")
assert plex.should_ignore_show(episode.show()) is True

episode.show().removeLabel("PAL_IGNORE")
assert plex.should_ignore_show(episode.show()) is False


def test_get_all_user_ids(plex):
user_ids = plex.get_all_user_ids()
assert len(user_ids) == 2
Expand Down