diff --git a/resources/language/resource.language.en_gb/strings.po b/resources/language/resource.language.en_gb/strings.po
index 4cb1359..f7a08a2 100644
--- a/resources/language/resource.language.en_gb/strings.po
+++ b/resources/language/resource.language.en_gb/strings.po
@@ -1192,3 +1192,15 @@ msgstr "Ask"
msgctxt "#30674"
msgid "Do Nothing"
msgstr "Do Nothing"
+
+msgctxt "#30675"
+msgid "Commercial Skipper"
+msgstr "Commercial Skipper"
+
+msgctxt "#30676"
+msgid "Preview Skipper"
+msgstr "Preview Skipper"
+
+msgctxt "#30677"
+msgid "Recap Skipper"
+msgstr "Recap Skipper"
diff --git a/resources/lib/dialogs.py b/resources/lib/dialogs.py
index 11716a3..bf35a94 100644
--- a/resources/lib/dialogs.py
+++ b/resources/lib/dialogs.py
@@ -213,11 +213,8 @@ class SkipDialog(xbmcgui.WindowXMLDialog):
action_exitkeys_id = None
media_id = None
- is_intro = False
- intro_start = None
- intro_end = None
- credit_start = None
- credit_end = None
+ start = None
+ end = None
has_been_dissmissed = False
@@ -239,24 +236,21 @@ class SkipDialog(xbmcgui.WindowXMLDialog):
log.debug("SkipDialog: onMessage: {0}".format(message))
def onAction(self, action):
-
+ log.debug("SkipDialog: onAction: {0}".format(action.getId()))
if action.getId() == 10 or action.getId() == 92: # ACTION_PREVIOUS_MENU & ACTION_NAV_BACK
+ log.debug("SkipDialog: dismissing dialog so it does not open again")
self.has_been_dissmissed = True
self.close()
- else:
- log.debug("SkipDialog: onAction: {0}".format(action.getId()))
def onClick(self, control_id):
+ log.debug("SkipDialog: onClick: {0}".format(control_id))
player = xbmc.Player()
current_ticks = seconds_to_ticks(player.getTime())
- if self.intro_start is not None and self.intro_end is not None and current_ticks >= self.intro_start and current_ticks <= self.intro_end:
- # If click during intro, skip it
- player.seekTime(ticks_to_seconds(self.intro_end))
-
- elif self.credit_start is not None and self.credit_end is not None and current_ticks >= self.credit_start and current_ticks <= self.credit_end:
- # If click during outro, skip it
- player.seekTime(ticks_to_seconds(self.credit_end))
-
+ if self.start is not None and self.end is not None and current_ticks >= self.start and current_ticks <= self.end:
+ log.debug("SkipDialog: skipping segment because current ticks ({0}) is in range".format(current_ticks))
+ # If click during segment, skip it
+ player.seekTime(ticks_to_seconds(self.end))
+
self.close()
def get_play_called(self):
diff --git a/resources/lib/intro_skipper.py b/resources/lib/intro_skipper.py
index 565773f..7161c1e 100644
--- a/resources/lib/intro_skipper.py
+++ b/resources/lib/intro_skipper.py
@@ -9,13 +9,15 @@ import xbmc
import xbmcaddon
import xbmcgui
-from resources.lib.play_utils import set_correct_skip_info
+from resources.lib.play_utils import get_media_segments
from resources.lib.utils import seconds_to_ticks, ticks_to_seconds, translate_path
+from resources.lib.intro_skipper_utils import get_setting_skip_action, set_correct_skip_info
from .lazylogger import LazyLogger
from .dialogs import SkipDialog
+from typing import Literal
log = LazyLogger(__name__)
@@ -38,108 +40,119 @@ class IntroSkipperService(threading.Thread):
skip_intro_dialog = None
skip_credit_dialog = None
+ skip_commercial_dialog = None
+ skip_preview_dialog = None
+ skip_recap_dialog = None
+
+ segments = None
+ playing_item_id = None
+
+ log.debug("SkipService: starting service")
while not xbmc.Monitor().abortRequested() and not self.stop_thread:
player = xbmc.Player()
if player.isPlaying():
item_id = get_jellyfin_playing_item()
if item_id is not None:
+ log.debug("SkipService: playing item is from jellyfin : {0}".format(item_id))
+
+ # If item id has changed or is new, retrieve segments
+ if playing_item_id is None or playing_item_id != item_id :
+ log.debug("SkipService: item is new, retrieving media segments : {0}".format(item_id))
+ segments = get_media_segments(item_id)
+
+ # Setting global playing item to current playing item
+ playing_item_id = item_id
+
# Handle skip only on jellyfin items
current_ticks = seconds_to_ticks(player.getTime())
# Handle Intros
- skip_intro_dialog = self.handle_intros(plugin_path_real, skip_intro_dialog, item_id, current_ticks, player)
-
+ skip_intro_dialog = self.handle_dialog(plugin_path_real, skip_intro_dialog, item_id, current_ticks, player, segments, "Intro")
# Handle Credits
- skip_credit_dialog = self.handle_credits(plugin_path_real, skip_credit_dialog, item_id, current_ticks, player)
+ skip_credit_dialog = self.handle_dialog(plugin_path_real, skip_credit_dialog, item_id, current_ticks, player, segments, "Outro")
+ # Handle commercial
+ skip_commercial_dialog = self.handle_dialog(plugin_path_real, skip_commercial_dialog, item_id, current_ticks, player, segments, "Commercial")
+ # Handle preview
+ skip_preview_dialog = self.handle_dialog(plugin_path_real, skip_preview_dialog, item_id, current_ticks, player, segments, "Preview")
+ # Handle recap
+ skip_recap_dialog = self.handle_dialog(plugin_path_real, skip_recap_dialog, item_id, current_ticks, player, segments, "Recap")
else:
+ playing_item_id = None
if skip_intro_dialog is not None:
+ log.debug("SkipService: Playback stopped, killing Intro dialog")
skip_intro_dialog.close()
skip_intro_dialog = None
if skip_credit_dialog is not None:
+ log.debug("SkipService: Playback stopped, killing Outro dialog")
skip_credit_dialog.close()
skip_credit_dialog = None
+
+ if skip_commercial_dialog is not None:
+ log.debug("SkipService: Playback stopped, killing Commercial dialog")
+ skip_commercial_dialog.close()
+ skip_commercial_dialog = None
+
+ if skip_preview_dialog is not None:
+ log.debug("SkipService: Playback stopped, killing Preview dialog")
+ skip_preview_dialog.close()
+ skip_preview_dialog = None
+
+ if skip_recap_dialog is not None:
+ log.debug("SkipService: Playback stopped, killing Recap dialog")
+ skip_recap_dialog.close()
+ skip_recap_dialog = None
if xbmc.Monitor().waitForAbort(1):
break
xbmc.sleep(200)
-
- def handle_intros(self, plugin_path_real: str, skip_intro_dialog: SkipDialog, item_id: str, current_ticks: float, player: xbmc.Player):
- settings = xbmcaddon.Addon()
- intro_skip_action = settings.getSetting("intro_skipper_action")
+
+ def handle_dialog(self, plugin_path_real: str, dialog: SkipDialog, item_id: str, current_ticks: float, player: xbmc.Player, segments, type: Literal["Commercial", "Preview", "Recap", "Outro", "Intro"]):
+ skip_action = get_setting_skip_action(type)
# In case do nothing is selected return
- if intro_skip_action == "2":
+ if skip_action == "2":
+ log.debug("SkipService: ignore {0} is selected".format(type))
return None
- if skip_intro_dialog is None:
- skip_intro_dialog = SkipDialog("SkipDialog.xml", plugin_path_real, "default", "720p")
+ if dialog is None:
+ log.debug("SkipService: init dialog")
+ dialog = SkipDialog("SkipDialog.xml", plugin_path_real, "default", "720p")
- set_correct_skip_info(item_id, skip_intro_dialog)
+ set_correct_skip_info(item_id, dialog, segments, type)
- is_intro = False
- if skip_intro_dialog.intro_start is not None and skip_intro_dialog.intro_end is not None:
+ is_segment = False
+ if dialog.start is not None and dialog.end is not None:
# Resets the dismiss var so that button can reappear in case of navigation in the timecodes
- if current_ticks < skip_intro_dialog.intro_start or current_ticks > skip_intro_dialog.intro_end:
- skip_intro_dialog.has_been_dissmissed = False
+ if (current_ticks < dialog.start or current_ticks > dialog.end) and dialog.has_been_dissmissed is True:
+ log.debug("SkipService: {0} skip was dismissed. It is reseted beacause timecode is outised of segment")
+ dialog.has_been_dissmissed = False
# Checks if segment is playing
- is_intro = current_ticks >= skip_intro_dialog.intro_start and current_ticks <= skip_intro_dialog.intro_end
+ is_segment = current_ticks >= dialog.start and current_ticks <= dialog.end
- if intro_skip_action == "1" and is_intro:
+ if skip_action == "1" and is_segment:
+ log.debug("SkipService: {0} is set to automatic skip, skipping segment".format(type))
# If auto skip is enabled, skips to semgent ends automatically
- player.seekTime(ticks_to_seconds(skip_intro_dialog.intro_end))
- xbmcgui.Dialog().notification("JellyCon", "Intro Skipped")
- elif intro_skip_action == "0":
+ player.seekTime(ticks_to_seconds(dialog.end))
+ xbmcgui.Dialog().notification("JellyCon", "{0} Skipped".format(type))
+ elif skip_action == "0":
# Otherwise show skip dialog
- if is_intro and not skip_intro_dialog.has_been_dissmissed:
- skip_intro_dialog.show()
+ if is_segment and not dialog.has_been_dissmissed:
+ log.debug("SkipService: {0} is playing, showing dialog".format(type))
+ dialog.show()
else:
# Could not find doc on what happens when closing a closed dialog, but it seems fine
- skip_intro_dialog.close()
+ log.debug("SkipService: {0} is not playing, closing dialog".format(type))
+ dialog.close()
- return skip_intro_dialog
-
- def handle_credits(self, plugin_path_real: str, skip_credit_dialog: SkipDialog, item_id: str, current_ticks: float, player: xbmc.Player):
- settings = xbmcaddon.Addon()
- credit_skip_action = settings.getSetting("credit_skipper_action")
-
- # In case do nothing is selected return
-
- if credit_skip_action == "2":
- return None
-
- if skip_credit_dialog is None:
- skip_credit_dialog = SkipDialog("SkipDialog.xml", plugin_path_real, "default", "720p")
-
- set_correct_skip_info(item_id, skip_credit_dialog)
-
- is_credit = False
- if skip_credit_dialog.credit_start is not None and skip_credit_dialog.credit_end is not None:
- # Resets the dismiss var so that button can reappear in case of navigation in the timecodes
- if current_ticks < skip_credit_dialog.credit_start or current_ticks > skip_credit_dialog.credit_end:
- skip_credit_dialog.has_been_dissmissed = False
-
- # Checks if segment is playing
- is_credit = current_ticks >= skip_credit_dialog.credit_start and current_ticks <= skip_credit_dialog.credit_end
-
- if credit_skip_action == "1" and is_credit:
- # If auto skip is enabled, skips to semgent ends automatically
- player.seekTime(ticks_to_seconds(skip_credit_dialog.credit_end))
- xbmcgui.Dialog().notification("JellyCon", "Credit Skipped")
- elif credit_skip_action == "0":
- # Otherwise show skip dialog
- if is_credit and not skip_credit_dialog.has_been_dissmissed:
- skip_credit_dialog.show()
- else:
- skip_credit_dialog.close()
-
- return skip_credit_dialog
+ return dialog
+
def stop_service(self):
log.debug("IntroSkipperService Stop Called")
self.stop_thread = True
diff --git a/resources/lib/intro_skipper_utils.py b/resources/lib/intro_skipper_utils.py
new file mode 100644
index 0000000..ef3ef8b
--- /dev/null
+++ b/resources/lib/intro_skipper_utils.py
@@ -0,0 +1,69 @@
+from typing import Literal
+
+import xbmcaddon
+
+from .lazylogger import LazyLogger
+from .dialogs import SkipDialog
+
+from .utils import seconds_to_ticks
+
+log = LazyLogger(__name__)
+
+def get_setting_skip_action(type: Literal["Commercial", "Preview", "Recap", "Outro", "Intro"]):
+ settings = xbmcaddon.Addon()
+ if (type == "Commercial"):
+ return settings.getSetting("commercial_skipper_action")
+ elif (type == "Preview"):
+ return settings.getSetting("preview_skipper_action")
+ elif (type == "Recap"):
+ return settings.getSetting("recap_skipper_action")
+ elif (type == "Outro"):
+ return settings.getSetting("credit_skipper_action")
+ elif (type == "Intro"):
+ return settings.getSetting("intro_skipper_action")
+
+def get_setting_skip_start_offset(type: Literal["Commercial", "Preview", "Recap", "Outro", "Intro"]):
+ settings = xbmcaddon.Addon()
+ if (type == "Commercial"):
+ return settings.getSettingInt("commercial_skipper_start_offset")
+ elif (type == "Preview"):
+ return settings.getSettingInt("preview_skipper_start_offset")
+ elif (type == "Recap"):
+ return settings.getSettingInt("recap_skipper_start_offset")
+ elif (type == "Outro"):
+ return settings.getSettingInt("credit_skipper_start_offset")
+ elif (type == "Intro"):
+ return settings.getSettingInt("intro_skipper_start_offset")
+
+def get_setting_skip_end_offset(type: Literal["Commercial", "Preview", "Recap", "Outro", "Intro"]):
+ settings = xbmcaddon.Addon()
+ if (type == "Commercial"):
+ return settings.getSettingInt("commercial_skipper_end_offset")
+ elif (type == "Preview"):
+ return settings.getSettingInt("preview_skipper_end_offset")
+ elif (type == "Recap"):
+ return settings.getSettingInt("recap_skipper_end_offset")
+ elif (type == "Outro"):
+ return settings.getSettingInt("credit_skipper_end_offset")
+ elif (type == "Intro"):
+ return settings.getSettingInt("intro_skipper_end_offset")
+
+def set_correct_skip_info(item_id: str, skip_dialog: SkipDialog, segments, type: Literal["Commercial", "Preview", "Recap", "Outro", "Intro"]):
+ if (skip_dialog.media_id is None or skip_dialog.media_id != item_id) and item_id is not None:
+ # If playback item has changed (or is new), sets its id and fetch media segments (happens twice per media - intro and outro - but it is a light call)
+ log.debug("SkipDialogInfo : Media Id has changed to {0}, setting segments".format(item_id))
+ skip_dialog.media_id = item_id
+ skip_dialog.has_been_dissmissed = False
+ if segments is not None:
+ # Find the intro and outro timings
+ start = next((segment["StartTicks"] for segment in segments if segment["Type"] == type), None)
+ end = next((segment["EndTicks"] for segment in segments if segment["Type"] == type), None)
+
+ # Sets timings with offsets if defined in settings
+ if start is not None:
+ skip_dialog.start = start + seconds_to_ticks(get_setting_skip_start_offset(type))
+ log.debug("SkipDialogInfo : Setting {0} start to {1}".format(type, skip_dialog.start))
+ if end is not None:
+ skip_dialog.end = end - seconds_to_ticks(get_setting_skip_end_offset(type))
+ log.debug("SkipDialogInfo : Setting {0} end to {1}".format(type, skip_dialog.end))
+
\ No newline at end of file
diff --git a/resources/lib/play_utils.py b/resources/lib/play_utils.py
index 69704ca..33006e6 100644
--- a/resources/lib/play_utils.py
+++ b/resources/lib/play_utils.py
@@ -16,6 +16,8 @@ import xbmcvfs
import xbmcplugin
from six.moves.urllib.parse import urlencode
+from typing import Literal
+
from .jellyfin import api
from .lazylogger import LazyLogger
from .dialogs import ResumeDialog, SkipDialog
@@ -1709,28 +1711,6 @@ def get_media_segments(item_id):
url = "/MediaSegments/{}".format(item_id)
result = api.get(url)
if result is None or result["Items"] is None:
+ log.debug("GetMediaSegments : Media segments cloud not be retrieved")
return None
return result["Items"]
-
-def set_correct_skip_info(item_id: str, skip_dialog: SkipDialog):
- if (skip_dialog.media_id is None or skip_dialog.media_id != item_id) and item_id is not None:
- # If playback item has changed (or is new), sets its id and fetch media segments (happens twice per media - intro and outro - but it is a light call)
- skip_dialog.media_id = item_id
- skip_dialog.has_been_dissmissed = False
- segments = get_media_segments(item_id)
- if segments is not None:
- # Find the intro and outro timings
- intro_start = next((segment["StartTicks"] for segment in segments if segment["Type"] == "Intro"), None)
- intro_end = next((segment["EndTicks"] for segment in segments if segment["Type"] == "Intro"), None)
- credit_start = next((segment["StartTicks"] for segment in segments if segment["Type"] == "Outro"), None)
- credit_end = next((segment["EndTicks"] for segment in segments if segment["Type"] == "Outro"), None)
-
- # Sets timings with offsets if defined in settings
- if intro_start is not None:
- skip_dialog.intro_start = intro_start + seconds_to_ticks(settings.getSettingInt("intro_skipper_start_offset"))
- if intro_end is not None:
- skip_dialog.intro_end = intro_end - seconds_to_ticks(settings.getSettingInt("intro_skipper_end_offset"))
- if credit_start is not None:
- skip_dialog.credit_start = credit_start + seconds_to_ticks(settings.getSettingInt("credit_skipper_start_offset"))
- if credit_end is not None:
- skip_dialog.credit_end = credit_end - seconds_to_ticks(settings.getSettingInt("credit_skipper_end_offset"))
diff --git a/resources/settings.xml b/resources/settings.xml
index 626ff41..31c5bb8 100644
--- a/resources/settings.xml
+++ b/resources/settings.xml
@@ -124,6 +124,18 @@
+
+
+
+
+
+
+
+
+
+
+
+