diff --git a/addon.xml b/addon.xml index aaa1fde..e5831e1 100644 --- a/addon.xml +++ b/addon.xml @@ -1,7 +1,8 @@ - + + @@ -15,7 +16,9 @@ https://github.com/bossanova808/script.service.playbackresumer https://forum.kodi.tv/showthread.php?tid=355383 bossanova808@gmail.com - v2.0.6 - Add support for non-library videos + v2.0.7 +- Remove old common code, use new module + icon.png diff --git a/default.py b/default.py index c661825..bf25109 100644 --- a/default.py +++ b/default.py @@ -1,6 +1,6 @@ -# -*- coding: utf-8 -*- - +from bossanova808 import exception_logger from resources.lib import playback_resumer if __name__ == "__main__": - playback_resumer.run() + with exception_logger.log_exception(): + playback_resumer.run() diff --git a/resources/lib/monitor.py b/resources/lib/monitor.py index 2d652d1..3ceb335 100644 --- a/resources/lib/monitor.py +++ b/resources/lib/monitor.py @@ -1,5 +1,6 @@ import xbmc -from .common import * +from bossanova808.logger import Logger +# noinspection PyPackages from .store import Store @@ -7,12 +8,11 @@ class KodiEventMonitor(xbmc.Monitor): def __init__(self, *args, **kwargs): xbmc.Monitor.__init__(self) - log('KodiEventMonitor __init__') + Logger.debug('KodiEventMonitor __init__') def onSettingsChanged(self): - log('onSettingsChanged - reload them.') + Logger.info('onSettingsChanged - reload them.') Store.load_config_from_settings() def onAbortRequested(self): - log('onAbortRequested') - log("Abort Requested") + Logger.debug('onAbortRequested') diff --git a/resources/lib/playback_resumer.py b/resources/lib/playback_resumer.py index 825faee..6d633c0 100644 --- a/resources/lib/playback_resumer.py +++ b/resources/lib/playback_resumer.py @@ -1,9 +1,13 @@ -from .common import * +from bossanova808.utilities import * +# noinspection PyPackages from .store import Store import xbmc +# noinspection PyPackages from .monitor import KodiEventMonitor +# noinspection PyPackages from .player import KodiPlayer + def run(): """ This is 'main' @@ -12,7 +16,7 @@ def run(): """ footprints() # load settings and create the store for our globals - config = Store() + Store() Store.kodi_event_monitor = KodiEventMonitor(xbmc.Monitor) Store.kodi_player = KodiPlayer(xbmc.Player) @@ -26,6 +30,3 @@ def run(): break footprints(False) - - - diff --git a/resources/lib/player.py b/resources/lib/player.py index e203a4e..1138f2e 100644 --- a/resources/lib/player.py +++ b/resources/lib/player.py @@ -1,6 +1,10 @@ from random import randint -from .common import * +from bossanova808.logger import Logger +from bossanova808.notify import Notify +from bossanova808.utilities import * + +# noinspection PyPackages from .store import Store import json import time @@ -15,48 +19,48 @@ class KodiPlayer(xbmc.Player): def __init__(self, *args): xbmc.Player.__init__(self) - log('KodiPlayer __init__') + Logger.debug('KodiPlayer __init__') def onPlayBackPaused(self): - log('onPlayBackPaused') + Logger.info('onPlayBackPaused') Store.paused_time = time.time() - log(f'Playback paused at: {Store.paused_time}') + Logger.info(f'Playback paused at: {Store.paused_time}') def onPlayBackEnded(self): # video ended normally (user didn't stop it) - log("onPlayBackEnded") + Logger.info("onPlayBackEnded") self.update_resume_point(-1) self.autoplay_random_if_enabled() def onPlayBackStopped(self): - log("onPlayBackStopped") + Logger.info("onPlayBackStopped") self.update_resume_point(-2) def onPlayBackSeek(self, time, seekOffset): - log(f'onPlayBackSeek time {time}, seekOffset {seekOffset}') + Logger.info(f'onPlayBackSeek time {time}, seekOffset {seekOffset}') try: self.update_resume_point(self.getTime()) except RuntimeError: - log("Could not get playing time - seeked past end?") + Logger.warning("Could not get playing time - seeked past end? Clearing resume point.") self.update_resume_point(0) pass def onPlayBackSeekChapter(self, chapter): - log(f'onPlayBackSeekChapter chapter: {chapter}') + Logger.info(f'onPlayBackSeekChapter chapter: {chapter}') try: self.update_resume_point(self.getTime()) except RuntimeError: - log("Could not get playing time - seeked past end?") + Logger.warning("Could not get playing time - seeked past end? Clearing resume point.") self.update_resume_point(0) pass def onAVStarted(self): - log("onAVStarted") + Logger.info("onAVStarted") # Clean up - get rid of any data about any files previously played Store.clear_old_play_details() if not self.isPlayingVideo(): - log("Not playing a video - skipping: " + self.getPlayingFile()) + Logger.info("Not playing a video - skipping: " + self.getPlayingFile()) return xbmc.sleep(1500) # give it a bit to start playing and let the stopped method finish @@ -69,7 +73,7 @@ def onAVStarted(self): try: self.update_resume_point(self.getTime()) except RuntimeError: - log('Could not get current playback time from player') + Logger.error('Could not get current playback time from player') for i in range(0, Store.save_interval_seconds): # Shutting down or not playing video anymore...stop handling playback @@ -92,7 +96,7 @@ def update_resume_point(self, seconds): # short circuit if we haven't got a record of the file that is currently playing if not Store.currently_playing_file_path: - log("No valid currently_playing_file_path found - therefore not setting resume point") + Logger.info("No valid currently_playing_file_path found - therefore not setting resume point") return # -1 indicates that the video has stopped playing @@ -103,7 +107,7 @@ def update_resume_point(self, seconds): for i in range(0, 30): if Store.kodi_event_monitor.abortRequested(): - log("Kodi is shutting down, and will save resume point") + Logger.info("Kodi is shutting down, so Kodi will save resume point") # Kodi is shutting down while playing a video. return @@ -115,19 +119,19 @@ def update_resume_point(self, seconds): # Short circuit if current time < Kodi's ignoresecondsatstart setting if 0 < seconds < Store.ignore_seconds_at_start: - log(f'Not updating resume point as current time ({seconds}) is below Kodi\'s ignoresecondsatstart' - f' setting of {Store.ignore_seconds_at_start}') + Logger.info(f'Not updating resume point as current time ({seconds}) is below Kodi\'s ignoresecondsatstart' + f' setting of {Store.ignore_seconds_at_start}') return # Short circuits # Weird library ID if Store.library_id and Store.library_id < 0: - log(f"No/invalid library id ({Store.library_id}) for {Store.currently_playing_file_path}") + Logger.info(f"No/invalid library id ({Store.library_id}) for {Store.currently_playing_file_path}") return # Kodi doing its normal stopping thing if seconds == -2: - log("Not updating Kodi native resume point because the file was stopped normally, so Kodi should do it itself") + Logger.info("Not updating Kodi native resume point because the file was stopped normally, so Kodi should do it itself") return # At this point if seconds is < 0, it is -1 meaning end of file/clear resume point if seconds < 0: @@ -137,22 +141,22 @@ def update_resume_point(self, seconds): # 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): - log(f'Not updating resume point as current percent played ({percent_played}) is above Kodi\'s ignorepercentatend' - f' setting of {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 # OK, BELOW HERE, we're probably going to set a resume point # First update the resume point in the tracker file for later retrieval if needed - log(f'Setting custom resume seconds to {seconds}') + Logger.info(f'Setting custom resume seconds to {seconds}') with open(Store.file_to_store_resume_point, 'w') as f: f.write(str(seconds)) # Log what we are doing if seconds == 0: - log(f'Removing resume point for: {Store.currently_playing_file_path}, type: {Store.type_of_video}, library id: {Store.library_id}') + Logger.info(f'Removing resume point for: {Store.currently_playing_file_path}, type: {Store.type_of_video}, library id: {Store.library_id}') else: - log(f'Setting resume point for: {Store.currently_playing_file_path}, type: {Store.type_of_video}, library id: {Store.library_id}, to: {seconds} seconds') + Logger.info(f'Setting resume point for: {Store.currently_playing_file_path}, type: {Store.type_of_video}, library id: {Store.library_id}, to: {seconds} seconds') # Determine the JSON-RPC setFooDetails method to use and what the library id name is based of the type of video id_name = None @@ -169,7 +173,7 @@ def update_resume_point(self, seconds): get_method = 'VideoLibrary.GetMusicVideoDetails' id_name = 'musicvideoid' else: - log(f'Did not recognise type of video [{Store.type_of_video}] - assume non-library video') + Logger.info(f'Did not recognise type of video [{Store.type_of_video}] - assume non-library video') method = 'Files.SetFileDetails' get_method = 'Files.GetFileDetails' @@ -180,11 +184,11 @@ def update_resume_point(self, seconds): } if id_name: params = { - id_name: Store.library_id, - "resume": { - "position": seconds, - "total": Store.length_of_currently_playing_file - } + id_name: Store.library_id, + "resume": { + "position": seconds, + "total": Store.length_of_currently_playing_file + } } else: params = { @@ -236,20 +240,20 @@ def resume_if_was_playing(self): with open(Store.file_to_store_resume_point, 'r') as f: try: resume_point = float(f.read()) - except Exception as e: - log("Error reading resume point from file, therefore not resuming.") + except Exception: + Logger.error("Error reading resume point from file, therefore not resuming.") return # neg 1 means the video wasn't playing when Kodi ended if resume_point < 0: - log("Not resuming playback because nothing was playing when Kodi last closed") + Logger.info("Not resuming playback because nothing was playing when Kodi last closed") return False 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) - log(f'Will resume playback at {str_timestamp} of {full_path}') + Logger.info(f'Will resume playback at {str_timestamp} of {full_path}') self.play(full_path) @@ -258,7 +262,7 @@ def resume_if_was_playing(self): if not self.isPlayingVideo() and not Store.kodi_event_monitor.abortRequested(): xbmc.sleep(100) else: - notify(f'Resuming playback at {str_timestamp}', xbmcgui.NOTIFICATION_INFO) + Notify.info(f'Resuming playback at {str_timestamp}') self.seekTime(resume_point) return True @@ -275,7 +279,7 @@ def get_random_library_video(self): if not Store.video_types_in_library['episodes'] \ and not Store.video_types_in_library['movies'] \ and not Store.video_types_in_library['musicvideos']: - log('No episodes, movies, or music videos exist in the Kodi library. Cannot autoplay a random video.') + Logger.warning('No episodes, movies, or music videos exist in the Kodi library. Cannot autoplay a random video.') return random_int = randint(0, 2) @@ -294,28 +298,28 @@ def get_random_library_video(self): if not Store.video_types_in_library[result_type]: return self.get_random_library_video() # get a different one - log(f'Getting a random video from: {result_type}') + Logger.info(f'Getting a random video from: {result_type}') query = { - "jsonrpc": "2.0", - "id": "randomLibraryVideo", - "method": "VideoLibrary." + method, - "params": { - "limits": { - "end": 1 - }, - "sort": { - "method": "random" - }, - "properties": [ - "file" - ] - } + "jsonrpc": "2.0", + "id": "randomLibraryVideo", + "method": "VideoLibrary." + method, + "params": { + "limits": { + "end": 1 + }, + "sort": { + "method": "random" + }, + "properties": [ + "file" + ] + } } - log(f'Executing JSON-RPC: {json.dumps(query)}') + Logger.info(f'Executing JSON-RPC: {json.dumps(query)}') json_response = json.loads(xbmc.executeJSONRPC(json.dumps(query))) - log(f'JSON-RPC VideoLibrary.{method} response: {json.dumps(json_response)}') + Logger.info(f'JSON-RPC VideoLibrary.{method} response: {json.dumps(json_response)}') # found a video! if json_response['result']['limits']['total'] > 0: @@ -323,7 +327,7 @@ def get_random_library_video(self): return json_response['result'][result_type][0]['file'] # no videos of this type else: - log("There are no " + result_type + " in the library") + Logger.info("There are no " + result_type + " in the library") Store.video_types_in_library[result_type] = False return self.get_random_library_video() @@ -335,7 +339,7 @@ def autoplay_random_if_enabled(self): if Store.autoplay_random: - log("Autoplay random is enabled in addon settings, so will play a new random video now.") + Logger.info("Autoplay random is enabled in addon settings, so will play a new random video now.") video_playlist = xbmc.PlayList(xbmc.PLAYLIST_VIDEO) @@ -343,10 +347,9 @@ 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() - log("Auto-playing next random video because nothing is playing and playlist is empty: " + full_path) + Logger.info("Auto-playing next random video because nothing is playing and playlist is empty: " + full_path) self.play(full_path) - notify(f'Auto-playing random video: {full_path}', xbmcgui.NOTIFICATION_INFO) + Notify.info(f'Auto-playing random video: {full_path}') else: - log(f'Not auto-playing random as playlist not empty or something is playing.') - log(f'Current playlist position: {video_playlist.getposition()}, playlist size: {video_playlist.size()}') - + Logger.info(f'Not auto-playing random as playlist not empty or something is playing.') + Logger.info(f'Current playlist position: {video_playlist.getposition()}, playlist size: {video_playlist.size()}') diff --git a/resources/lib/store.py b/resources/lib/store.py index bc3e354..76730f8 100644 --- a/resources/lib/store.py +++ b/resources/lib/store.py @@ -1,4 +1,5 @@ -from .common import * +from bossanova808.utilities import * +from bossanova808.logger import Logger import os import json import xml.etree.ElementTree as ElementTree @@ -59,21 +60,21 @@ def __init__(self): root = None try: root = ElementTree.parse(advancedsettings_file).getroot() - log("Found and parsed advancedsettings.xml") + Logger.info("Found and parsed advancedsettings.xml") except (ElementTree.ParseError, IOError): - log("Could not find/parse advancedsettings.xml, will use defaults") + Logger.info("Could not find/parse advancedsettings.xml, will use defaults") if root is not None: element = root.find('./video/ignoresecondsatstart') if element is not None: - log("Found advanced setting ignoresecondsatstart") + Logger.info("Found advanced setting ignoresecondsatstart") Store.ignore_seconds_at_start = int(element.text) element = root.find('./video/ignorepercentatend') if element is not None: - log("Found advanced setting ignorepercentatend") + Logger.info("Found advanced setting ignorepercentatend") Store.ignore_percent_at_end = int(element.text) - log(f"Using ignoresecondsatstart: {Store.ignore_seconds_at_start}, ignorepercentatend: {Store.ignore_percent_at_end}") + Logger.info(f"Using ignoresecondsatstart: {Store.ignore_seconds_at_start}, ignorepercentatend: {Store.ignore_percent_at_end}") @staticmethod def clear_old_play_details(): @@ -81,7 +82,7 @@ def clear_old_play_details(): As soon as a new file is played, clear out all old references to anything that was being stored as the currently playing file :return: """ - log("New playback - clearing legacy now playing details") + Logger.info("New playback - clearing legacy now playing details") Store.library_id = None Store.currently_playing_file_path = None Store.type_of_video = None @@ -98,7 +99,7 @@ def load_config_from_settings(): Load in the addon settings, at start or reload them if they have been changed :return: """ - log("Loading configuration") + Logger.info("Loading configuration") Store.save_interval_seconds = int(float(ADDON.getSetting("saveintervalsecs"))) Store.resume_on_startup = get_setting_as_bool("resumeonstartup") @@ -107,9 +108,9 @@ def load_config_from_settings(): @staticmethod def log_configuration(): - log(f'Will save a resume point every {Store.save_interval_seconds} seconds') - log(f'Resume on startup: {Store.resume_on_startup}') - log(f'Autoplay random video: {Store.autoplay_random}') + Logger.info(f'Will save a resume point every {Store.save_interval_seconds} seconds') + Logger.info(f'Resume on startup: {Store.resume_on_startup}') + Logger.info(f'Autoplay random video: {Store.autoplay_random}') @staticmethod def is_excluded(full_path): @@ -123,32 +124,32 @@ def is_excluded(full_path): if not full_path: return True - log(f'Store.isExcluded(): Checking exclusion settings for [{full_path}]') + Logger.info(f'Store.isExcluded(): Checking exclusion settings for [{full_path}]') - if (full_path.find("pvr://") > -1) and getSettingAsBool('ExcludeLiveTV'): - log('Store.isExcluded(): Video is PVR (Live TV), which is currently set as an excluded source.') + if (full_path.find("pvr://") > -1) and get_setting_as_bool('ExcludeLiveTV'): + Logger.info('Store.isExcluded(): Video is PVR (Live TV), which is currently set as an excluded source.') return True if (full_path.find("http://") > -1 or full_path.find("https://") > -1) and get_setting_as_bool('ExcludeHTTP'): - log("Store.isExcluded(): Video is from an HTTP/S source, which is currently set as an excluded source.") + Logger.info("Store.isExcluded(): Video is from an HTTP/S source, which is currently set as an excluded source.") return True exclude_path = get_setting('exclude_path') if exclude_path and get_setting_as_bool('ExcludePathOption'): if full_path.find(exclude_path) > -1: - log(f'Store.isExcluded(): Video is playing from [{exclude_path}], which is set as excluded path 1.') + Logger.info(f'Store.isExcluded(): Video is playing from [{exclude_path}], which is set as excluded path 1.') return True exclude_path2 = get_setting('exclude_path2') if exclude_path2 and get_setting_as_bool('ExcludePathOption2'): if full_path.find(exclude_path2) > -1: - log(f'Store.isExcluded(): Video is playing from [{exclude_path2}], which is set as excluded path 2.') + Logger.info(f'Store.isExcluded(): Video is playing from [{exclude_path2}], which is set as excluded path 2.') return True exclude_path3 = get_setting('exclude_path3') if exclude_path3 and get_setting_as_bool('ExcludePathOption3'): if full_path.find(exclude_path3) > -1: - log(f'Store.isExcluded(): Video is playing from [{exclude_path3}], which is set as excluded path 3.') + Logger.info(f'Store.isExcluded(): Video is playing from [{exclude_path3}], which is set as excluded path 3.') return True return False @@ -163,7 +164,7 @@ def update_current_playing_file_path(filepath): """ if Store.is_excluded(filepath): - log("Skipping excluded filepath: " + filepath) + Logger.info("Skipping excluded filepath: " + filepath) Store.currently_playing_file_path = None return @@ -173,7 +174,7 @@ def update_current_playing_file_path(filepath): with open(Store.file_to_store_last_played, 'w+', encoding='utf8') as f: f.write(filepath) - log(f'Last played file set to: {filepath}') + Logger.info(f'Last played file set to: {filepath}') # check if it is a library video and if so store the library_id and type_of_video query = { @@ -190,19 +191,19 @@ def update_current_playing_file_path(filepath): "id": "fileDetailsCheck" } - log(f'Executing JSON-RPC: {json.dumps(query)}') + Logger.info(f'Executing JSON-RPC: {json.dumps(query)}') json_response = json.loads(xbmc.executeJSONRPC(json.dumps(query))) - log(f'JSON-RPC Files.GetFileDetails response: {json.dumps(json_response)}') + Logger.info(f'JSON-RPC Files.GetFileDetails response: {json.dumps(json_response)}') try: Store.type_of_video = json_response['result']['filedetails']['type'] except KeyError: Store.library_id = -1 - log(f"ERROR: Kodi did not return even an 'unknown' file type for: {Store.currently_playing_file_path}") + Logger.info(f"ERROR: Kodi did not return even an 'unknown' file type for: {Store.currently_playing_file_path}") if Store.type_of_video in ['episode', 'movie', 'musicvideo']: Store.library_id = json_response['result']['filedetails']['id'] else: Store.library_id = None - log(f'Kodi type: {Store.type_of_video}, library id: {Store.library_id}') \ No newline at end of file + Logger.info(f'Kodi type: {Store.type_of_video}, library id: {Store.library_id}')