From 81a7c8553152499cfd2eab994996b4144f5846dd Mon Sep 17 00:00:00 2001 From: realcopacetic Date: Wed, 3 Apr 2024 11:27:03 +0100 Subject: [PATCH] script.copacetic.helper 1.0.12 --- script.copacetic.helper/README.md | 17 ++- script.copacetic.helper/addon.xml | 4 +- .../resources/lib/service/art.py | 143 ++++++++++++------ .../resources/lib/service/monitor.py | 12 +- .../resources/lib/service/player.py | 2 +- .../resources/lib/service/settings.py | 3 +- 6 files changed, 121 insertions(+), 60 deletions(-) diff --git a/script.copacetic.helper/README.md b/script.copacetic.helper/README.md index 5debf47bd..9cf98261b 100644 --- a/script.copacetic.helper/README.md +++ b/script.copacetic.helper/README.md @@ -13,6 +13,20 @@ All code contained in this project is licensed under GPL 3.0. * __jurialmunkey__ for all the best-practice code examples from [plugin.video.themoviedb.helper](https://github.com/jurialmunkey/plugin.video.themoviedb.helper) and forum support. ### Changelog +**1.0.11** +- Removed visualisation waveform setting from list of settings changed by SettingsMonitor + +**1.0.11** +- Reordered addon.xml back to how it was before 1.0.9 due to removal of script.copacetic.helper as a plugin source selectable for widgets. +- Enhanced Slideshow_Monitor class so that it can now fetch fanarts from containers with plugin sources when they are available then use these fanarts in the custom background slideshow. In this way, you can use a custom path to populate a global custom fanart slideshow even without any content in your local library + +**1.0.10** +- read_fanart() method added in 1.0.10 now triggers on services monitor initialise rather than the first time that the SlideShow monitor is run so it should display backgrounds slightly quicker + +**1.0.10** +- Custom path for Global slideshows can now be refreshed on first entry or on change of path without needing Kodi to restart https://github.com/realcopacetic/script.copacetic.helper/issues/6 +- Added new methods to SlideShow monitor class enabling the service monitor to save current global slideshow fanarts to XML on exit, then make these available during initialisation. The aim of this is to serve the last fanart URL from the previous session while new fanarts are being fetched by the slideshow monitor, which should minimise the black-screen delay on starting up Kodi on slower hardware while fanarts are fetched for the first time https://github.com/realcopacetic/script.copacetic.helper/issues/4 + **1.0.9** - Background slideshows from custom paths/playlists are now generated via the background fanart fetching service and available globally throughout the skin, via a window property. Previously this was done in-skin using a container so would not be available persistently across windows. - Removed glitch in background slideshows causing them to fetch new fanarts too often @@ -21,9 +35,6 @@ All code contained in this project is licensed under GPL 3.0. - Switched incorrect labels Enabled/Disabled for script enabler toggle function - Reordered addon.xml extension points to update how addon is categorised in Kodi repository -TO DO -- Add check of json repsonse before notifying of addon status toggle success - **1.0.8** - Push dbid of corresponding cropped clearlogo to window prop for comparison so cropped clearlogos only show on correct listitems. diff --git a/script.copacetic.helper/addon.xml b/script.copacetic.helper/addon.xml index 1e950759c..b3955c435 100644 --- a/script.copacetic.helper/addon.xml +++ b/script.copacetic.helper/addon.xml @@ -1,13 +1,13 @@ - + - video + executable diff --git a/script.copacetic.helper/resources/lib/service/art.py b/script.copacetic.helper/resources/lib/service/art.py index 6dab5fc5f..785427948 100644 --- a/script.copacetic.helper/resources/lib/service/art.py +++ b/script.copacetic.helper/resources/lib/service/art.py @@ -9,9 +9,8 @@ from resources.lib.utilities import (CROPPED_FOLDERPATH, LOOKUP_XML, TEMP_FOLDERPATH, condition, infolabel, - json_call, log, os, skin_string, - validate_path, window_property, xbmc, - xbmcvfs) + json_call, log, os, validate_path, + window_property, xbmc, xbmcvfs) class ImageEditor(): @@ -215,66 +214,108 @@ def _return_average_color(self, image): class SlideshowMonitor: def __init__(self): + self.lookup = LOOKUP_XML + self.art = {} + self.art_types = ['global', 'movies', + 'tvshows', 'videos', 'artists', 'custom'] + self.on_next_run_flag = True + self.custom_path = infolabel( + 'Skin.String(Background_Slideshow_Custom_Path)') self.refresh_count = self.refresh_interval = self._get_refresh_interval() self.fetch_count = self.fetch_interval = self.refresh_interval * 40 - + def background_slideshow(self): - # Check if refresh interval has been adjusted in skin settings + # If refresh interval has been adjusted in skin settings if self.refresh_interval != self._get_refresh_interval(): self.refresh_interval = self._get_refresh_interval() self.fetch_interval = self.refresh_interval * 40 - # Fech art every 40 x refresh interval - if self.fetch_count >= self.fetch_interval: + + # Capture plugin art if it's available and on_next_run flag is true + if condition( + 'Integer.IsGreater(Container(3300).NumItems,0)' + ) and 'plugin://' in self.custom_path and self.on_next_run_flag: + self._get_plugin_arts() + + # Fech art every 40 x refresh interval, reset if custom path changes + if self.fetch_count >= self.fetch_interval or self.custom_path != infolabel('Skin.String(Background_Slideshow_Custom_Path)'): + self.custom_path = infolabel( + 'Skin.String(Background_Slideshow_Custom_Path)') + if 'plugin://' in self.custom_path and not self.on_next_run_flag: + self.on_next_run_flag = True log('Monitor fetching background art') self.art = self._get_art() self.fetch_count = 1 else: self.fetch_count += 1 + # Set art every refresh interval if self.refresh_count >= self.refresh_interval: - if self.art.get('all'): - self._set_art('Background_Global', self.art['all']) - if self.art.get('movies'): - self._set_art('Background_Movies', self.art['movies']) - if self.art.get('tvshows'): - self._set_art('Background_TVShows', self.art['tvshows']) - if self.art.get('videos'): - self._set_art('Background_Videos', self.art['videos']) - if self.art.get('artists'): - self._set_art('Background_Artists', self.art['artists']) - if self.art.get('custom'): - self._set_art('Background_Custom', self.art['custom']) + for type in self.art_types: + if self.art.get(type): + self._set_art(f'background_{type}', self.art[type]) self.refresh_count = 1 else: self.refresh_count += 1 + + def read_fanart(self): + lookup_tree = ET.parse(self.lookup) + root = lookup_tree.getroot() + for type in self.art_types: + # try search on background tag, if it doesn't exist then create it at root level + try: + for node in root.find('backgrounds'): + if type in node.attrib['type'] and validate_path(node.find('path').text): + path = node.find('path').text + window_property(f'background_{type}_fanart', set=path) + except TypeError: + ET.SubElement(root, 'backgrounds') + lookup_tree.write(self.lookup, encoding="utf-8") - def _get_refresh_interval(self): - try: - self.refresh_interval_check = int( - infolabel('Skin.String(Background_Interval)') - ) - except ValueError: - self.refresh_interval_check = 10 - return self.refresh_interval_check + def write_art(self): + lookup_tree = ET.parse(self.lookup) + root = lookup_tree.getroot() + for type in self.art_types: + current_fanart = infolabel(f'Window(home).Property(background_{type}_fanart)') + for node in root.find('backgrounds'): + if type in node.attrib['type']: + background = node.find('path') + background.text = current_fanart + break + else: + background = ET.SubElement(root.find('backgrounds'), 'background') + background.attrib['type'] = type + path = ET.SubElement(background, 'path') + path.text = current_fanart + lookup_tree.write(self.lookup, encoding="utf-8") + + def _get_plugin_arts(self): + if self.on_next_run_flag: + self.art['custom'] = [] + num_items = int(infolabel('Container(3300).NumItems')) + for i in range(num_items): + item = { + 'title': infolabel( + f'Container(3300).ListItem({i}).Label'), + 'fanart': infolabel( + f'Container(3300).ListItem({i}).Art(fanart)'), + 'clearlogo': infolabel( + f'Container(3300).ListItem({i}).Art(clearlogo)') + } + if item['fanart']: + self.art['custom'].append(item) + self.on_next_run_flag = False def _get_art(self): self.art = {} - self.art['movies'] = [] - self.art['tvshows'] = [] - self.art['artists'] = [] - self.art['videos'] = [] - self.art['all'] = [] - self.art['custom'] = [] - + for type in self.art_types: + self.art[type] = [] + # Populate custom path/playlist slideshow if selected in skin settings - custom_path = infolabel( - 'Skin.String(Background_Slideshow_Custom_Path)') - if custom_path and condition('Skin.String(Background_Slideshow,Custom)'): + if self.custom_path and 'plugin://' not in self.custom_path and condition('Skin.String(Background_Slideshow,Custom)'): query = json_call('Files.GetDirectory', - params={'directory': custom_path}, + params={'directory': self.custom_path}, sort={'method': 'random'}, limit=40, parent='get_directory') - try: for result in query['result']['files']: type = result['type'] @@ -292,7 +333,7 @@ def _get_art(self): except KeyError: pass - # Populate global slidshows + # Populate video and music slidshows from library for item in ['movies', 'tvshows', 'artists']: dbtype = 'Video' if item != 'artists' else 'Audio' query = json_call(f'{dbtype}Library.Get{item}', properties=['art'], sort={ @@ -305,13 +346,23 @@ def _get_art(self): self.art[item].append(data) except KeyError: pass - self.art['videos'] = self.art['movies'] + self.art['tvshows'] + + # Populate global slideshow for list in self.art: if self.art[list]: - self.art['all'] = self.art['all'] + self.art[list] + self.art['global'] = self.art['global'] + self.art[list] return self.art + def _get_refresh_interval(self): + try: + self.refresh_interval_check = int( + infolabel('Skin.String(Background_Interval)') + ) + except ValueError: + self.refresh_interval_check = 10 + return self.refresh_interval_check + def _set_art(self, key, items): art = random.choice(items) art.pop('set.fanart', None) @@ -320,15 +371,17 @@ def _set_art(self, key, items): key, value) in art.items() if 'fanart' in key} fanart = random.choice(list(fanarts.values())) fanart = self._url_decode_path(fanart) - window_property(f'{key}_Fanart', set=fanart) + window_property(f'{key}_fanart', set=fanart) # clearlogo if present otherwise clear clearlogo = art.get('clearlogo', False) if clearlogo: clearlogo = self._url_decode_path(clearlogo) - window_property(f'{key}_Clearlogo', set=clearlogo) + window_property(f'{key}_clearlogo', set=clearlogo) + # title + window_property(f'{key}_title', set=art.get('title', False)) def _url_decode_path(self, path): path = path[:-1] if path.endswith('/') else path path = path.replace('image://', '') path = urllib.unquote(path.replace('image://', '')) - return path + return path \ No newline at end of file diff --git a/script.copacetic.helper/resources/lib/service/monitor.py b/script.copacetic.helper/resources/lib/service/monitor.py index 774703375..79b90c00b 100644 --- a/script.copacetic.helper/resources/lib/service/monitor.py +++ b/script.copacetic.helper/resources/lib/service/monitor.py @@ -16,6 +16,7 @@ XMLSTR = ''' + ''' @@ -56,6 +57,7 @@ def _on_start(self): log('Monitor started', force=True) self.start = False self.player_monitor = PlayerMonitor() + self.art_monitor.read_fanart() else: log('Monitor resumed', force=True) if self._conditions_met() else None while not self.abortRequested() and self._conditions_met(): @@ -64,11 +66,7 @@ def _on_start(self): def _conditions_met(self): return ( - self._get_skindir() and not self.idle and - ( - condition('!Skin.HasSetting(Background_Disabled)') or - condition('Skin.HasSetting(Crop_Clearlogos)') - ) + self._get_skindir() and not self.idle ) def _get_skindir(self): @@ -148,8 +146,7 @@ def poller(self): # slideshow window is visible run SlideshowMonitor() elif condition( - '!Skin.HasSetting(Background_Disabled) + [' - 'Window.IsVisible(home) | ' + '[Window.IsVisible(home) | ' 'Window.IsVisible(skinsettings) | ' 'Window.IsVisible(appearancesettings) | ' 'Window.IsVisible(mediasettings) | ' @@ -231,6 +228,7 @@ def _on_stop(self): if not self.abortRequested(): self._on_start() else: + self.art_monitor.write_art() del self.player_monitor del self.settings_monitor del self.art_monitor diff --git a/script.copacetic.helper/resources/lib/service/player.py b/script.copacetic.helper/resources/lib/service/player.py index 0775b2a8a..adbe7407e 100644 --- a/script.copacetic.helper/resources/lib/service/player.py +++ b/script.copacetic.helper/resources/lib/service/player.py @@ -4,7 +4,7 @@ from resources.lib.script.actions import clean_filename from resources.lib.service.art import ImageEditor -from resources.lib.utilities import condition, window_property +from resources.lib.utilities import condition, json_call, log, window_property class PlayerMonitor(Player): diff --git a/script.copacetic.helper/resources/lib/service/settings.py b/script.copacetic.helper/resources/lib/service/settings.py index 450772501..6c424779f 100644 --- a/script.copacetic.helper/resources/lib/service/settings.py +++ b/script.copacetic.helper/resources/lib/service/settings.py @@ -19,8 +19,7 @@ def __init__(self): 'videolibrary.tvshowartwhitelist': ['keyart', 'square', 'clearlogo', 'clearlogo-alt', 'clearlogo-billboard'], 'musiclibrary.showallitems': False, 'musiclibrary.showcompilationartists': False, - 'pictures.generatethumbs': True, - 'musicplayer.visualisation': 'visualization.waveform' + 'pictures.generatethumbs': True } self.settings_to_change = {}