From 913cda1baa06b53cb69c783b25b33e2008da0dbc Mon Sep 17 00:00:00 2001 From: "bossanova808@gmail.com" Date: Mon, 6 Oct 2025 16:59:51 +1100 Subject: [PATCH 1/2] Filter watched and update resume points on list load --- .../resource.language.en_gb/strings.po | 8 +++++ resources/lib/playback.py | 33 +++++++++++++++---- resources/lib/player.py | 2 +- resources/lib/store.py | 11 ++++++- resources/settings.xml | 10 ++++++ 5 files changed, 56 insertions(+), 8 deletions(-) diff --git a/resources/language/resource.language.en_gb/strings.po b/resources/language/resource.language.en_gb/strings.po index a559e2e..6a6da81 100644 --- a/resources/language/resource.language.en_gb/strings.po +++ b/resources/language/resource.language.en_gb/strings.po @@ -44,3 +44,11 @@ msgstr "" msgctxt "#32009" msgid "Enable Switchback context menu items?" msgstr "" + +msgctxt "#32010" +msgid "Automatically filter watched items out of the Switchback list?" +msgstr "" + +msgctxt "#32011" +msgid "(After a switchback playback) force browse to episode in library?" +msgstr "" diff --git a/resources/lib/playback.py b/resources/lib/playback.py index ac2d8bd..799e086 100644 --- a/resources/lib/playback.py +++ b/resources/lib/playback.py @@ -8,13 +8,12 @@ import xbmcvfs # noinspection PyPackages -from bossanova808.utilities import clean_art_url, send_kodi_json +from bossanova808.utilities import clean_art_url, send_kodi_json, get_resume_point, get_playcount # noinspection PyPackages from bossanova808.logger import Logger # noinspection PyUnresolvedReferences from infotagger.listitem import ListItemInfoTag - @dataclass class Playback: """ @@ -23,8 +22,6 @@ class Playback: file: Optional[str] = None path: Optional[str] = None type: Optional[str] = None # episode, movie, video (per Kodi types) - song is the other type, but Switchback supports video only - # Seems to be a newer version of the above, but unclear how/when to use, and what about music?? - # mediatype: str | None = None # mediatype: string - "video", "movie", "tvshow", "season", "episode" or "musicvideo" source: Optional[str] = None # kodi_library, pvr_live, pvr_recording, addon, file dbid: Optional[int] = None tvshowdbid: Optional[int] = None @@ -345,6 +342,7 @@ class PlaybackList: """ list: List[Playback] file: str + remove_watched_playbacks: bool = False def toJson(self) -> str: """ @@ -386,8 +384,31 @@ def load_or_init(self) -> None: except json.JSONDecodeError: Logger.error(f"JSONDecodeError - Unable to parse PlaybackList file [{self.file}] - creating empty PlaybackList & file") self.init() - # Let unexpected exceptions propagate - # Logger.info("PlaybackList is:", self.list) + + list_needs_save = False + + # If the user wants to filter out watched items from the list + if self.remove_watched_playbacks: + for item in self.list: + if item.dbid: + # Is it marked as watched in the DB? + playcount = get_playcount(item.type, item.dbid) + if playcount and playcount > 0: + list_needs_save = True + Logger.debug(f"Filtering watched playback from the list: [{item.pluginlabel}]") + self.remove_playbacks_of_path(item.path) + + # Update resume points with current data from the Kodi library (consider e.g. shared library scenarios) + for item in self.list: + if item.dbid: + library_resume_point = get_resume_point(item.type, item.dbid) + if library_resume_point != item.resumetime: + Logger.debug(f"Retrieved library resume point: {library_resume_point} != existing list resume point {item.resumetime} - updating playback list") + list_needs_save = True + item.resumetime = library_resume_point + + if list_needs_save: + self.save_to_file() def save_to_file(self) -> None: """ diff --git a/resources/lib/player.py b/resources/lib/player.py index 58fe1f4..a79038e 100644 --- a/resources/lib/player.py +++ b/resources/lib/player.py @@ -84,7 +84,7 @@ def onPlaybackFinished(): # If we Switchbacked to a library episode, force Kodi to browse to the Show/Season # (NB it is not possible to force Kodi to go to movies and focus a specific movie as far as I can determine) - if switchback_playback: + if Store.episode_force_browse and switchback_playback: if Store.current_playback.type == "episode" and Store.current_playback.source == "kodi_library": Logger.info("Force browsing to tvshow/season of just finished playback") Logger.debug(f'flatten tvshows {Store.flatten_tvshows} totalseasons {Store.current_playback.totalseasons} dbid {Store.current_playback.dbid} tvshowdbid {Store.current_playback.tvshowdbid}') diff --git a/resources/lib/store.py b/resources/lib/store.py index c9795c4..1402b34 100644 --- a/resources/lib/store.py +++ b/resources/lib/store.py @@ -18,7 +18,7 @@ class Store: kodi_event_monitor = None kodi_player = None # Holds our playlist of things played back, in first is the latest order - switchback = PlaybackList([], xbmcvfs.translatePath(os.path.join(PROFILE, "switchback.json"))) + switchback = None # When something is being played back, store the details current_playback = None # Playbacks are of these possible types @@ -28,6 +28,9 @@ class Store: save_across_sessions = ADDON.getSettingBool('save_across_sessions') maximum_list_length = ADDON.getSettingInt('maximum_list_length') enable_context_menu = ADDON.getSettingBool('enable_context_menu') + episode_force_browse = ADDON.getSettingBool('episode_force_browse') + remove_watched_playbacks = ADDON.getSettingBool('remove_watched_playbacks') + # GUI Settings - to work out how to force browse to a show after a switchback initiated playback flatten_tvshows = None @@ -38,9 +41,11 @@ def __init__(self): """ Store.load_config_from_settings() Store.load_config_from_kodi_settings() + Store.switchback = PlaybackList([], xbmcvfs.translatePath(os.path.join(PROFILE, "switchback.json")), Store.remove_watched_playbacks) Store.switchback.load_or_init() Store.update_switchback_context_menu() + @staticmethod def load_config_from_settings(): """ @@ -54,6 +59,10 @@ def load_config_from_settings(): Logger.info(f"Save across sessions is: {Store.save_across_sessions}") Store.enable_context_menu = ADDON.getSettingBool('enable_context_menu') Logger.info(f"Enable context menu is: {Store.enable_context_menu}") + Store.remove_watched_playbacks = ADDON.getSettingBool('remove_watched_playbacks') + Logger.info(f"Remove watched playbacks is: {Store.remove_watched_playbacks}") + Store.episode_force_browse = ADDON.getSettingBool('episode_force_browse') + Logger.info(f"Episode force browse is: {Store.episode_force_browse}") @staticmethod def load_config_from_kodi_settings(): diff --git a/resources/settings.xml b/resources/settings.xml index 1c95822..1fda760 100644 --- a/resources/settings.xml +++ b/resources/settings.xml @@ -20,6 +20,16 @@ true + + 0 + true + + + + 0 + false + + From 60b4c6370e61aa4155d78849336c3836d28f2255 Mon Sep 17 00:00:00 2001 From: "bossanova808@gmail.com" Date: Mon, 6 Oct 2025 17:38:11 +1100 Subject: [PATCH 2/2] CR --- resources/lib/playback.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/resources/lib/playback.py b/resources/lib/playback.py index 799e086..b295509 100644 --- a/resources/lib/playback.py +++ b/resources/lib/playback.py @@ -389,14 +389,20 @@ def load_or_init(self) -> None: # If the user wants to filter out watched items from the list if self.remove_watched_playbacks: - for item in self.list: + paths_to_remove = [] + for item in list(self.list): if item.dbid: # Is it marked as watched in the DB? playcount = get_playcount(item.type, item.dbid) if playcount and playcount > 0: list_needs_save = True Logger.debug(f"Filtering watched playback from the list: [{item.pluginlabel}]") - self.remove_playbacks_of_path(item.path) + paths_to_remove.append(item.path) + + if paths_to_remove: + list_needs_save = True + for path in paths_to_remove: + self.remove_playbacks_of_path(path) # Update resume points with current data from the Kodi library (consider e.g. shared library scenarios) for item in self.list: