Skip to content

Commit

Permalink
Add possibility to ignore shows based on Plex tags (#85)
Browse files Browse the repository at this point in the history
* Add possibility to ignore shows based on Plex tags

* Add comma separated list support for ignore_tags configuration
  • Loading branch information
RemiRigal committed Aug 11, 2023
1 parent 581ed76 commit 7c8e936
Show file tree
Hide file tree
Showing 14 changed files with 123 additions and 1 deletion.
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

0 comments on commit 7c8e936

Please sign in to comment.