diff --git a/script.service.playbackresumer/addon.xml b/script.service.playbackresumer/addon.xml index e5831e1847..ecfbe8a654 100644 --- a/script.service.playbackresumer/addon.xml +++ b/script.service.playbackresumer/addon.xml @@ -1,5 +1,5 @@ - + @@ -7,17 +7,21 @@ Periodically sets the resume point of videos and can automatically resume last played video if Kodi crashes. + Ställer in återuppspelningspunkten för videor med jämna mellanrum och kan automatiskt återuppspela den senast spelade videon om Kodi kraschar. Runs as a service and will periodically update the resume point while videos are playing, so you can re-start from where you were in the event of a crash. It can also automatically resume a video if Kodi was shutdown while playing it. See setting to configure how often the resume point is set, and whether to automatically resume. + + Körs som en tjänst och uppdaterar regelbundet återuppspelningspunkten vid spelning medan videor spelas upp, så att du kan starta om från där du var i händelse av ett krascher. Den kan också automatiskt återuppspela en video om Kodi stängdes av vid spelning. Se inställningarna för att konfigurera hur ofta återuppspelningspunkten ska ställas in och om den ska återuppspelas automatiskt. + all GPL-3.0-only https://github.com/bossanova808/script.service.playbackresumer https://github.com/bossanova808/script.service.playbackresumer https://forum.kodi.tv/showthread.php?tid=355383 bossanova808@gmail.com - v2.0.7 -- Remove old common code, use new module + v2.0.8 +- Minor changes for Piers icon.png @@ -26,3 +30,4 @@ + diff --git a/script.service.playbackresumer/changelog.txt b/script.service.playbackresumer/changelog.txt index 916709d3e4..c4a0413939 100644 --- a/script.service.playbackresumer/changelog.txt +++ b/script.service.playbackresumer/changelog.txt @@ -1,5 +1,10 @@ -v2.0.6 +v2.0.8 +- Minor changes for Piers + +v2.0.7 +- Remove old common code, use new module +v2.0.6 - Add support for non library videos v2.0.5 diff --git a/script.service.playbackresumer/resources/language/resource.language.sv_se/strings.po b/script.service.playbackresumer/resources/language/resource.language.sv_se/strings.po new file mode 100644 index 0000000000..736986f39b --- /dev/null +++ b/script.service.playbackresumer/resources/language/resource.language.sv_se/strings.po @@ -0,0 +1,63 @@ +# XBMC Media Center Swedish language file +# Addon Name: XBMC Playback Resumer +# Addon id: script.service.playbackresumer +# Addon version: 1.2.0 +# Addon Provider: bradvido88,bossanova808 +msgid "" +msgstr "" +"Project-Id-Version: XBMC-Addons\n" +"Report-Msgid-Bugs-To: alanwww1@xbmc.org\n" +"POT-Creation-Date: 2014-01-12 02:29+0000\n" +"PO-Revision-Date: 2025-10-01 11:42+0200\n" +"Last-Translator: Daniel Nylander \n" +"Language-Team: sv\n" +"Language: sv\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Generator: Poedit 3.7\n" + +msgctxt "Addon Summary" +msgid "Periodically sets the resume point while videos are playing and optionally automatically resumes last played video if XBMC crashes or is shutdown during playback." +msgstr "Ställer regelbundet in återupptagningspunkten medan videor spelas upp och återupptar automatiskt den senast spelade videon om XBMC kraschar eller stängs av under uppspelning." + +msgctxt "Addon Description" +msgid "This addon runs in the background as a service and will periodically update the resume point while videos are playing. It will also automatically resume a video if XBMC was shutdown while playing it. See setting to configure how often the resume point is set and whether to automatically resume." +msgstr "Detta tillägg körs i bakgrunden som en tjänst och kommer regelbundet att uppdatera återuppspelningspunkten medan videor spelas upp. Det kommer också automatiskt att återuppta en video om XBMC stängdes av medan den spelades. Se Inställning för att konfigurera hur ofta återuppspelningspunkten ska ställas in och om den ska återupptas automatiskt." + +msgctxt "#32000" +msgid "Resumer" +msgstr "Återuppspelare" + +msgctxt "#32001" +msgid "Save resume point every X seconds" +msgstr "Spara återuppspelningspunkt var X:e sekund" + +msgctxt "#32002" +msgid "Auto-Resume playback at startup" +msgstr "Återuppta uppspelningen automatiskt vid start" + +msgctxt "#32003" +msgid "Auto-Play a random video if nothing is playing" +msgstr "Spela automatiskt upp en slumpmässig video om inget spelas" + +msgctxt "#32030" +msgid "Exclude" +msgstr "Exkludera" + +msgctxt "#32031" +msgid "Exclude Live TV" +msgstr "Exkludera direktsänd TV" + +msgctxt "#32032" +msgid "Exclude HTTP sources" +msgstr "Exkludera HTTP-källor" + +msgctxt "#32033" +msgid "Exclude path" +msgstr "Exkludera sökväg" + +msgctxt "#32034" +msgid "Folder's path (and subfolders)" +msgstr "Mappens sökväg (och undermappar)" diff --git a/script.service.playbackresumer/resources/lib/monitor.py b/script.service.playbackresumer/resources/lib/monitor.py index 3ceb33588b..9e24b2eabc 100644 --- a/script.service.playbackresumer/resources/lib/monitor.py +++ b/script.service.playbackresumer/resources/lib/monitor.py @@ -11,8 +11,12 @@ def __init__(self, *args, **kwargs): Logger.debug('KodiEventMonitor __init__') def onSettingsChanged(self): + """ + Handle Kodi settings changes by reloading the add-on configuration from settings. + + Invoked when Kodi reports settings have changed; calls the Store to reload configuration so runtime state reflects updated settings. + """ Logger.info('onSettingsChanged - reload them.') Store.load_config_from_settings() - def onAbortRequested(self): - Logger.debug('onAbortRequested') + diff --git a/script.service.playbackresumer/resources/lib/playback_resumer.py b/script.service.playbackresumer/resources/lib/playback_resumer.py index 6d633c037a..a979cad702 100644 --- a/script.service.playbackresumer/resources/lib/playback_resumer.py +++ b/script.service.playbackresumer/resources/lib/playback_resumer.py @@ -1,20 +1,26 @@ -from bossanova808.utilities import * -# noinspection PyPackages -from .store import Store import xbmc # noinspection PyPackages from .monitor import KodiEventMonitor # noinspection PyPackages from .player import KodiPlayer +# noinspection PyPackages +from .store import Store + +from bossanova808.logger import Logger def run(): """ - This is 'main' - - :return: + Start the addon: initialize logging and global state, configure Kodi monitor and player, attempt to resume or start playback, then run the main event loop until an abort is requested. + + This function: + - Starts the logger and creates the global Store. + - Instantiates and stores Kodi event monitor and player objects. + - Attempts to resume previous playback; if nothing resumed and no video is playing, triggers autoplay when enabled. + - Enters a loop that waits for an abort request and exits when one is detected. + - Stops the logger before returning. """ - footprints() + Logger.start() # load settings and create the store for our globals Store() Store.kodi_event_monitor = KodiEventMonitor(xbmc.Monitor) @@ -26,7 +32,8 @@ def run(): while not Store.kodi_event_monitor.abortRequested(): if Store.kodi_event_monitor.waitForAbort(1): + Logger.debug('onAbortRequested') # Abort was requested while waiting. We should exit break - footprints(False) + Logger.stop() diff --git a/script.service.playbackresumer/resources/lib/player.py b/script.service.playbackresumer/resources/lib/player.py index 1138f2ea0a..81cedb9d6a 100644 --- a/script.service.playbackresumer/resources/lib/player.py +++ b/script.service.playbackresumer/resources/lib/player.py @@ -2,7 +2,7 @@ from bossanova808.logger import Logger from bossanova808.notify import Notify -from bossanova808.utilities import * +from bossanova808.utilities import send_kodi_json # noinspection PyPackages from .store import Store @@ -17,7 +17,14 @@ class KodiPlayer(xbmc.Player): This class represents/monitors the Kodi video player """ - def __init__(self, *args): + # noinspection PyUnusedLocal + def __init__(self, *_args): + """ + Initialize the KodiPlayer instance and bind it to xbmc.Player. + + Parameters: + *_args: Optional positional arguments accepted for compatibility; any values passed are ignored. + """ xbmc.Player.__init__(self) Logger.debug('KodiPlayer __init__') @@ -32,17 +39,32 @@ def onPlayBackEnded(self): # video ended normally (user didn't stop it) self.autoplay_random_if_enabled() def onPlayBackStopped(self): + """ + Handle the playback-stopped event and mark the current resume point as managed by Kodi. + + When playback stops, record a sentinel resume value indicating that Kodi should retain or handle the resume point (internal sentinel -2). + """ Logger.info("onPlayBackStopped") self.update_resume_point(-2) - def onPlayBackSeek(self, time, seekOffset): - Logger.info(f'onPlayBackSeek time {time}, seekOffset {seekOffset}') + def onPlayBackSeek(self, time_to_seek, seek_offset): + """ + Handle a user-initiated seek during playback and update the stored resume point. + + When a seek occurs, attempt to record the current playback time as the resume point. + If reading the current playback time raises a RuntimeError (e.g., seeked past the end), + clear the stored resume point. + + Parameters: + time_to_seek (float): The target time position of the seek (seconds). + seek_offset (float): The relative offset of the seek from the previous position (seconds). + """ + Logger.info(f'onPlayBackSeek time {time_to_seek}, seekOffset {seek_offset}') try: self.update_resume_point(self.getTime()) except RuntimeError: Logger.warning("Could not get playing time - seeked past end? Clearing resume point.") self.update_resume_point(0) - pass def onPlayBackSeekChapter(self, chapter): Logger.info(f'onPlayBackSeekChapter chapter: {chapter}') @@ -51,7 +73,6 @@ def onPlayBackSeekChapter(self, chapter): except RuntimeError: Logger.warning("Could not get playing time - seeked past end? Clearing resume point.") self.update_resume_point(0) - pass def onAVStarted(self): Logger.info("onAVStarted") @@ -86,7 +107,11 @@ def update_resume_point(self, seconds): """ This is where the work is done - stores a new resume point in the Kodi library for the currently playing file - :param: seconds: the time to update the resume point to. @todo add notes on -1, -2 etc here! + :param seconds: target resume time in seconds. + Special values: + -2 -> stopped normally, let Kodi persist native resume (no-op here) + -1 -> end-of-file, clear resume point (sends 0) + 0 -> explicit clear resume point :param: Store.library_id: the Kodi library id of the currently playing file :return: None """ @@ -139,11 +164,13 @@ def update_resume_point(self, seconds): seconds = 0 # if current time > Kodi's ignorepercentatend setting - percent_played = int((seconds * 100) / Store.length_of_currently_playing_file) - if percent_played > (100 - Store.ignore_percent_at_end): - Logger.info(f'Not updating resume point as current percent played ({percent_played}) is above Kodi\'s ignorepercentatend' - f' setting of {Store.ignore_percent_at_end}') - return + # if current time > Kodi's ignorepercentatend setting + total = Store.length_of_currently_playing_file + if total: + percent_played = int((seconds * 100) / total) + if percent_played > (100 - Store.ignore_percent_at_end): + Logger.info(f"Not updating resume point as current percent played ({percent_played}) is above Kodi's ignorepercentatend setting of {Store.ignore_percent_at_end}") + return # OK, BELOW HERE, we're probably going to set a resume point @@ -228,9 +255,12 @@ def update_resume_point(self, seconds): def resume_if_was_playing(self): """ - Automatically resume a video after a crash, if one was playing... - - :return: + Attempt to resume playback after a previous shutdown if resuming is enabled and saved resume data exist. + + If configured and valid resume data are present, the player will start the saved file and seek to the stored resume time; on any failure or if no resume data are applicable, no playback is resumed. + + Returns: + True if playback was resumed and seeked to the saved position, False otherwise. """ if Store.resume_on_startup \ @@ -242,7 +272,7 @@ def resume_if_was_playing(self): resume_point = float(f.read()) except Exception: Logger.error("Error reading resume point from file, therefore not resuming.") - return + return False # neg 1 means the video wasn't playing when Kodi ended if resume_point < 0: @@ -252,13 +282,17 @@ def resume_if_was_playing(self): with open(Store.file_to_store_last_played, 'r') as f: full_path = f.read() - str_timestamp = '%d:%02d' % (resume_point / 60, resume_point % 60) - Logger.info(f'Will resume playback at {str_timestamp} of {full_path}') + if not full_path: + Logger.info("No last-played file found; skipping resume.") + return False + + mins, secs = divmod(int(resume_point), 60) + str_timestamp = f'{mins}:{secs:02d}' self.play(full_path) # wait up to 10 secs for the video to start playing before we try to seek - for i in range(0, 1000): + for _ in range(100): if not self.isPlayingVideo() and not Store.kodi_event_monitor.abortRequested(): xbmc.sleep(100) else: @@ -270,9 +304,13 @@ def resume_if_was_playing(self): def get_random_library_video(self): """ - Get a random video from the library for playback - - :return: + Selects a random video file path from the Kodi library. + + Chooses among episodes, movies, and music videos and returns the file path of a randomly selected item if one exists. Updates Store.video_types_in_library to reflect whether a given type is present. If the library contains no eligible videos, no selection is made. + + Returns: + str: File path of the selected video. + False: If no episodes, movies, or music videos exist in the library. """ # Short circuit if library is empty @@ -280,9 +318,11 @@ def get_random_library_video(self): and not Store.video_types_in_library['movies'] \ and not Store.video_types_in_library['musicvideos']: Logger.warning('No episodes, movies, or music videos exist in the Kodi library. Cannot autoplay a random video.') - return + return False random_int = randint(0, 2) + result_type = None + method = None if random_int == 0: result_type = 'episodes' method = "GetEpisodes" @@ -347,7 +387,10 @@ def autoplay_random_if_enabled(self): if not self.isPlayingVideo() \ and (video_playlist.getposition() == -1 or video_playlist.getposition() == video_playlist.size()): full_path = self.get_random_library_video() - Logger.info("Auto-playing next random video because nothing is playing and playlist is empty: " + full_path) + if not full_path: + Logger.info("No random video available to autoplay.") + return + Logger.info(f"Auto-playing next random video because nothing is playing and playlist is empty: {full_path}") self.play(full_path) Notify.info(f'Auto-playing random video: {full_path}') else: diff --git a/script.service.playbackresumer/resources/lib/store.py b/script.service.playbackresumer/resources/lib/store.py index 76730f8c51..1c92453561 100644 --- a/script.service.playbackresumer/resources/lib/store.py +++ b/script.service.playbackresumer/resources/lib/store.py @@ -1,10 +1,13 @@ -from bossanova808.utilities import * -from bossanova808.logger import Logger import os import json import xml.etree.ElementTree as ElementTree import xbmc +import xbmcvfs + +from bossanova808.constants import PROFILE, ADDON +from bossanova808.logger import Logger +from bossanova808.utilities import get_setting, get_setting_as_bool class Store: