From b3d5f0004b4d422d31b9d1930c688acd124efbcf Mon Sep 17 00:00:00 2001 From: bossanova808 Date: Thu, 18 Apr 2024 22:29:42 +0000 Subject: [PATCH] [script.service.playbackresumer] 2.0.6 --- script.service.playbackresumer/addon.xml | 6 +- script.service.playbackresumer/changelog.txt | 4 + .../resources/lib/common.py | 72 ++++++++++++------ .../resources/lib/player.py | 73 +++++++++++++------ .../resources/lib/store.py | 13 ++-- 5 files changed, 110 insertions(+), 58 deletions(-) diff --git a/script.service.playbackresumer/addon.xml b/script.service.playbackresumer/addon.xml index 0e85d60b60..aaa1fde081 100644 --- a/script.service.playbackresumer/addon.xml +++ b/script.service.playbackresumer/addon.xml @@ -1,5 +1,5 @@ - + @@ -15,9 +15,7 @@ https://github.com/bossanova808/script.service.playbackresumer https://forum.kodi.tv/showthread.php?tid=355383 bossanova808@gmail.com - v2.0.5 - - Fix odd reported bug with resume points - + v2.0.6 - Add support for non-library videos icon.png diff --git a/script.service.playbackresumer/changelog.txt b/script.service.playbackresumer/changelog.txt index a92f823fd3..916709d3e4 100644 --- a/script.service.playbackresumer/changelog.txt +++ b/script.service.playbackresumer/changelog.txt @@ -1,3 +1,7 @@ +v2.0.6 + +- Add support for non library videos + v2.0.5 - Fix wierd bug with resume points: https://forum.kodi.tv/showthread.php?tid=355383&pid=3163480#pid3163480 diff --git a/script.service.playbackresumer/resources/lib/common.py b/script.service.playbackresumer/resources/lib/common.py index 8e75b7b509..6e7b9f1de8 100644 --- a/script.service.playbackresumer/resources/lib/common.py +++ b/script.service.playbackresumer/resources/lib/common.py @@ -1,11 +1,23 @@ # -*- coding: utf-8 -*- + """ -Handy utility functions for Kodi Addons -By bossanova808 -Free in all senses.... -VERSION 0.2.3 2021-06-21 -(For Kodi Matrix & later) + +Handy utility functions & constants for Kodi Addons +For Kodi Matrix & later +By bossanova808 - freely released +VERSION 0.2.7 2024-04-19 + +Changelog: +0.2.7 - Fix getting the major Kodi version (& change float -> int), as it was failing on e.g. 'RC' being in the string apparently +0.2.6 - (SkinPatcher) - add float KODI_VERSION_FLOAT constant, alongside string KODI_VERSION +0.2.5 - (Skin) - move to storing copy of latest in bossanova808 repo and adding this mini changelog + +For latest version - ALWAYS COPY BACK ANY CHANGES, plus do changelog, and a version & date bump above: +https://github.com/bossanova808/repository.bossanova808/blob/main/latest-common/common.py + + """ + import sys import traceback @@ -13,6 +25,8 @@ import xbmcvfs import xbmcgui import xbmcaddon +import json + ADDON = xbmcaddon.Addon() ADDON_NAME = ADDON.getAddonInfo('name') @@ -25,6 +39,7 @@ LANGUAGE = ADDON.getLocalizedString PROFILE = xbmcvfs.translatePath(ADDON.getAddonInfo('profile')) KODI_VERSION = xbmc.getInfoLabel('System.BuildVersion') +KODI_VERSION_INT = int(KODI_VERSION.split(".")[0]) USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36" HOME_WINDOW = xbmcgui.Window(10000) WEATHER_WINDOW = xbmcgui.Window(12600) @@ -49,6 +64,8 @@ """ unit_testing = False + +# Testing outside of Kodi if not xbmc.getUserAgent(): xbmc = None @@ -63,6 +80,7 @@ def log(message, exception_instance=None, level=None): print(f'EXCPT: {traceback.format_exc(exception_instance)}') +# Running inside of Kodi else: def log(message, exception_instance=None, level=xbmc.LOGDEBUG): @@ -105,9 +123,9 @@ def set_property(window, name, value=""): def get_property(window, name): """ Return the value of a window property - @param window: - @param name: - @return: + :param window: the Kodi window to get the property value from + :param name: the name of the property to get + :return: the value of the window property """ return window.getProperty(name) @@ -115,32 +133,33 @@ def get_property(window, name): def get_property_as_bool(window, name): """ Return the value of a window property as a boolean - @param window: - @param name: - @return: + :param window: the Kodi window to get the property value from + :param name: the name of the property to get + :return: the value of the window property in boolean form """ return window.getProperty(name).lower() == "true" def send_kodi_json(human_description, json_string): """ - Send a JSON command to Kodi, logging the human description, command, and result returned. + Send a JSON command to Kodi, logging the human description, command, and result as returned. :param human_description: Required. A human sensible description of what the command is aiming to do/retrieve. :param json_string: Required. The json command to send. + :return the json object loaded from the result string """ log(f'KODI JSON RPC command: {human_description} [{json_string}]') result = xbmc.executeJSONRPC(json_string) log(f'KODI JSON RPC result: {result}') - return result + return json.loads(result) def get_setting(setting): """ Helper function to get string type from settings - @param setting: - @return: setting value + :param setting: The addon setting to return + :return: the setting value """ return ADDON.getSetting(setting).strip() @@ -149,8 +168,8 @@ def get_setting_as_bool(setting): """ Helper function to get bool type from settings - @param setting: - @return: setting value as boolen + :param setting: The addon setting to return + :return: the setting value as boolean """ return get_setting(setting).lower() == "true" @@ -159,10 +178,10 @@ def notify(message, notification_type=xbmcgui.NOTIFICATION_ERROR, duration=5000) """ Send a notification to the user via the Kodi GUI - @param message: the message to send - @param notification_type: xbmcgui.NOTIFICATION_ERROR (default), xbmcgui.NOTIFICATION_WARNING, or xbmcgui.NOTIFICATION_INFO - @param duration: time to display notification in milliseconds, default 5000 - @return: None + :param message: the message to send + :param notification_type: xbmcgui.NOTIFICATION_ERROR (default), xbmcgui.NOTIFICATION_WARNING, or xbmcgui.NOTIFICATION_INFO + :param duration: time to display notification in milliseconds, default 5000 + :return: None """ dialog = xbmcgui.Dialog() @@ -171,6 +190,15 @@ def notify(message, notification_type=xbmcgui.NOTIFICATION_ERROR, duration=5000) notification_type, duration) + def is_playback_paused(): + """ + Helper function to return Kodi player state. + (Odd this is needed, it should be a testable state on Player really..) + + :return: boolean indicating player paused state + """ + return bool(xbmc.getCondVisibility("Player.Paused")) + def footprints(startup=True): """ @@ -180,7 +208,7 @@ def footprints(startup=True): """ if startup: log(f'Starting...', level=xbmc.LOGINFO) - log(f'Kodi Version: {KODI_VERSION}', level=xbmc.LOGINFO) + log(f'Kodi System.BuildVersion: {KODI_VERSION}, which is Kodi major version: {KODI_VERSION_INT}', level=xbmc.LOGINFO) log(f'Addon arguments: {ADDON_ARGUMENTS}', level=xbmc.LOGINFO) else: log(f'Exiting...', level=xbmc.LOGINFO) diff --git a/script.service.playbackresumer/resources/lib/player.py b/script.service.playbackresumer/resources/lib/player.py index 89cbff5914..e203a4ef3b 100644 --- a/script.service.playbackresumer/resources/lib/player.py +++ b/script.service.playbackresumer/resources/lib/player.py @@ -121,9 +121,9 @@ def update_resume_point(self, seconds): # Short circuits - # No library ID or weird library ID - if not Store.library_id or Store.library_id < 0: - log(f"No/invalid library id ({Store.library_id}) for {Store.currently_playing_file_path} so can't set a resume point") + # 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}") return # Kodi doing its normal stopping thing if seconds == -2: @@ -150,52 +150,77 @@ def update_resume_point(self, seconds): # Log what we are doing if seconds == 0: - log(f'Removing resume point for {Store.type_of_video} id {Store.library_id}') + log(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.type_of_video} id {Store.library_id} to {seconds} seconds') + log(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 if Store.type_of_video == 'episode': - method = 'SetEpisodeDetails' - get_method = 'GetEpisodeDetails' + method = 'VideoLibrary.SetEpisodeDetails' + get_method = 'VideoLibrary.GetEpisodeDetails' id_name = 'episodeid' elif Store.type_of_video == 'movie': - method = 'SetMovieDetails' - get_method = 'GetMovieDetails' + method = 'VideoLibrary.SetMovieDetails' + get_method = 'VideoLibrary.GetMovieDetails' id_name = 'movieid' elif Store.type_of_video == 'musicvideo': - method = 'SetMusicVideoDetails' - get_method = 'GetMusicVideoDetails' + method = 'VideoLibrary.SetMusicVideoDetails' + get_method = 'VideoLibrary.GetMusicVideoDetails' id_name = 'musicvideoid' else: - log(f'Can\'t update resume point as did not recognise type of video [{Store.type_of_video}]') - return + log(f'Did not recognise type of video [{Store.type_of_video}] - assume non-library video') + method = 'Files.SetFileDetails' + get_method = 'Files.GetFileDetails' - query = json.dumps({ + json_dict = { "jsonrpc": "2.0", "id": "setResumePoint", - "method": "VideoLibrary." + method, - "params": { + "method": method, + } + if id_name: + params = { id_name: Store.library_id, "resume": { "position": seconds, "total": Store.length_of_currently_playing_file } } - }) - send_kodi_json(f'Set resume point to {seconds}, total to {Store.length_of_currently_playing_file}', query) + else: + params = { + "file": Store.currently_playing_file_path, + "media": "video", + "resume": { + "position": seconds, + "total": Store.length_of_currently_playing_file + } + } + + json_dict['params'] = params + query = json.dumps(json_dict) + send_kodi_json(f'Set resume point for: {Store.currently_playing_file_path}, type: {Store.type_of_video}, id: {Store.library_id}, to: {seconds} seconds, total: {Store.length_of_currently_playing_file}', query) - # For debugging - let's retrieve and log the current resume point... - query = json.dumps({ + # For debugging - let's retrieve and log the current resume point to check it was actually set as intended... + json_dict = { "jsonrpc": "2.0", "id": "getResumePoint", - "method": "VideoLibrary." + get_method, - "params": { + "method": get_method, + } + if id_name: + params = { id_name: Store.library_id, "properties": ["resume"], } - }) - send_kodi_json(f'Check new resume point & total for id {Store.library_id}', query) + else: + params = { + "file": Store.currently_playing_file_path, + "media": "video", + "properties": ["resume"], + } + + json_dict['params'] = params + query = json.dumps(json_dict) + send_kodi_json(f'Check new resume point & total for: {Store.currently_playing_file_path}, type: {Store.type_of_video}, id: {Store.library_id}', query) def resume_if_was_playing(self): """ diff --git a/script.service.playbackresumer/resources/lib/store.py b/script.service.playbackresumer/resources/lib/store.py index 6b78e16c96..bc3e354b24 100644 --- a/script.service.playbackresumer/resources/lib/store.py +++ b/script.service.playbackresumer/resources/lib/store.py @@ -24,7 +24,7 @@ class Store: # Store the full path of the currently playing file currently_playing_file_path = '' # What type of video is it? episode, movie, musicvideo - type_of_video = 'unknown' + type_of_video = None # What is the library id of this video, if there is one? library_id = -1 # if the video was paused, at what time was it paused? @@ -194,18 +194,15 @@ def update_current_playing_file_path(filepath): json_response = json.loads(xbmc.executeJSONRPC(json.dumps(query))) log(f'JSON-RPC Files.GetFileDetails response: {json.dumps(json_response)}') - Store.type_of_video = 'unknown' - try: Store.type_of_video = json_response['result']['filedetails']['type'] - except: + except KeyError: Store.library_id = -1 - log(f'Error determining type of video; probably not in Kodi\'s library: {Store.currently_playing_file_path}') + log(f"ERROR: Kodi did not return even an 'unknown' file type for: {Store.currently_playing_file_path}") - if Store.type_of_video == 'episode' or Store.type_of_video == 'movie' or Store.type_of_video == 'musicvideo': + if Store.type_of_video in ['episode', 'movie', 'musicvideo']: Store.library_id = json_response['result']['filedetails']['id'] - log(f'The library id for this {Store.type_of_video} is {Store.library_id}') else: Store.library_id = None - log(f'Unsupported type of video {Store.type_of_video} for {Store.currently_playing_file_path}') + log(f'Kodi type: {Store.type_of_video}, library id: {Store.library_id}') \ No newline at end of file