diff --git a/script.copacetic.helper/README.md b/script.copacetic.helper/README.md index 07eb1fe08..07005f767 100644 --- a/script.copacetic.helper/README.md +++ b/script.copacetic.helper/README.md @@ -13,6 +13,25 @@ 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.1.6** +- Added missing PVR windows to background monitor expression + +**1.1.5** +- Fixed bug in actor_credits() where the current infoscreen item was not being removed from the 'More from X' actor credits widget if the infoscreen was for an episode, because it was expecting to find the TV show title in ListItem.Label and instead receiving the episode name, which wouldn't ever match. + +**1.1.4** +- Fixed bug with infoscreen widgets not updating when navigating between infoscreens by ensuring director and genre properties update each time the infoscreen is loaded, even if the underlying list hasn't been scrolled +- Added season info monitoring +- Added a window property that is set to true while set progress is being calculated in get_collection_status() + +**1.1.3** +- Fixed conditional that was preventing cropped clearlogo paths from being fetched for home widgets + +**.1.1.2** +- Support for more edge case conversions of different image modes in clearlogo_cropper(). +- Added method to service monitor to calculate watched percentage of sets and return as a property + **.1.1.1** - Fixed bug in previous version causing dark cropped clearlogos to always be served in certain scenarios diff --git a/script.copacetic.helper/addon.xml b/script.copacetic.helper/addon.xml index 67dd42575..ea862189c 100644 --- a/script.copacetic.helper/addon.xml +++ b/script.copacetic.helper/addon.xml @@ -1,5 +1,5 @@ - + diff --git a/script.copacetic.helper/resources/lib/plugin/content.py b/script.copacetic.helper/resources/lib/plugin/content.py index 4858a8195..b97b5873f 100644 --- a/script.copacetic.helper/resources/lib/plugin/content.py +++ b/script.copacetic.helper/resources/lib/plugin/content.py @@ -2,7 +2,7 @@ from resources.lib.plugin.json_map import JSON_MAP from resources.lib.plugin.library import * -from resources.lib.utilities import (ADDON, infolabel, json_call, log, +from resources.lib.utilities import (ADDON, condition, infolabel, json_call, log, set_plugincontent) @@ -224,8 +224,12 @@ def director_credits(self): def actor_credits(self): filters = [self.filter_actor] - current_item = infolabel('ListItem.Label') - + # grab current movie or tvshow name + if condition('String.IsEqual(ListItem.DBType,episode)'): + current_item = infolabel('ListItem.TVShowTitle') + else: + current_item = infolabel('ListItem.Label') + # json lookup for movies and tvshows by given actor movies_json_query = json_call('VideoLibrary.GetMovies', properties=JSON_MAP['movie_properties'], sort=self.sort_year, @@ -239,10 +243,11 @@ def actor_credits(self): query_filter={'and': filters}, parent='actor_credits' ) - + # work out combined number of movie/tvshow credits total_items = int(movies_json_query['result']['limits']['total']) + int( tvshows_json_query['result']['limits']['total']) + # if there are movie results, remove the current item if it is in the list, then add the remaining to the plugin directory try: movies_json_query = movies_json_query['result']['movies'] except Exception: @@ -253,7 +258,7 @@ def actor_credits(self): movies_json_query.remove( dict_to_remove) if dict_to_remove is not None and total_items > 1 else None add_items(self.li, movies_json_query, type='movie') - + # if there are tvshow results, remove the current item if it is in the list, then add the remaining to the plugin directory try: tvshows_json_query = tvshows_json_query['result']['tvshows'] except Exception: diff --git a/script.copacetic.helper/resources/lib/script/actions.py b/script.copacetic.helper/resources/lib/script/actions.py index e0efada79..78088bf0a 100644 --- a/script.copacetic.helper/resources/lib/script/actions.py +++ b/script.copacetic.helper/resources/lib/script/actions.py @@ -33,6 +33,39 @@ def dialog_yesno(heading, message, **kwargs): log_and_execute(action) +def get_collection_status(dbid, **kwargs): + window_property('collection_watched_calculating', set='true') + watched = 0 + query = json_call( + 'VideoLibrary.GetMovieSetDetails', + params={'setid': int(dbid)}, + parent='get_set_movies' + ) + try: + total = query['result']['setdetails']['limits']['total'] + movies = query['result']['setdetails']['movies'] + except KeyError: + return + else: + for movie in movies: + query = json_call( + 'VideoLibrary.GetMovieDetails', + params={'properties': [ + 'playcount'], 'movieid': movie['movieid']}, + parent='get_movie_playcounts' + ) + playcount = query['result']['moviedetails'].get('playcount') + if playcount: + watched += 1 + finally: + # https://stackoverflow.com/a/68118106/21112145 + percentage = (total and watched / total or 0) * 100 + unwatched = total - watched + window_property('collection_watched_calculating', clear=True) + window_property('collection_watched_dbid', set=dbid) + window_property('collection_watched_percentage', set=percentage) + window_property('collection_unwatched', set=unwatched) + def globalsearch_input(**kwargs): kb = xbmc.Keyboard( infolabel('$INFO[Skin.String(globalsearch)]'), infolabel('$LOCALIZE[137]')) diff --git a/script.copacetic.helper/resources/lib/service/art.py b/script.copacetic.helper/resources/lib/service/art.py index 6050c2add..93ee33413 100644 --- a/script.copacetic.helper/resources/lib/service/art.py +++ b/script.copacetic.helper/resources/lib/service/art.py @@ -89,10 +89,9 @@ def crop_image(self, url): log( f'ImageEditor: Error - could not open cached image --> {error}', force=True) else: - if image.mode == 'LA': # Convert if mode == 'LA' - converted_image = Image.new("RGBA", image.size) - converted_image.paste(image) - image = converted_image + converted_image = Image.new("RGBA", image.size) + converted_image.paste(image) + image = converted_image try: image = image.crop(image.convert('RGBa').getbbox()) except ValueError as error: @@ -418,7 +417,7 @@ def _set_art(self, key, items): clearlogo = art.get('clearlogo-billboard', False) if not clearlogo: clearlogo = art.get('clearlogo', False) - if clearlogo and condition('!Skin.HasSetting(Experiment_Disable_Transitions)'): + if clearlogo and condition('!Skin.HasSetting(Quick_Transitions)'): clearlogo = url_decode_path(clearlogo) clearlogo = self._crop_clearlogo(clearlogo) window_property(f'{key}_clearlogo', set=clearlogo) diff --git a/script.copacetic.helper/resources/lib/service/monitor.py b/script.copacetic.helper/resources/lib/service/monitor.py index 0b0601549..fce4592f4 100644 --- a/script.copacetic.helper/resources/lib/service/monitor.py +++ b/script.copacetic.helper/resources/lib/service/monitor.py @@ -9,7 +9,7 @@ from resources.lib.service.settings import SettingsMonitor from resources.lib.utilities import (CROPPED_FOLDERPATH, LOOKUP_XML, TEMP_FOLDERPATH, condition, create_dir, - get_cache_size, infolabel, log, + get_cache_size, infolabel, json_call, log, log_and_execute, split, split_random_return, validate_path, window_property) @@ -42,6 +42,15 @@ def __init__(self): self._create_dirs() self._on_start() + def _conditions_met(self): + return ( + self._get_skindir() and not self.idle + ) + + def _container_scrolling(self, key='ListItem'): + container = 'Container' if key == 'ListItem' else f'Container({key})' + return condition(f'{container}.Scrolling') + def _create_dirs(self): if not validate_path(self.cropped_folder): create_dir(self.cropped_folder) @@ -52,6 +61,73 @@ def _create_dirs(self): ET.ElementTree(root).write( self.lookup, xml_declaration=True, encoding="utf-8") + def _current_item(self, key='ListItem'): + container = 'Container' if key == 'ListItem' else f'Container({key})' + item = infolabel(f'{container}.CurrentItem') + dbid = infolabel(f'{container}.ListItem.DBID') + dbtype = infolabel(f'{container}.ListItem.DBType') + return (container, item, dbid, dbtype) + + def _get_info(self): + split_random_return( + infolabel('ListItem.Director'), name='RandomDirector') + split_random_return( + infolabel('ListItem.Genre'), name='RandomGenre') + split(infolabel('ListItem.Writer'), name='WriterSplit') + split(infolabel('ListItem.Studio'), name='StudioSplit') + + def _get_season_info(self, container): + window_property('Season_Number', infolabel( + f'{container}.ListItem.Season')) + window_property('Season_Year', infolabel( + f'{container}.ListItem.Year')) + window_property('Season_Fanart', infolabel( + f'{container}.ListItem.Art(fanart)')) + + def _get_skindir(self): + skindir = xbmc.getSkinDir() + if 'skin.copacetic' in skindir: + return True + + def _on_recommendedsettings(self): + if condition('Window.Is(skinsettings)') and self.check_settings: + self.settings_monitor.get_default() + self.check_settings = False + elif not condition('Window.Is(skinsettings)'): + self.check_settings = True + if condition('Skin.HasSetting(run_set_default)'): + self.settings_monitor.set_default() + self.check_settings = True + log_and_execute('Skin.ToggleSetting(run_set_default)') + + def _on_scroll_functions(self, key='ListItem', crop=True, return_color=True, get_info=False, get_season_info=True): + path, current_item, current_dbid, current_dbtype = self._current_item( + key) + if ( + current_item != self.position or + current_dbid != self.dbid or + current_dbtype != self.dbtype + ) and not self._container_scrolling(key): + if crop and condition( + 'Skin.HasSetting(Crop_Clearlogos)' + ): + self._clearlogo_cropper( + source=key, return_color=return_color, reporting=window_property) + if get_info: + self._get_info() + if get_season_info: + self._get_season_info(path) + self.position = current_item + self.dbid = current_dbid + self.dbtype = current_dbtype + + def _on_skinsettings(self): + if condition('Window.Is(skinsettings)') and self.check_cache: + get_cache_size() + self.check_cache = False + elif condition('!Window.Is(skinsettings)'): + self.check_cach = True + def _on_start(self): if self.start: log('Monitor started', force=True) @@ -64,23 +140,18 @@ def _on_start(self): self.poller() self._on_stop() - def _conditions_met(self): - return ( - self._get_skindir() and not self.idle - ) - - def _get_skindir(self): - skindir = xbmc.getSkinDir() - if 'skin.copacetic' in skindir: - return True - - def _get_info(self): - split_random_return( - infolabel('ListItem.Director'), name='RandomDirector') - split_random_return( - infolabel('ListItem.Genre'), name='RandomGenre') - split(infolabel('ListItem.Writer'), name='WriterSplit') - split(infolabel('ListItem.Studio'), name='StudioSplit') + def _on_stop(self): + log(f'Monitor idle', force=True) + while not self.abortRequested() and not self._conditions_met(): + self.waitForAbort(2) + if not self.abortRequested(): + self._on_start() + else: + self.art_monitor.fanart_write() + del self.player_monitor + del self.settings_monitor + del self.art_monitor + log(f'Monitor stopped', force=True) def poller(self): # video playing fullscreen @@ -104,26 +175,30 @@ def poller(self): 'Control.HasFocus(3208) | ' 'Control.HasFocus(3209)]' ): - self._on_scroll(crop=False, return_color=False, get_info=True) + self._on_scroll_functions( + crop=False, return_color=False, get_info=True, get_season_info=False) self.waitForAbort(0.2) - # secondary list has focus and clearlogo view visible + # media view is visible and container content type not empty elif condition( - 'Skin.HasSetting(Crop_Clearlogos) + ' - 'Control.HasFocus(3100) + [' - 'Control.IsVisible(501) | Control.IsVisible(502) | Control.IsVisible(504)]' - ): - self._on_scroll(key='3100', return_color=False) - self.waitForAbort(0.2) - - # clearlogo view visible - elif condition( - 'Skin.HasSetting(Crop_Clearlogos) + [' - 'Control.IsVisible(501) | ' - 'Control.IsVisible(502) | ' - 'Control.IsVisible(504)]' + '[Window.Is(videos) | Window.Is(music)] + ' + '[Container.Content(movies) | ' + 'Container.Content(sets) | ' + 'Container.Content(tvshows) | ' + 'Container.Content(seasons) | ' + 'Container.Content(episodes) | ' + 'Container.Content(videos) | ' + 'Container.Content(artists) | ' + 'Container.Content(albums) | ' + 'Container.Content(songs) | ' + 'Container.Content(musicvideos)]' ): - self._on_scroll() + # secondary + if condition('Control.HasFocus(3100)'): + self._on_scroll_functions(key='3100', return_color=False) + # primary + else: + self._on_scroll_functions() self.waitForAbort(0.2) # home widgets has clearlogo visible @@ -141,7 +216,7 @@ def poller(self): 'Control.HasFocus(3209)]' ): widget = infolabel('System.CurrentControlID') - self._on_scroll(key=widget) + self._on_scroll_functions(key=widget) self.waitForAbort(0.2) # slideshow window is visible run SlideshowMonitor() @@ -164,8 +239,11 @@ def poller(self): 'Window.IsVisible(mediasource) | ' 'Window.IsVisible(smartplaylisteditor) | ' 'Window.IsVisible(musicplaylisteditor) | ' - 'Window.IsVisible(tvguide) | Window.IsVisible(radioguide) | ' - 'Window.IsVisible(tvchannels) | Window.IsVisible(radiochannels) | ' + 'Window.IsVisible(radiochannels) | Window.IsVisible(tvchannels) | ' + 'Window.IsVisible(radioguide) | Window.IsVisible(tvguide) | ' + 'Window.IsVisible(radiosearch) | Window.IsVisible(tvsearch) | ' + 'Window.IsVisible(radiotimers) | Window.IsVisible(tvtimers) | ' + 'Window.IsVisible(radiotimerrules) | Window.IsVisible(tvtimerrules) | ' 'Container.Content(genres) | ' 'Container.Content(years) | ' 'Container.Content(playlists) | ' @@ -187,65 +265,6 @@ def poller(self): self.check_settings = True self.waitForAbort(1) - def _on_scroll(self, key='ListItem', crop=True, return_color=True, get_info=False): - path, current_item, current_dbid, current_dbtype = self._current_item( - key) - if ( - current_item != self.position or - current_dbid != self.dbid or - current_dbtype != self.dbtype - ) and not self._container_scrolling(key): - if crop and condition('!Skin.HasSetting(Experiment_Disable_Transitions)'): - self._clearlogo_cropper( - source=key, return_color=return_color, reporting=window_property) - if get_info: - self._get_info() - self.position = current_item - self.dbid = current_dbid - self.dbtype = current_dbtype - - def _on_skinsettings(self): - if condition('Window.Is(skinsettings)') and self.check_cache: - get_cache_size() - self.check_cache = False - elif condition('!Window.Is(skinsettings)'): - self.check_cach = True - - def _on_recommendedsettings(self): - if condition('Window.Is(skinsettings)') and self.check_settings: - self.settings_monitor.get_default() - self.check_settings = False - elif not condition('Window.Is(skinsettings)'): - self.check_settings = True - if condition('Skin.HasSetting(run_set_default)'): - self.settings_monitor.set_default() - self.check_settings = True - log_and_execute('Skin.ToggleSetting(run_set_default)') - - def _on_stop(self): - log(f'Monitor idle', force=True) - while not self.abortRequested() and not self._conditions_met(): - self.waitForAbort(2) - if not self.abortRequested(): - self._on_start() - else: - self.art_monitor.fanart_write() - del self.player_monitor - del self.settings_monitor - del self.art_monitor - log(f'Monitor stopped', force=True) - - def _current_item(self, key='ListItem'): - container = 'Container' if key == 'ListItem' else f'Container({key})' - item = infolabel(f'{container}.CurrentItem') - dbid = infolabel(f'{container}.ListItem.DBID') - dbtype = infolabel(f'{container}.ListItem.DBType') - return (container, item, dbid, dbtype) - - def _container_scrolling(self, key='ListItem'): - container = 'Container' if key == 'ListItem' else f'Container({key})' - return condition(f'{container}.Scrolling') - def onScreensaverActivated(self): self.idle = True