From 05b7954aa86967735d79067ad905d1da65c1be8e Mon Sep 17 00:00:00 2001 From: MoojMidge <56883549+MoojMidge@users.noreply.github.com> Date: Fri, 28 Jun 2024 21:05:43 +1000 Subject: [PATCH 01/15] Fix ask for video quality setting being inconsistently applied --- .../lib/youtube_plugin/kodion/settings/abstract_settings.py | 5 +++-- resources/lib/youtube_plugin/kodion/utils/methods.py | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/resources/lib/youtube_plugin/kodion/settings/abstract_settings.py b/resources/lib/youtube_plugin/kodion/settings/abstract_settings.py index da02fd67b..360b2989b 100644 --- a/resources/lib/youtube_plugin/kodion/settings/abstract_settings.py +++ b/resources/lib/youtube_plugin/kodion/settings/abstract_settings.py @@ -83,8 +83,9 @@ def fixed_video_quality(self, value=None): return self._VIDEO_QUALITY_MAP[_value] def ask_for_video_quality(self): - return (self.get_bool(SETTINGS.VIDEO_QUALITY_ASK, False) - or self.get_int(SETTINGS.MPD_STREAM_SELECT) == 4) + if self.use_mpd_videos(): + return self.get_int(SETTINGS.MPD_STREAM_SELECT) == 4 + return self.get_bool(SETTINGS.VIDEO_QUALITY_ASK, False) def fanart_selection(self): return self.get_int(SETTINGS.FANART_SELECTION, 2) diff --git a/resources/lib/youtube_plugin/kodion/utils/methods.py b/resources/lib/youtube_plugin/kodion/utils/methods.py index 5c9ff8494..2997a7b03 100644 --- a/resources/lib/youtube_plugin/kodion/utils/methods.py +++ b/resources/lib/youtube_plugin/kodion/utils/methods.py @@ -63,7 +63,7 @@ def select_stream(context, use_adaptive_formats=True): settings = context.get_settings() if ask_for_quality is None: - ask_for_quality = context.get_settings().ask_for_video_quality() + ask_for_quality = settings.ask_for_video_quality() if audio_only is None: audio_only = settings.audio_only() @@ -98,7 +98,7 @@ def _stream_sort(_stream): stream_list.sort(key=_stream_sort, reverse=True) num_streams = len(stream_list) - ask_for_quality = ask_for_quality and num_streams > 1 + ask_for_quality = ask_for_quality and num_streams >= 1 context.log_debug('Available streams: {0}'.format(num_streams)) for idx, stream in enumerate(stream_list): From e351abd9011b26a9aa9ed9f20606c575b739efff Mon Sep 17 00:00:00 2001 From: MoojMidge <56883549+MoojMidge@users.noreply.github.com> Date: Sat, 29 Jun 2024 10:56:52 +1000 Subject: [PATCH 02/15] Make My Subscriptions filter list setting a child of the My Subscriptions (Filtered) setting --- resources/settings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/settings.xml b/resources/settings.xml index 8863e2215..d62d4cf09 100644 --- a/resources/settings.xml +++ b/resources/settings.xml @@ -353,7 +353,7 @@ - + 0 From 7d08c0bc0c1902fcbf9f07a04b6babcbcb1e4a28 Mon Sep 17 00:00:00 2001 From: MoojMidge <56883549+MoojMidge@users.noreply.github.com> Date: Sat, 29 Jun 2024 11:00:26 +1000 Subject: [PATCH 03/15] Rename My Subscriptions plugin url - From: plugin://plugin.video.youtube/special/new_uploaded_videos_tv plugin://plugin.video.youtube/special/new_uploaded_videos_tv_filtered - To: plugin://plugin.video.youtube/special/my_subscriptions plugin://plugin.video.youtube/special/my_subscriptions_filtered - Old url retained for backwards compatibility, to be removed with Kodi v22 release --- .../lib/youtube_plugin/kodion/constants/const_paths.py | 2 +- .../lib/youtube_plugin/youtube/helper/yt_specials.py | 10 ++++------ resources/lib/youtube_plugin/youtube/provider.py | 4 ++-- 3 files changed, 7 insertions(+), 9 deletions(-) diff --git a/resources/lib/youtube_plugin/kodion/constants/const_paths.py b/resources/lib/youtube_plugin/kodion/constants/const_paths.py index 962cee461..3d1f3831a 100644 --- a/resources/lib/youtube_plugin/kodion/constants/const_paths.py +++ b/resources/lib/youtube_plugin/kodion/constants/const_paths.py @@ -24,7 +24,7 @@ HOME = '/home' LIKED_VIDEOS = '/channel/mine/playlist/LL' MY_PLAYLISTS = '/channel/mine/playlists' -MY_SUBSCRIPTIONS = '/special/new_uploaded_videos' +MY_SUBSCRIPTIONS = '/special/my_subscriptions' SUBSCRIPTIONS = '/subscriptions/list' API = '/youtube/api' diff --git a/resources/lib/youtube_plugin/youtube/helper/yt_specials.py b/resources/lib/youtube_plugin/youtube/helper/yt_specials.py index 8c8006d65..a296f06a1 100644 --- a/resources/lib/youtube_plugin/youtube/helper/yt_specials.py +++ b/resources/lib/youtube_plugin/youtube/helper/yt_specials.py @@ -294,7 +294,7 @@ def _process_saved_playlists_tv(provider, context, client): return tv.saved_playlists_to_items(provider, context, json_data) -def _process_new_uploaded_videos_tv(provider, context, client, filtered=False): +def _process_my_subscriptions(provider, context, client, filtered=False): context.set_content(CONTENT.VIDEO_CONTENT) function_cache = context.get_function_cache() @@ -326,11 +326,9 @@ def process(category, provider, context): return _process_recommendations(provider, context, client) if category == 'browse_channels': return _process_browse_channels(provider, context, client) - if category == 'new_uploaded_videos_tv': - return _process_new_uploaded_videos_tv(provider, context, client) - if category == 'new_uploaded_videos_tv_filtered': - return _process_new_uploaded_videos_tv( - provider, context, client, filtered=True + if category.startswith(('my_subscriptions', 'new_uploaded_videos_tv')): + return _process_my_subscriptions( + provider, context, client, filtered=category.endswith('_filtered'), ) if category == 'disliked_videos': if provider.is_logged_in(): diff --git a/resources/lib/youtube_plugin/youtube/provider.py b/resources/lib/youtube_plugin/youtube/provider.py index 78c3195a7..f172e81b5 100644 --- a/resources/lib/youtube_plugin/youtube/provider.py +++ b/resources/lib/youtube_plugin/youtube/provider.py @@ -1135,7 +1135,7 @@ def on_root(self, context, re_match): item_label = localize('my_subscriptions') my_subscriptions_item = DirectoryItem( ui.bold(item_label), - create_uri(('special', 'new_uploaded_videos_tv')), + create_uri(('special', 'my_subscriptions')), image='{media}/new_uploads.png', category_label=item_label, ) @@ -1145,7 +1145,7 @@ def on_root(self, context, re_match): # my subscriptions filtered my_subscriptions_filtered_item = DirectoryItem( localize('my_subscriptions.filtered'), - create_uri(('special', 'new_uploaded_videos_tv_filtered')), + create_uri(('special', 'my_subscriptions_filtered')), image='{media}/new_uploads.png', ) result.append(my_subscriptions_filtered_item) From 6f44e88c50edee88f16666ad4bb77e53a1d0b7a0 Mon Sep 17 00:00:00 2001 From: MoojMidge <56883549+MoojMidge@users.noreply.github.com> Date: Sat, 29 Jun 2024 23:58:51 +1000 Subject: [PATCH 04/15] Add notifications for creating/removing/clearing bookmarks #720 --- .../resource.language.en_gb/strings.po | 2 +- .../kodion/context/xbmc/xbmc_context.py | 9 +++--- .../youtube_plugin/kodion/items/menu_items.py | 12 ++++---- .../youtube_plugin/kodion/script_actions.py | 2 +- .../youtube_plugin/youtube/helper/utils.py | 12 ++++---- .../lib/youtube_plugin/youtube/provider.py | 29 ++++++++++++++++--- 6 files changed, 44 insertions(+), 22 deletions(-) diff --git a/resources/language/resource.language.en_gb/strings.po b/resources/language/resource.language.en_gb/strings.po index 2f78f4113..f3ed9d9b0 100644 --- a/resources/language/resource.language.en_gb/strings.po +++ b/resources/language/resource.language.en_gb/strings.po @@ -970,7 +970,7 @@ msgid "Switch to '%s' now?" msgstr "" msgctxt "#30666" -msgid "Removed '%s'" +msgid "%s removed" msgstr "" msgctxt "#30667" diff --git a/resources/lib/youtube_plugin/kodion/context/xbmc/xbmc_context.py b/resources/lib/youtube_plugin/kodion/context/xbmc/xbmc_context.py index 80783a453..40c353f46 100644 --- a/resources/lib/youtube_plugin/kodion/context/xbmc/xbmc_context.py +++ b/resources/lib/youtube_plugin/kodion/context/xbmc/xbmc_context.py @@ -59,12 +59,13 @@ class XbmcContext(AbstractContext): 'archive': 30105, 'are_you_sure': 30703, 'auto_remove_watch_later': 30515, + 'bookmark': 30101, + 'bookmark.channel': 30803, + 'bookmark.created': 21362, + 'bookmark.remove': 20404, 'bookmarks': 30100, - 'bookmarks.add': 30101, - 'bookmarks.add.channel': 30803, 'bookmarks.clear': 30801, 'bookmarks.clear.confirm': 30802, - 'bookmarks.remove': 20404, 'browse_channels': 30512, 'cancel': 30615, 'channels': 30500, @@ -212,7 +213,7 @@ class XbmcContext(AbstractContext): 'sign.twice.text': 30547, 'sign.twice.title': 30546, 'stats.commentCount': 30732, - # 'stats.favoriteCount': 30100, + # 'stats.favoriteCount': 1036, 'stats.likeCount': 30733, 'stats.viewCount': 30767, 'stream.alternate': 30747, diff --git a/resources/lib/youtube_plugin/kodion/items/menu_items.py b/resources/lib/youtube_plugin/kodion/items/menu_items.py index a7a1d2735..d442615fb 100644 --- a/resources/lib/youtube_plugin/kodion/items/menu_items.py +++ b/resources/lib/youtube_plugin/kodion/items/menu_items.py @@ -465,9 +465,9 @@ def history_reset_resume(context, video_id): ) -def bookmarks_add(context, item): +def bookmark_add(context, item): return ( - context.localize('bookmarks.add'), + context.localize('bookmark'), 'RunPlugin({0})'.format(context.create_uri( (PATHS.BOOKMARKS, 'add',), { @@ -478,9 +478,9 @@ def bookmarks_add(context, item): ) -def bookmarks_add_channel(context, channel_id, channel_name=''): +def bookmark_add_channel(context, channel_id, channel_name=''): return ( - (context.localize('bookmarks.add.channel') % ( + (context.localize('bookmark.channel') % ( context.get_ui().bold(channel_name) if channel_name else context.localize(19029) )), @@ -494,9 +494,9 @@ def bookmarks_add_channel(context, channel_id, channel_name=''): ) -def bookmarks_remove(context, item_id): +def bookmark_remove(context, item_id): return ( - context.localize('bookmarks.remove'), + context.localize('bookmark.remove'), 'RunPlugin({0})'.format(context.create_uri( (PATHS.BOOKMARKS, 'remove',), { diff --git a/resources/lib/youtube_plugin/kodion/script_actions.py b/resources/lib/youtube_plugin/kodion/script_actions.py index 27a0dd66a..f3234bd50 100644 --- a/resources/lib/youtube_plugin/kodion/script_actions.py +++ b/resources/lib/youtube_plugin/kodion/script_actions.py @@ -280,7 +280,7 @@ def switch_to_user(user): username = access_manager.get_username(user) if ui.on_remove_content(username): access_manager.remove_user(user) - ui.show_notification(localize('removed') % username, + ui.show_notification(localize('removed') % '"%s"' % username, localize('remove')) if user == 0: access_manager.add_user(username=localize('user.default'), diff --git a/resources/lib/youtube_plugin/youtube/helper/utils.py b/resources/lib/youtube_plugin/youtube/helper/utils.py index 8ca667cf0..84a96fd8f 100644 --- a/resources/lib/youtube_plugin/youtube/helper/utils.py +++ b/resources/lib/youtube_plugin/youtube/helper/utils.py @@ -221,7 +221,7 @@ def update_channel_infos(provider, context, channel_id_dict, if not in_bookmarks_list: context_menu.append( - menu_items.bookmarks_add_channel( + menu_items.bookmark_add_channel( context, channel_id ) ) @@ -289,7 +289,7 @@ def update_playlist_infos(provider, context, playlist_id_dict, menu_items.play_all_from_playlist( context, playlist_id ), - menu_items.bookmarks_add( + menu_items.bookmark_add( context, playlist_item ) if not in_bookmarks_list and channel_id != 'mine' else None, ] @@ -333,7 +333,7 @@ def update_playlist_infos(provider, context, playlist_id_dict, if not in_bookmarks_list and channel_id != 'mine': context_menu.append( # bookmark channel of the playlist - menu_items.bookmarks_add_channel( + menu_items.bookmark_add_channel( context, channel_id, channel_name ) ) @@ -697,7 +697,7 @@ def update_video_infos(provider, context, video_id_dict, if not in_bookmarks_list: context_menu.append( - menu_items.bookmarks_add( + menu_items.bookmark_add( context, video_item ) ) @@ -744,11 +744,11 @@ def update_video_infos(provider, context, video_id_dict, if not in_bookmarks_list: context_menu.append( # remove bookmarked channel of the video - menu_items.bookmarks_remove( + menu_items.bookmark_remove( context, item_id=channel_id ) if in_my_subscriptions_list else # bookmark channel of the video - menu_items.bookmarks_add_channel( + menu_items.bookmark_add_channel( context, channel_id, channel_name ) ) diff --git a/resources/lib/youtube_plugin/youtube/provider.py b/resources/lib/youtube_plugin/youtube/provider.py index f172e81b5..65f255a09 100644 --- a/resources/lib/youtube_plugin/youtube/provider.py +++ b/resources/lib/youtube_plugin/youtube/provider.py @@ -1451,7 +1451,7 @@ def on_bookmarks(self, context, re_match): if not isinstance(item, BaseItem): continue context_menu = [ - menu_items.bookmarks_remove( + menu_items.bookmark_remove( context, item_id ), menu_items.bookmarks_clear( @@ -1464,12 +1464,21 @@ def on_bookmarks(self, context, re_match): return bookmarks - if command == 'clear' and context.get_ui().on_yes_no_input( + ui = context.get_ui() + localize = context.localize + + if command == 'clear' and ui.on_yes_no_input( context.get_name(), - context.localize('bookmarks.clear.confirm') + localize('bookmarks.clear.confirm') ): context.get_bookmarks_list().clear() - context.get_ui().refresh_container() + ui.refresh_container() + + ui.show_notification( + localize('succeeded'), + time_ms=2500, + audible=False + ) return True item_id = params.get('item_id') @@ -1479,11 +1488,23 @@ def on_bookmarks(self, context, re_match): if command == 'add': item = params.get('item') context.get_bookmarks_list().add(item_id, item) + + ui.show_notification( + localize('bookmark.created'), + time_ms=2500, + audible=False + ) return True if command == 'remove': context.get_bookmarks_list().remove(item_id) context.get_ui().refresh_container() + + ui.show_notification( + localize('removed') % localize('bookmark'), + time_ms=2500, + audible=False + ) return True return False From b49bcddd3cefe221e191ce3bccaa547af6cd72ed Mon Sep 17 00:00:00 2001 From: MoojMidge <56883549+MoojMidge@users.noreply.github.com> Date: Sun, 30 Jun 2024 20:23:25 +1000 Subject: [PATCH 05/15] Misc tidy up --- .../kodion/abstract_provider.py | 2 +- .../kodion/context/abstract_context.py | 2 +- .../helper/signature/json_script_engine.py | 2 +- .../lib/youtube_plugin/youtube/helper/v3.py | 2 +- .../youtube/helper/yt_specials.py | 2 +- .../youtube/helper/yt_subscriptions.py | 2 +- .../youtube_plugin/youtube/helper/yt_video.py | 2 +- .../lib/youtube_plugin/youtube/provider.py | 61 ++++++++++--------- 8 files changed, 38 insertions(+), 37 deletions(-) diff --git a/resources/lib/youtube_plugin/kodion/abstract_provider.py b/resources/lib/youtube_plugin/kodion/abstract_provider.py index 613f9514e..64ece6560 100644 --- a/resources/lib/youtube_plugin/kodion/abstract_provider.py +++ b/resources/lib/youtube_plugin/kodion/abstract_provider.py @@ -162,7 +162,7 @@ def navigate(self, context): return result, options - raise KodionException("Mapping for path '%s' not found" % path) + raise KodionException('Mapping for path "%s" not found' % path) # noinspection PyUnusedLocal @staticmethod diff --git a/resources/lib/youtube_plugin/kodion/context/abstract_context.py b/resources/lib/youtube_plugin/kodion/context/abstract_context.py index 3fc802192..323bbc39d 100644 --- a/resources/lib/youtube_plugin/kodion/context/abstract_context.py +++ b/resources/lib/youtube_plugin/kodion/context/abstract_context.py @@ -72,9 +72,9 @@ class AbstractContext(object): 'refresh', } _FLOAT_PARAMS = { + 'end', 'seek', 'start', - 'end' } _LIST_PARAMS = { 'channel_ids', diff --git a/resources/lib/youtube_plugin/youtube/helper/signature/json_script_engine.py b/resources/lib/youtube_plugin/youtube/helper/signature/json_script_engine.py index 90fb3a9d2..975d20e5d 100644 --- a/resources/lib/youtube_plugin/youtube/helper/signature/json_script_engine.py +++ b/resources/lib/youtube_plugin/youtube/helper/signature/json_script_engine.py @@ -35,7 +35,7 @@ def execute(self, signature): if method: _signature = method(*params) else: - raise Exception("Unknown method '%s'" % func) + raise Exception('Unknown method: %s' % func) return _signature diff --git a/resources/lib/youtube_plugin/youtube/helper/v3.py b/resources/lib/youtube_plugin/youtube/helper/v3.py index 0336fbcae..c90eaea23 100644 --- a/resources/lib/youtube_plugin/youtube/helper/v3.py +++ b/resources/lib/youtube_plugin/youtube/helper/v3.py @@ -406,7 +406,7 @@ def response_to_items(provider, provider, context, json_data, item_filter ) else: - raise KodionException("Unknown kind '%s'" % kind) + raise KodionException('Unknown kind: %s' % kind) if item_filter: result = filter_videos(result, **item_filter) diff --git a/resources/lib/youtube_plugin/youtube/helper/yt_specials.py b/resources/lib/youtube_plugin/youtube/helper/yt_specials.py index a296f06a1..c2305fd2e 100644 --- a/resources/lib/youtube_plugin/youtube/helper/yt_specials.py +++ b/resources/lib/youtube_plugin/youtube/helper/yt_specials.py @@ -354,4 +354,4 @@ def process(category, provider, context): return _process_child_comments(provider, context, client) if category == 'saved_playlists': return _process_saved_playlists_tv(provider, context, client) - raise KodionException("YouTube special category '%s' not found" % category) + raise KodionException('YouTube special category "%s" not found' % category) diff --git a/resources/lib/youtube_plugin/youtube/helper/yt_subscriptions.py b/resources/lib/youtube_plugin/youtube/helper/yt_subscriptions.py index c4ceed0f9..aad82587a 100644 --- a/resources/lib/youtube_plugin/youtube/helper/yt_subscriptions.py +++ b/resources/lib/youtube_plugin/youtube/helper/yt_subscriptions.py @@ -92,4 +92,4 @@ def process(method, provider, context): return _process_add(provider, context, client) if method == 'remove': return _process_remove(provider, context, client) - raise KodionException("Unknown subscriptions method '%s'" % method) + raise KodionException('Unknown subscriptions method: %s' % method) diff --git a/resources/lib/youtube_plugin/youtube/helper/yt_video.py b/resources/lib/youtube_plugin/youtube/helper/yt_video.py index 8560463a1..1d4beec00 100644 --- a/resources/lib/youtube_plugin/youtube/helper/yt_video.py +++ b/resources/lib/youtube_plugin/youtube/helper/yt_video.py @@ -120,4 +120,4 @@ def process(method, provider, context, re_match): return _process_rate_video(provider, context, re_match) if method == 'more': return _process_more_for_video(context) - raise KodionException("Unknown method '%s'" % method) + raise KodionException('Unknown method: %s' % method) diff --git a/resources/lib/youtube_plugin/youtube/provider.py b/resources/lib/youtube_plugin/youtube/provider.py index 65f255a09..1b90fcd8d 100644 --- a/resources/lib/youtube_plugin/youtube/provider.py +++ b/resources/lib/youtube_plugin/youtube/provider.py @@ -1108,7 +1108,8 @@ def on_root(self, context, re_match): create_uri = context.create_uri localize = context.localize settings = context.get_settings() - ui = context.get_ui() + settings_bool = settings.get_bool + bold = context.get_ui().bold _ = self.get_client(context) # required for self.is_logged_in() logged_in = self.is_logged_in() @@ -1119,10 +1120,10 @@ def on_root(self, context, re_match): result = [] # sign in - if not logged_in and settings.get_bool('youtube.folder.sign.in.show', True): + if not logged_in and settings_bool('youtube.folder.sign.in.show', True): item_label = localize('sign.in') sign_in_item = DirectoryItem( - ui.bold(item_label), + bold(item_label), create_uri(('sign', 'in')), image='{media}/sign_in.png', action=True, @@ -1130,18 +1131,18 @@ def on_root(self, context, re_match): ) result.append(sign_in_item) - if settings.get_bool('youtube.folder.my_subscriptions.show', True): + if settings_bool('youtube.folder.my_subscriptions.show', True): # my subscription item_label = localize('my_subscriptions') my_subscriptions_item = DirectoryItem( - ui.bold(item_label), + bold(item_label), create_uri(('special', 'my_subscriptions')), image='{media}/new_uploads.png', category_label=item_label, ) result.append(my_subscriptions_item) - if settings.get_bool('youtube.folder.my_subscriptions_filtered.show', True): + if settings_bool('youtube.folder.my_subscriptions_filtered.show'): # my subscriptions filtered my_subscriptions_filtered_item = DirectoryItem( localize('my_subscriptions.filtered'), @@ -1156,7 +1157,7 @@ def on_root(self, context, re_match): local_history = settings.use_local_history() # Home / Recommendations - if logged_in and settings.get_bool('youtube.folder.recommendations.show', True): + if logged_in and settings_bool('youtube.folder.recommendations.show', True): recommendations_item = DirectoryItem( localize('recommendations'), create_uri(('special', 'recommendations')), @@ -1165,7 +1166,7 @@ def on_root(self, context, re_match): result.append(recommendations_item) # Related - if settings.get_bool('youtube.folder.related.show', True): + if settings_bool('youtube.folder.related.show', True): if history_id or local_history: related_item = DirectoryItem( localize('related_videos'), @@ -1175,7 +1176,7 @@ def on_root(self, context, re_match): result.append(related_item) # Trending - if settings.get_bool('youtube.folder.popular_right_now.show', True): + if settings_bool('youtube.folder.popular_right_now.show', True): trending_item = DirectoryItem( localize('trending'), create_uri(('special', 'popular_right_now')), @@ -1184,13 +1185,13 @@ def on_root(self, context, re_match): result.append(trending_item) # search - if settings.get_bool('youtube.folder.search.show', True): + if settings_bool('youtube.folder.search.show', True): search_item = SearchItem( context, ) result.append(search_item) - if settings.get_bool('youtube.folder.quick_search.show', True): + if settings_bool('youtube.folder.quick_search.show'): quick_search_item = NewSearchItem( context, name=localize('search.quick'), @@ -1198,7 +1199,7 @@ def on_root(self, context, re_match): ) result.append(quick_search_item) - if settings.get_bool('youtube.folder.quick_search_incognito.show', True): + if settings_bool('youtube.folder.quick_search_incognito.show'): quick_search_incognito_item = NewSearchItem( context, name=localize('search.quick.incognito'), @@ -1208,7 +1209,7 @@ def on_root(self, context, re_match): result.append(quick_search_incognito_item) # my location - if settings.get_bool('youtube.folder.my_location.show', True) and settings.get_location(): + if settings_bool('youtube.folder.my_location.show', True) and settings.get_location(): my_location_item = DirectoryItem( localize('my_location'), create_uri(('location', 'mine')), @@ -1217,7 +1218,7 @@ def on_root(self, context, re_match): result.append(my_location_item) # my channel - if logged_in and settings.get_bool('youtube.folder.my_channel.show', True): + if logged_in and settings_bool('youtube.folder.my_channel.show', True): my_channel_item = DirectoryItem( localize('my_channel'), create_uri(('channel', 'mine')), @@ -1226,7 +1227,7 @@ def on_root(self, context, re_match): result.append(my_channel_item) # watch later - if settings.get_bool('youtube.folder.watch_later.show', True): + if settings_bool('youtube.folder.watch_later.show', True): if watch_later_id: watch_later_item = DirectoryItem( localize('watch_later'), @@ -1249,7 +1250,7 @@ def on_root(self, context, re_match): result.append(watch_history_item) # liked videos - if logged_in and settings.get_bool('youtube.folder.liked_videos.show', True): + if logged_in and settings_bool('youtube.folder.liked_videos.show', True): resource_manager = self.get_resource_manager(context) playlists = resource_manager.get_related_playlists(channel_id='mine') if playlists and 'likes' in playlists: @@ -1267,7 +1268,7 @@ def on_root(self, context, re_match): result.append(liked_videos_item) # disliked videos - if logged_in and settings.get_bool('youtube.folder.disliked_videos.show', True): + if logged_in and settings_bool('youtube.folder.disliked_videos.show', True): disliked_videos_item = DirectoryItem( localize('video.disliked'), create_uri(('special', 'disliked_videos')), @@ -1276,7 +1277,7 @@ def on_root(self, context, re_match): result.append(disliked_videos_item) # history - if settings.get_bool('youtube.folder.history.show', False): + if settings_bool('youtube.folder.history.show', False): if history_id: watch_history_item = DirectoryItem( localize('history'), @@ -1299,7 +1300,7 @@ def on_root(self, context, re_match): result.append(watch_history_item) # (my) playlists - if logged_in and settings.get_bool('youtube.folder.playlists.show', True): + if logged_in and settings_bool('youtube.folder.playlists.show', True): playlists_item = DirectoryItem( localize('playlists'), create_uri(('channel', 'mine', 'playlists')), @@ -1309,7 +1310,7 @@ def on_root(self, context, re_match): # saved playlists # TODO: re-enable once functionality is restored - # if logged_in and settings.get_bool('youtube.folder.saved.playlists.show', True): + # if logged_in and settings_bool('youtube.folder.saved.playlists.show', True): # playlists_item = DirectoryItem( # localize('saved.playlists'), # create_uri(('special', 'saved_playlists')), @@ -1318,7 +1319,7 @@ def on_root(self, context, re_match): # result.append(playlists_item) # subscriptions - if logged_in and settings.get_bool('youtube.folder.subscriptions.show', True): + if logged_in and settings_bool('youtube.folder.subscriptions.show', True): subscriptions_item = DirectoryItem( localize('subscriptions'), create_uri(('subscriptions', 'list')), @@ -1327,7 +1328,7 @@ def on_root(self, context, re_match): result.append(subscriptions_item) # bookmarks - if settings.get_bool('youtube.folder.bookmarks.show', True): + if settings_bool('youtube.folder.bookmarks.show', True): bookmarks_item = DirectoryItem( localize('bookmarks'), create_uri((PATHS.BOOKMARKS, 'list')), @@ -1336,7 +1337,7 @@ def on_root(self, context, re_match): result.append(bookmarks_item) # browse channels - if logged_in and settings.get_bool('youtube.folder.browse_channels.show', True): + if logged_in and settings_bool('youtube.folder.browse_channels.show', True): browse_channels_item = DirectoryItem( localize('browse_channels'), create_uri(('special', 'browse_channels')), @@ -1345,7 +1346,7 @@ def on_root(self, context, re_match): result.append(browse_channels_item) # completed live events - if settings.get_bool('youtube.folder.completed.live.show', True): + if settings_bool('youtube.folder.completed.live.show', True): live_events_item = DirectoryItem( localize('live.completed'), create_uri(('special', 'completed_live')), @@ -1354,7 +1355,7 @@ def on_root(self, context, re_match): result.append(live_events_item) # upcoming live events - if settings.get_bool('youtube.folder.upcoming.live.show', True): + if settings_bool('youtube.folder.upcoming.live.show', True): live_events_item = DirectoryItem( localize('live.upcoming'), create_uri(('special', 'upcoming_live')), @@ -1363,7 +1364,7 @@ def on_root(self, context, re_match): result.append(live_events_item) # live events - if settings.get_bool('youtube.folder.live.show', True): + if settings_bool('youtube.folder.live.show', True): live_events_item = DirectoryItem( localize('live'), create_uri(('special', 'live')), @@ -1372,7 +1373,7 @@ def on_root(self, context, re_match): result.append(live_events_item) # switch user - if settings.get_bool('youtube.folder.switch.user.show', True): + if settings_bool('youtube.folder.switch.user.show', True): switch_user_item = DirectoryItem( localize('user.switch'), create_uri(('users', 'switch')), @@ -1382,7 +1383,7 @@ def on_root(self, context, re_match): result.append(switch_user_item) # sign out - if logged_in and settings.get_bool('youtube.folder.sign.out.show', True): + if logged_in and settings_bool('youtube.folder.sign.out.show', True): sign_out_item = DirectoryItem( localize('sign.out'), create_uri(('sign', 'out')), @@ -1391,7 +1392,7 @@ def on_root(self, context, re_match): ) result.append(sign_out_item) - if settings.get_bool('youtube.folder.settings.show', True): + if settings_bool('youtube.folder.settings.show', True): settings_menu_item = DirectoryItem( localize('setup_wizard'), create_uri(('config', 'setup_wizard')), @@ -1400,7 +1401,7 @@ def on_root(self, context, re_match): ) result.append(settings_menu_item) - if settings.get_bool('youtube.folder.settings.advanced.show', True): + if settings_bool('youtube.folder.settings.advanced.show'): settings_menu_item = DirectoryItem( localize('settings'), create_uri(('config', 'youtube')), From 4a8641a4d8faf76628617230abf4fa0bf82ef279 Mon Sep 17 00:00:00 2001 From: MoojMidge <56883549+MoojMidge@users.noreply.github.com> Date: Mon, 1 Jul 2024 08:44:26 +1000 Subject: [PATCH 06/15] Fix http server not sleeping after initial start of service --- .../kodion/monitors/service_monitor.py | 2 ++ .../lib/youtube_plugin/kodion/service_runner.py | 14 +++++++------- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/resources/lib/youtube_plugin/kodion/monitors/service_monitor.py b/resources/lib/youtube_plugin/kodion/monitors/service_monitor.py index 739f1ff9c..9b7ef586e 100644 --- a/resources/lib/youtube_plugin/kodion/monitors/service_monitor.py +++ b/resources/lib/youtube_plugin/kodion/monitors/service_monitor.py @@ -41,6 +41,7 @@ def __init__(self, context): self.httpd = None self.httpd_thread = None + self.httpd_sleep_allowed = True self.refresh = False self.interrupt = False @@ -103,6 +104,7 @@ def onNotification(self, sender, method, data): self.interrupt = True elif target == SERVER_WAKEUP: if not self.httpd and self.httpd_required(): + self.httpd_sleep_allowed = False self.start_httpd() if data.get('response_required'): self.set_property(WAKEUP, target) diff --git a/resources/lib/youtube_plugin/kodion/service_runner.py b/resources/lib/youtube_plugin/kodion/service_runner.py index 31f4c66ba..3d0405998 100644 --- a/resources/lib/youtube_plugin/kodion/service_runner.py +++ b/resources/lib/youtube_plugin/kodion/service_runner.py @@ -51,7 +51,7 @@ def run(): # wipe add-on temp folder on updates/restarts (subtitles, and mpd files) rm_dir(TEMP_PATH) - plugin_sleeping = httpd_can_sleep = False + plugin_sleeping = False plugin_sleep_timeout = httpd_sleep_timeout = 0 ping_period = 60 loop_num = sub_loop_num = 0 @@ -72,15 +72,15 @@ def run(): plugin_sleeping = clear_property(PLUGIN_SLEEPING) if not monitor.httpd: - httpd_can_sleep = False httpd_sleep_timeout = 0 elif idle: - if pop_property(SERVER_POST_START): - httpd_can_sleep = True + if monitor.httpd_sleep_allowed: + if httpd_sleep_timeout >= 30: + monitor.shutdown_httpd(sleep=True) + else: + if pop_property(SERVER_POST_START): + monitor.httpd_sleep_allowed = True httpd_sleep_timeout = 0 - if httpd_can_sleep and httpd_sleep_timeout >= 30: - httpd_can_sleep = False - monitor.shutdown_httpd(sleep=True) else: if httpd_sleep_timeout >= ping_period: httpd_sleep_timeout = 0 From f3d504ba7e76bbb5e1173264733d9732552ba650 Mon Sep 17 00:00:00 2001 From: MoojMidge <56883549+MoojMidge@users.noreply.github.com> Date: Tue, 2 Jul 2024 09:34:50 +1000 Subject: [PATCH 07/15] Add ability for sub-classes of Storage to define a subset of the sql statements they will use - Remainder of sql statement will be updated from the Storage parent class --- .../lib/youtube_plugin/kodion/sql_store/storage.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/resources/lib/youtube_plugin/kodion/sql_store/storage.py b/resources/lib/youtube_plugin/kodion/sql_store/storage.py index 984228c0f..92fc6e582 100644 --- a/resources/lib/youtube_plugin/kodion/sql_store/storage.py +++ b/resources/lib/youtube_plugin/kodion/sql_store/storage.py @@ -181,6 +181,20 @@ def __init__(self, for name, sql in Storage._sql.items() } self._base._sql.update(statements) + elif self._sql and '_partial' in self._sql: + statements = { + name: sql.format(table=self._table_name, + order_col='timestamp') + for name, sql in Storage._sql.items() + } + partial_statements = { + name: sql.format(table=self._table_name, + order_col='timestamp') + for name, sql in self._base._sql.items() + if not name.startswith('_') + } + statements.update(partial_statements) + self._base._sql = statements def set_max_item_count(self, max_item_count): self._max_item_count = max_item_count From 7ec0ae627ae198cf07b34c03f5dc2cbbd4922ba8 Mon Sep 17 00:00:00 2001 From: MoojMidge <56883549+MoojMidge@users.noreply.github.com> Date: Wed, 3 Jul 2024 09:29:14 +1000 Subject: [PATCH 08/15] Add methods to properly update rows in sqlite database - Also rename all methods consistently - Allow inserting rows with auto-incrementing primary key --- .../kodion/abstract_provider.py | 9 ++++---- .../kodion/items/xbmc/xbmc_items.py | 2 +- .../kodion/monitors/player_monitor.py | 6 +++--- .../kodion/sql_store/bookmarks_list.py | 8 +++---- .../kodion/sql_store/data_cache.py | 6 +++--- .../kodion/sql_store/playback_history.py | 9 +++++--- .../kodion/sql_store/search_history.py | 11 +++++----- .../kodion/sql_store/storage.py | 19 +++++++++++++++-- .../kodion/sql_store/watch_later_list.py | 4 ++-- .../youtube/helper/yt_playlist.py | 6 +++--- .../youtube/helper/yt_setup_wizard.py | 4 ++-- .../lib/youtube_plugin/youtube/provider.py | 21 ++++++++++++------- 12 files changed, 64 insertions(+), 41 deletions(-) diff --git a/resources/lib/youtube_plugin/kodion/abstract_provider.py b/resources/lib/youtube_plugin/kodion/abstract_provider.py index 64ece6560..1dc39cda1 100644 --- a/resources/lib/youtube_plugin/kodion/abstract_provider.py +++ b/resources/lib/youtube_plugin/kodion/abstract_provider.py @@ -266,12 +266,12 @@ def _internal_search(self, context, re_match): if not command or command == 'query': query = to_unicode(params.get('q', '')) if not params.get('incognito') and not params.get('channel_id'): - search_history.update(query) + search_history.add_item(query) return self.on_search(query, context, re_match) if command == 'remove': query = to_unicode(params.get('q', '')) - search_history.remove(query) + search_history.del_item(query) ui.refresh_container() return True @@ -281,7 +281,8 @@ def _internal_search(self, context, re_match): context.localize('search.rename'), query ) if result: - search_history.rename(query, new_query) + search_history.del_item(query) + search_history.add_item(new_query) ui.refresh_container() return True @@ -317,7 +318,7 @@ def _internal_search(self, context, re_match): data_cache.set_item('search_query', query) if not params.get('incognito') and not params.get('channel_id'): - search_history.update(query) + search_history.add_item(query) context.set_path(PATHS.SEARCH, 'query') return self.on_search(query, context, re_match) diff --git a/resources/lib/youtube_plugin/kodion/items/xbmc/xbmc_items.py b/resources/lib/youtube_plugin/kodion/items/xbmc/xbmc_items.py index 0dbf6fa11..08c350fa5 100644 --- a/resources/lib/youtube_plugin/kodion/items/xbmc/xbmc_items.py +++ b/resources/lib/youtube_plugin/kodion/items/xbmc/xbmc_items.py @@ -722,7 +722,7 @@ def video_listitem(context, if not set_play_count: video_id = video_item.video_id playback_history = context.get_playback_history() - playback_history.update(video_id, dict( + playback_history.set_item(video_id, dict( playback_history.get_item(video_id) or {}, play_count=int(not video_item.get_play_count()), played_time=0.0, diff --git a/resources/lib/youtube_plugin/kodion/monitors/player_monitor.py b/resources/lib/youtube_plugin/kodion/monitors/player_monitor.py index 311939f9f..6bd9989de 100644 --- a/resources/lib/youtube_plugin/kodion/monitors/player_monitor.py +++ b/resources/lib/youtube_plugin/kodion/monitors/player_monitor.py @@ -217,8 +217,8 @@ def run(self): status=(segment_end, segment_end, segment_end, 'stopped'), ) if use_local_history: - self._context.get_playback_history().update(self.video_id, - play_data) + self._context.get_playback_history().update_item(self.video_id, + play_data) self._context.send_notification(PLAYBACK_STOPPED, self.playback_data) self._context.log_debug('Playback stopped [{video_id}]:' @@ -246,7 +246,7 @@ def run(self): confirmed=True, ) else: - self._context.get_watch_later_list().remove(self.video_id) + self._context.get_watch_later_list().del_item(self.video_id) if logged_in and not refresh_only: history_id = access_manager.get_watch_history_id() diff --git a/resources/lib/youtube_plugin/kodion/sql_store/bookmarks_list.py b/resources/lib/youtube_plugin/kodion/sql_store/bookmarks_list.py index 9d5683095..1fe376796 100644 --- a/resources/lib/youtube_plugin/kodion/sql_store/bookmarks_list.py +++ b/resources/lib/youtube_plugin/kodion/sql_store/bookmarks_list.py @@ -27,14 +27,14 @@ def get_items(self): result = self._get_by_ids(process=from_json, as_dict=True) return result - def add(self, item_id, item): + def add_item(self, item_id, item): self._set(item_id, item) - def remove(self, item_id): + def del_item(self, item_id): self._remove(item_id) - def update(self, item_id, item, timestamp=None): - self._set(item_id, item, timestamp) + def update_item(self, item_id, item, timestamp=None): + self._update(item_id, item, timestamp) def _optimize_item_count(self, limit=-1, defer=False): return False diff --git a/resources/lib/youtube_plugin/kodion/sql_store/data_cache.py b/resources/lib/youtube_plugin/kodion/sql_store/data_cache.py index 6a62724a3..595813be8 100644 --- a/resources/lib/youtube_plugin/kodion/sql_store/data_cache.py +++ b/resources/lib/youtube_plugin/kodion/sql_store/data_cache.py @@ -63,11 +63,11 @@ def set_item(self, content_id, item): def set_items(self, items): self._set_many(items) - def remove(self, content_id): + def del_item(self, content_id): self._remove(content_id) - def update(self, content_id, item, timestamp=None): - self._set(content_id, item, timestamp) + def update_item(self, content_id, item, timestamp=None): + self._update(content_id, item, timestamp) def _optimize_item_count(self, limit=-1, defer=False): return False diff --git a/resources/lib/youtube_plugin/kodion/sql_store/playback_history.py b/resources/lib/youtube_plugin/kodion/sql_store/playback_history.py index 7077a3a95..8f679075f 100644 --- a/resources/lib/youtube_plugin/kodion/sql_store/playback_history.py +++ b/resources/lib/youtube_plugin/kodion/sql_store/playback_history.py @@ -40,11 +40,14 @@ def get_item(self, key): result = self._get(key, process=self._add_last_played) return result - def remove(self, video_id): + def set_item(self, video_id, play_data, timestamp=None): + self._set(video_id, play_data, timestamp) + + def del_item(self, video_id): self._remove(video_id) - def update(self, video_id, play_data, timestamp=None): - self._set(video_id, play_data, timestamp) + def update_item(self, video_id, play_data, timestamp=None): + self._update(video_id, play_data, timestamp) def _optimize_item_count(self, limit=-1, defer=False): return False diff --git a/resources/lib/youtube_plugin/kodion/sql_store/search_history.py b/resources/lib/youtube_plugin/kodion/sql_store/search_history.py index 1c504f468..66e5ad723 100644 --- a/resources/lib/youtube_plugin/kodion/sql_store/search_history.py +++ b/resources/lib/youtube_plugin/kodion/sql_store/search_history.py @@ -36,12 +36,11 @@ def get_items(self, process=None): def _make_id(search_text): return md5(search_text.encode('utf-8')).hexdigest() - def rename(self, old_search_text, new_search_text): - self.remove(old_search_text) - self.update(new_search_text) + def add_item(self, search_text): + self._set(self._make_id(search_text), search_text) - def remove(self, search_text): + def del_item(self, search_text): self._remove(self._make_id(search_text)) - def update(self, search_text, timestamp=None): - self._set(self._make_id(search_text), search_text, timestamp) + def update_item(self, search_text, timestamp=None): + self._update(self._make_id(search_text), search_text, timestamp) diff --git a/resources/lib/youtube_plugin/kodion/sql_store/storage.py b/resources/lib/youtube_plugin/kodion/sql_store/storage.py index 92fc6e582..6edb6ce2f 100644 --- a/resources/lib/youtube_plugin/kodion/sql_store/storage.py +++ b/resources/lib/youtube_plugin/kodion/sql_store/storage.py @@ -150,6 +150,12 @@ class Storage(object): ' (key, timestamp, value, size)' ' VALUES {{0}};' ), + 'update': ( + 'UPDATE' + ' {table}' + ' SET timestamp = ?, value = ?, size = ?' + ' WHERE key = ?;' + ), } def __init__(self, @@ -389,6 +395,11 @@ def _set_many(self, items, flatten=False): self._execute(cursor, query, many=(not flatten), values=values) self._optimize_file_size() + def _update(self, item_id, item, timestamp=None): + values = self._encode(item_id, item, timestamp, for_update=True) + with self as (db, cursor), db: + self._execute(cursor, self._sql['update'], values=values) + def clear(self, defer=False): query = self._sql['clear'] if defer: @@ -416,7 +427,7 @@ def _decode(obj, process=None, item=None): return decoded_obj @staticmethod - def _encode(key, obj, timestamp=None): + def _encode(key, obj, timestamp=None, for_update=False): timestamp = timestamp or since_epoch() blob = sqlite3.Binary(pickle.dumps( obj, protocol=pickle.HIGHEST_PROTOCOL @@ -424,7 +435,11 @@ def _encode(key, obj, timestamp=None): size = getattr(blob, 'nbytes', None) if not size: size = int(memoryview(blob).itemsize) * len(blob) - return str(key), timestamp, blob, size + if key: + if for_update: + return timestamp, blob, size, str(key) + return str(key), timestamp, blob, size + return timestamp, blob, size def _get(self, item_id, process=None, seconds=None, as_dict=False): with self as (db, cursor), db: diff --git a/resources/lib/youtube_plugin/kodion/sql_store/watch_later_list.py b/resources/lib/youtube_plugin/kodion/sql_store/watch_later_list.py index 1efa3b935..54150c577 100644 --- a/resources/lib/youtube_plugin/kodion/sql_store/watch_later_list.py +++ b/resources/lib/youtube_plugin/kodion/sql_store/watch_later_list.py @@ -27,8 +27,8 @@ def get_items(self): result = self._get_by_ids(process=from_json, as_dict=True) return result - def add(self, video_id, item): + def add_item(self, video_id, item): self._set(video_id, item) - def remove(self, video_id): + def del_item(self, video_id): self._remove(video_id) diff --git a/resources/lib/youtube_plugin/youtube/helper/yt_playlist.py b/resources/lib/youtube_plugin/youtube/helper/yt_playlist.py index 60a137dd0..965a76493 100644 --- a/resources/lib/youtube_plugin/youtube/helper/yt_playlist.py +++ b/resources/lib/youtube_plugin/youtube/helper/yt_playlist.py @@ -63,7 +63,7 @@ def _process_add_video(provider, context, keymap_action=False): if playlist_cache: cache_key, _, cached_last_page = playlist_cache[0] if cached_last_page: - data_cache.update(cache_key, None) + data_cache.update_item(cache_key, None) return True @@ -170,7 +170,7 @@ def _process_remove_playlist(provider, context): if channel_id: data_cache = context.get_data_cache() - data_cache.remove(channel_id) + data_cache.del_item(channel_id) ui.refresh_container() return False @@ -301,7 +301,7 @@ def _process_rename_playlist(provider, context): return False data_cache = context.get_data_cache() - data_cache.remove(playlist_id) + data_cache.del_item(playlist_id) ui.refresh_container() return False diff --git a/resources/lib/youtube_plugin/youtube/helper/yt_setup_wizard.py b/resources/lib/youtube_plugin/youtube/helper/yt_setup_wizard.py index 68d9cd8a8..ab7124cec 100644 --- a/resources/lib/youtube_plugin/youtube/helper/yt_setup_wizard.py +++ b/resources/lib/youtube_plugin/youtube/helper/yt_setup_wizard.py @@ -481,7 +481,7 @@ def _convert_old_search_item(value, item): ) items = old_search_db.get_items(process=_convert_old_search_item) for search in items: - search_history.update(search['text'], search['timestamp']) + search_history.update_item(search['text'], search['timestamp']) ui.show_notification(localize('succeeded')) context.execute( @@ -527,7 +527,7 @@ def _convert_old_history_item(value, item): items = old_history_db.get_items(process=_convert_old_history_item) for video_id, history in items.items(): timestamp = history.pop('timestamp', None) - playback_history.update(video_id, history, timestamp) + playback_history.update_item(video_id, history, timestamp) ui.show_notification(localize('succeeded')) context.execute( diff --git a/resources/lib/youtube_plugin/youtube/provider.py b/resources/lib/youtube_plugin/youtube/provider.py index 1b90fcd8d..e060ea913 100644 --- a/resources/lib/youtube_plugin/youtube/provider.py +++ b/resources/lib/youtube_plugin/youtube/provider.py @@ -1073,12 +1073,15 @@ def on_playback_history(self, context, re_match): return False if action == 'remove': - playback_history.remove(video_id) + playback_history.del_item(video_id) context.get_ui().refresh_container() return True play_data = playback_history.get_item(video_id) - if not play_data: + if play_data: + playback_history_method = playback_history.update_item + else: + playback_history_method = playback_history.set_item play_data = { 'play_count': 0, 'total_time': 0, @@ -1100,7 +1103,7 @@ def on_playback_history(self, context, re_match): play_data['played_time'] = 0 play_data['played_percent'] = 0 - playback_history.update(video_id, play_data) + playback_history_method(video_id, play_data) context.get_ui().refresh_container() return True @@ -1445,7 +1448,9 @@ def on_bookmarks(self, context, re_match): timestamp = items[channel_id] channel_item.set_bookmark_timestamp(timestamp) items[channel_id] = channel_item - bookmarks_list.update(channel_id, repr(channel_item), timestamp) + bookmarks_list.update_item( + channel_id, repr(channel_item), timestamp + ) bookmarks = [] for item_id, item in items.items(): @@ -1488,7 +1493,7 @@ def on_bookmarks(self, context, re_match): if command == 'add': item = params.get('item') - context.get_bookmarks_list().add(item_id, item) + context.get_bookmarks_list().add_item(item_id, item) ui.show_notification( localize('bookmark.created'), @@ -1498,7 +1503,7 @@ def on_bookmarks(self, context, re_match): return True if command == 'remove': - context.get_bookmarks_list().remove(item_id) + context.get_bookmarks_list().del_item(item_id) context.get_ui().refresh_container() ui.show_notification( @@ -1564,11 +1569,11 @@ def on_watch_later(self, context, re_match): if command == 'add': item = params.get('item') if item: - context.get_watch_later_list().add(video_id, item) + context.get_watch_later_list().add_item(video_id, item) return True if command == 'remove': - context.get_watch_later_list().remove(video_id) + context.get_watch_later_list().del_item(video_id) context.get_ui().refresh_container() return True From da804f49c0c5a46cb6cc59f51757c8f35b44e0fa Mon Sep 17 00:00:00 2001 From: MoojMidge <56883549+MoojMidge@users.noreply.github.com> Date: Wed, 3 Jul 2024 22:25:47 +1000 Subject: [PATCH 09/15] Use create_uri in XbmcContext.is_plugin_path - Test paths should use the same creation method as the original path they are being tested against --- .../youtube_plugin/kodion/context/abstract_context.py | 2 +- .../youtube_plugin/kodion/context/xbmc/xbmc_context.py | 9 +++------ 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/resources/lib/youtube_plugin/kodion/context/abstract_context.py b/resources/lib/youtube_plugin/kodion/context/abstract_context.py index 323bbc39d..de63edfe9 100644 --- a/resources/lib/youtube_plugin/kodion/context/abstract_context.py +++ b/resources/lib/youtube_plugin/kodion/context/abstract_context.py @@ -262,7 +262,7 @@ def create_uri(self, path=None, params=None): elif path: uri = path else: - uri = '/' if params else '/?' + uri = '/' uri = self._plugin_id.join(('plugin://', uri)) diff --git a/resources/lib/youtube_plugin/kodion/context/xbmc/xbmc_context.py b/resources/lib/youtube_plugin/kodion/context/xbmc/xbmc_context.py index 40c353f46..853125600 100644 --- a/resources/lib/youtube_plugin/kodion/context/xbmc/xbmc_context.py +++ b/resources/lib/youtube_plugin/kodion/context/xbmc/xbmc_context.py @@ -365,23 +365,20 @@ def get_region(self): pass # implement from abstract def is_plugin_path(self, uri, uri_path='', partial=False): - plugin = self.get_id() - if isinstance(uri_path, (list, tuple)): if partial: - paths = ['plugin://{0}/{1}'.format(plugin, path).rstrip('/') - for path in uri_path] + paths = [self.create_uri(path).rstrip('/') for path in uri_path] else: paths = [] for path in uri_path: - path = 'plugin://{0}/{1}'.format(plugin, path).rstrip('/') + path = self.create_uri(path).rstrip('/') paths.extend(( path + '/', path + '?' )) return uri.startswith(tuple(paths)) - uri_path = 'plugin://{0}/{1}'.format(plugin, uri_path).rstrip('/') + uri_path = self.create_uri(uri_path).rstrip('/') if not partial: uri_path = ( uri_path + '/', From 595b377cfed2d7eb53e0b8dc7e787e055c786c45 Mon Sep 17 00:00:00 2001 From: MoojMidge <56883549+MoojMidge@users.noreply.github.com> Date: Wed, 3 Jul 2024 22:28:54 +1000 Subject: [PATCH 10/15] Use constant for plugin play path --- .../youtube_plugin/kodion/constants/const_paths.py | 1 + .../kodion/context/abstract_context.py | 3 ++- .../lib/youtube_plugin/kodion/items/menu_items.py | 12 ++++++------ .../youtube_plugin/kodion/monitors/player_monitor.py | 3 ++- resources/lib/youtube_plugin/youtube/helper/tv.py | 3 ++- .../youtube/helper/url_to_item_converter.py | 7 ++++--- .../lib/youtube_plugin/youtube/helper/yt_playlist.py | 6 +++--- .../lib/youtube_plugin/youtube/helper/yt_video.py | 3 ++- resources/lib/youtube_plugin/youtube/provider.py | 2 +- 9 files changed, 23 insertions(+), 17 deletions(-) diff --git a/resources/lib/youtube_plugin/kodion/constants/const_paths.py b/resources/lib/youtube_plugin/kodion/constants/const_paths.py index 3d1f3831a..e0003afb4 100644 --- a/resources/lib/youtube_plugin/kodion/constants/const_paths.py +++ b/resources/lib/youtube_plugin/kodion/constants/const_paths.py @@ -25,6 +25,7 @@ LIKED_VIDEOS = '/channel/mine/playlist/LL' MY_PLAYLISTS = '/channel/mine/playlists' MY_SUBSCRIPTIONS = '/special/my_subscriptions' +PLAY = '/play' SUBSCRIPTIONS = '/subscriptions/list' API = '/youtube/api' diff --git a/resources/lib/youtube_plugin/kodion/context/abstract_context.py b/resources/lib/youtube_plugin/kodion/context/abstract_context.py index de63edfe9..a1cf01023 100644 --- a/resources/lib/youtube_plugin/kodion/context/abstract_context.py +++ b/resources/lib/youtube_plugin/kodion/context/abstract_context.py @@ -15,6 +15,7 @@ from .. import logger from ..compatibility import quote, to_str, urlencode from ..constants import ( + PATHS, PLAY_FORCE_AUDIO, PLAY_PROMPT_QUALITY, PLAY_PROMPT_SUBTITLES, @@ -337,7 +338,7 @@ def parse_params(self, params=None): elif param == 'action': if parsed_value in {'play_all', 'play_video'}: to_delete.append(param) - self.set_path('play') + self.set_path(PATHS.PLAY) continue elif param == 'videoid': to_delete.append(param) diff --git a/resources/lib/youtube_plugin/kodion/items/menu_items.py b/resources/lib/youtube_plugin/kodion/items/menu_items.py index d442615fb..2e3ac4269 100644 --- a/resources/lib/youtube_plugin/kodion/items/menu_items.py +++ b/resources/lib/youtube_plugin/kodion/items/menu_items.py @@ -75,7 +75,7 @@ def play_with(context, video_id): return ( context.localize('video.play.with'), 'RunPlugin({0})'.format(context.create_uri( - ('play',), + (PATHS.PLAY,), { 'video_id': video_id, PLAY_WITH: True, @@ -107,7 +107,7 @@ def play_all_from_playlist(context, playlist_id, video_id=''): return ( context.localize('playlist.play.from_here'), 'RunPlugin({0})'.format(context.create_uri( - ('play',), + (PATHS.PLAY,), { 'playlist_id': playlist_id, 'video_id': video_id, @@ -118,7 +118,7 @@ def play_all_from_playlist(context, playlist_id, video_id=''): return ( context.localize('playlist.play.all'), 'RunPlugin({0})'.format(context.create_uri( - ('play',), + (PATHS.PLAY,), { 'playlist_id': playlist_id, 'play': True, @@ -366,7 +366,7 @@ def play_with_subtitles(context, video_id): return ( context.localize('video.play.with_subtitles'), 'RunPlugin({0})'.format(context.create_uri( - ('play',), + (PATHS.PLAY,), { 'video_id': video_id, PLAY_PROMPT_SUBTITLES: True, @@ -379,7 +379,7 @@ def play_audio_only(context, video_id): return ( context.localize('video.play.audio_only'), 'RunPlugin({0})'.format(context.create_uri( - ('play',), + (PATHS.PLAY,), { 'video_id': video_id, PLAY_FORCE_AUDIO: True, @@ -392,7 +392,7 @@ def play_ask_for_quality(context, video_id): return ( context.localize('video.play.ask_for_quality'), 'RunPlugin({0})'.format(context.create_uri( - ('play',), + (PATHS.PLAY,), { 'video_id': video_id, PLAY_PROMPT_QUALITY: True, diff --git a/resources/lib/youtube_plugin/kodion/monitors/player_monitor.py b/resources/lib/youtube_plugin/kodion/monitors/player_monitor.py index 6bd9989de..03e435beb 100644 --- a/resources/lib/youtube_plugin/kodion/monitors/player_monitor.py +++ b/resources/lib/youtube_plugin/kodion/monitors/player_monitor.py @@ -16,6 +16,7 @@ from ..compatibility import xbmc from ..constants import ( BUSY_FLAG, + PATHS, PLAYBACK_STARTED, PLAYBACK_STOPPED, PLAYER_DATA, @@ -122,7 +123,7 @@ def run(self): break if (not current_file.startswith(playing_file) and not ( - self._context.is_plugin_path(current_file, 'play') + self._context.is_plugin_path(current_file, PATHS.PLAY) and video_id_param in current_file )) or total_time <= 0: self.stop() diff --git a/resources/lib/youtube_plugin/youtube/helper/tv.py b/resources/lib/youtube_plugin/youtube/helper/tv.py index 155670d9f..4dde234fd 100644 --- a/resources/lib/youtube_plugin/youtube/helper/tv.py +++ b/resources/lib/youtube_plugin/youtube/helper/tv.py @@ -11,6 +11,7 @@ from __future__ import absolute_import, division, unicode_literals from ..helper import utils +from ...kodion.constants import PATHS from ...kodion.items import DirectoryItem, NextPageItem, VideoItem @@ -31,7 +32,7 @@ def tv_videos_to_items(provider, context, json_data): video_id = item['id'] item_params['video_id'] = video_id video_item = VideoItem( - item['title'], context.create_uri(('play',), item_params) + item['title'], context.create_uri((PATHS.PLAY,), item_params) ) if incognito: video_item.set_play_count(0) diff --git a/resources/lib/youtube_plugin/youtube/helper/url_to_item_converter.py b/resources/lib/youtube_plugin/youtube/helper/url_to_item_converter.py index 1489dce86..737720607 100644 --- a/resources/lib/youtube_plugin/youtube/helper/url_to_item_converter.py +++ b/resources/lib/youtube_plugin/youtube/helper/url_to_item_converter.py @@ -14,6 +14,7 @@ from . import utils from ...kodion.compatibility import parse_qsl, urlsplit +from ...kodion.constants import PATHS from ...kodion.items import DirectoryItem, UriItem, VideoItem from ...kodion.utils import duration_to_seconds @@ -85,7 +86,7 @@ def add_url(self, url, context): video_id = new_params['video_id'] video_item = VideoItem( - '', context.create_uri(('play',), new_params) + '', context.create_uri((PATHS.PLAY,), new_params) ) self._video_id_dict[video_id] = video_item @@ -110,7 +111,7 @@ def add_url(self, url, context): return channel_item = VideoItem( - '', context.create_uri(('play',), new_params) + '', context.create_uri((PATHS.PLAY,), new_params) ) if live else DirectoryItem( '', context.create_uri(('channel', channel_id,), new_params) ) @@ -151,7 +152,7 @@ def get_items(self, provider, context, skip_title=False): if context.get_param('uri'): playlists_item = UriItem( context.create_uri( - ('play',), + (PATHS.PLAY,), { 'playlist_ids': ','.join(self._playlist_ids), 'play': True, diff --git a/resources/lib/youtube_plugin/youtube/helper/yt_playlist.py b/resources/lib/youtube_plugin/youtube/helper/yt_playlist.py index 965a76493..fc637c94b 100644 --- a/resources/lib/youtube_plugin/youtube/helper/yt_playlist.py +++ b/resources/lib/youtube_plugin/youtube/helper/yt_playlist.py @@ -12,7 +12,7 @@ from .utils import get_thumbnail from ...kodion import KodionException -from ...kodion.constants import CHANNEL_ID, PLAYLISTITEM_ID, PLAYLIST_ID +from ...kodion.constants import CHANNEL_ID, PATHS, PLAYLISTITEM_ID, PLAYLIST_ID from ...kodion.utils import find_video_id @@ -36,7 +36,7 @@ def _process_add_video(provider, context, keymap_action=False): video_id = context.get_param('video_id', '') if not video_id: - if context.is_plugin_path(listitem_path, 'play'): + if context.is_plugin_path(listitem_path, PATHS.PLAY): video_id = find_video_id(listitem_path) keymap_action = True if not video_id: @@ -187,7 +187,7 @@ def _process_select_playlist(provider, context): video_id = params.get('video_id', '') if not video_id: - if context.is_plugin_path(listitem_path, 'play'): + if context.is_plugin_path(listitem_path, PATHS.PLAY): video_id = find_video_id(listitem_path) if video_id: context.set_param('video_id', video_id) diff --git a/resources/lib/youtube_plugin/youtube/helper/yt_video.py b/resources/lib/youtube_plugin/youtube/helper/yt_video.py index 1d4beec00..eb270a457 100644 --- a/resources/lib/youtube_plugin/youtube/helper/yt_video.py +++ b/resources/lib/youtube_plugin/youtube/helper/yt_video.py @@ -11,6 +11,7 @@ from __future__ import absolute_import, division, unicode_literals from ...kodion import KodionException +from ...kodion.constants import PATHS from ...kodion.items import menu_items from ...kodion.utils import find_video_id @@ -28,7 +29,7 @@ def _process_rate_video(provider, context, re_match): try: video_id = re_match.group('video_id') except IndexError: - if context.is_plugin_path(listitem_path, 'play'): + if context.is_plugin_path(listitem_path, PATHS.PLAY): video_id = find_video_id(listitem_path) if not video_id: diff --git a/resources/lib/youtube_plugin/youtube/provider.py b/resources/lib/youtube_plugin/youtube/provider.py index e060ea913..a17a5bb49 100644 --- a/resources/lib/youtube_plugin/youtube/provider.py +++ b/resources/lib/youtube_plugin/youtube/provider.py @@ -666,7 +666,7 @@ def on_play(self, context, re_match): if ({'channel_id', 'playlist_id', 'playlist_ids', 'video_id'} .isdisjoint(param_keys)): listitem_path = context.get_listitem_info('FileNameAndPath') - if context.is_plugin_path(listitem_path, 'play'): + if context.is_plugin_path(listitem_path, PATHS.PLAY): video_id = find_video_id(listitem_path) if video_id: context.set_param('video_id', video_id) From b058abc2387c20256ea35fb620fa9275198251bf Mon Sep 17 00:00:00 2001 From: MoojMidge <56883549+MoojMidge@users.noreply.github.com> Date: Wed, 3 Jul 2024 22:40:39 +1000 Subject: [PATCH 11/15] Update v3 module methods - Allow for custom plugin kinds - plugin#pluginListResponse - plugin#pluginItem - plugin#commandItem - Allow passing context params as part of item creation using custom '_params' value of response item - Allow adding context menu items as part of item creation using custom '_context_menu' value of response item - Support for playlist response items without channelId --- .../lib/youtube_plugin/youtube/helper/v3.py | 152 +++++++++++------- 1 file changed, 95 insertions(+), 57 deletions(-) diff --git a/resources/lib/youtube_plugin/youtube/helper/v3.py b/resources/lib/youtube_plugin/youtube/helper/v3.py index c90eaea23..4f16e5151 100644 --- a/resources/lib/youtube_plugin/youtube/helper/v3.py +++ b/resources/lib/youtube_plugin/youtube/helper/v3.py @@ -49,14 +49,14 @@ def _process_list_response(provider, context, json_data, item_filter): result = [] - item_params = {} + new_params = {} params = context.get_params() incognito = params.get('incognito', False) if incognito: - item_params['incognito'] = incognito + new_params['incognito'] = incognito addon_id = params.get('addon_id', '') if addon_id: - item_params['addon_id'] = addon_id + new_params['addon_id'] = addon_id settings = context.get_settings() use_play_data = not incognito and settings.use_local_history() @@ -71,46 +71,59 @@ def _process_list_response(provider, context, json_data, item_filter): fanart_type = False for yt_item in yt_items: - is_youtube, kind = _parse_kind(yt_item) - if not is_youtube or not kind: - context.log_debug('v3 response: Item discarded, is_youtube=False') + kind, is_youtube, is_plugin, kind_type = _parse_kind(yt_item) + if not (is_youtube or is_plugin) or not kind_type: + context.log_debug('v3 item discarded: |%s|' % kind) continue - item_id = yt_item.get('id') - snippet = yt_item.get('snippet', {}) - title = snippet.get('title', context.localize('untitled')) - - thumbnails = snippet.get('thumbnails') - if not thumbnails and yt_item.get('_partial'): - thumbnails = { - thumb_type: { - 'url': thumb['url'].format(item_id, ''), - 'size': thumb['size'], - 'ratio': thumb['ratio'], + item_params = yt_item.get('_params', {}) + item_params.update(new_params) + + if is_youtube: + item_id = yt_item.get('id') + snippet = yt_item.get('snippet', {}) + title = snippet.get('title', context.localize('untitled')) + + thumbnails = snippet.get('thumbnails') + if not thumbnails and yt_item.get('_partial'): + thumbnails = { + thumb_type: { + 'url': thumb['url'].format(item_id, ''), + 'size': thumb['size'], + 'ratio': thumb['ratio'], + } + for thumb_type, thumb in THUMB_TYPES.items() } - for thumb_type, thumb in THUMB_TYPES.items() - } - image = get_thumbnail(thumb_size, thumbnails) - fanart = get_thumbnail(fanart_type, thumbnails) if fanart_type else None - - if kind == 'searchresult': - _, kind = _parse_kind(item_id) - if kind == 'video': + image = get_thumbnail(thumb_size, thumbnails) + if fanart_type: + fanart = get_thumbnail(fanart_type, thumbnails) + else: + fanart = None + + if kind_type == 'searchresult': + kind, _, _, kind_type = _parse_kind(item_id) + if kind_type == 'video' and 'videoId' in item_id: item_id = item_id['videoId'] - elif kind == 'playlist': + elif kind_type == 'playlist' and 'playlistId' in item_id: item_id = item_id['playlistId'] - elif kind == 'channel': + elif kind_type == 'channel' and 'channelId' in item_id: item_id = item_id['channelId'] + else: + item_id = None + if not item_id: + context.log_debug('v3 searchResult discarded: |%s|' % kind) + continue - if kind == 'video': + if kind_type == 'video': + item_params['video_id'] = item_id item_uri = context.create_uri( - ('play',), - dict(item_params, video_id=item_id), + (PATHS.PLAY,), + item_params, ) item = VideoItem(title, item_uri, image=image, fanart=fanart) video_id_dict[item_id] = item - elif kind == 'channel': + elif kind_type == 'channel': item_uri = context.create_uri( ('channel', item_id), item_params, @@ -122,14 +135,15 @@ def _process_list_response(provider, context, json_data, item_filter): channel_id=item_id) channel_id_dict[item_id] = item - elif kind == 'guidecategory': + elif kind_type == 'guidecategory': + item_params['guide_id'] = item_id item_uri = context.create_uri( ('special', 'browse_channels'), - dict(item_params, guide_id=item_id), + item_params, ) item = DirectoryItem(title, item_uri) - elif kind == 'subscription': + elif kind_type == 'subscription': subscription_id = item_id item_id = snippet['resourceId']['channelId'] # map channel id with subscription id - needed to unsubscribe @@ -147,17 +161,23 @@ def _process_list_response(provider, context, json_data, item_filter): subscription_id=subscription_id) channel_id_dict[item_id] = item - elif kind == 'playlist': + elif kind_type == 'playlist': # set channel id to 'mine' if the path is for a playlist of our own + channel_id = snippet.get('channelId') if context.get_path().startswith(PATHS.MY_PLAYLISTS): uri_channel_id = 'mine' - channel_id = snippet['channelId'] else: - uri_channel_id = channel_id = snippet['channelId'] - item_uri = context.create_uri( - ('channel', uri_channel_id, 'playlist', item_id), - item_params, - ) + uri_channel_id = channel_id + if uri_channel_id: + item_uri = context.create_uri( + ('channel', uri_channel_id, 'playlist', item_id), + item_params, + ) + else: + item_uri = context.create_uri( + ('playlist', item_id), + item_params, + ) item = DirectoryItem(title, item_uri, image=image, @@ -166,20 +186,21 @@ def _process_list_response(provider, context, json_data, item_filter): playlist_id=item_id) playlist_id_dict[item_id] = item - elif kind == 'playlistitem': + elif kind_type == 'playlistitem': playlistitem_id = item_id item_id = snippet['resourceId']['videoId'] # store the id of the playlistItem - needed for deleting item playlist_item_id_dict[item_id] = playlistitem_id + item_params['video_id'] = item_id item_uri = context.create_uri( - ('play',), - dict(item_params, video_id=item_id), + (PATHS.PLAY,), + item_params, ) item = VideoItem(title, item_uri, image=image, fanart=fanart) video_id_dict[item_id] = item - elif kind == 'activity': + elif kind_type == 'activity': details = yt_item['contentDetails'] activity_type = snippet['type'] if activity_type == 'recommendation': @@ -189,14 +210,15 @@ def _process_list_response(provider, context, json_data, item_filter): else: continue + item_params['video_id'] = item_id item_uri = context.create_uri( - ('play',), - dict(item_params, video_id=item_id), + (PATHS.PLAY,), + item_params, ) item = VideoItem(title, item_uri, image=image, fanart=fanart) video_id_dict[item_id] = item - elif kind == 'commentthread': + elif kind_type == 'commentthread': total_replies = snippet['totalReplyCount'] snippet = snippet['topLevelComment']['snippet'] if total_replies: @@ -208,14 +230,25 @@ def _process_list_response(provider, context, json_data, item_filter): item_uri = '' item = make_comment_item(context, snippet, item_uri, total_replies) - elif kind == 'comment': + elif kind_type == 'comment': item = make_comment_item(context, snippet, uri='') + elif kind_type == 'pluginitem': + item = DirectoryItem(**item_params) + + elif kind_type == 'commanditem': + item = CommandItem(context=context, **item_params) + else: - raise KodionException("Unknown kind '%s'" % kind) + item = None + raise KodionException('Unknown kind: %s' % kind) if not item: continue + + if '_context_menu' in yt_item: + item.add_context_menu(**yt_item['_context_menu']) + if isinstance(item, VideoItem): item.video_id = item_id if incognito: @@ -224,6 +257,7 @@ def _process_list_response(provider, context, json_data, item_filter): # match "Default" (unsorted) sort order position = snippet.get('position') or len(result) item.set_track_number(position + 1) + result.append(item) # this will also update the channel_id_dict with the correct channel_id @@ -385,6 +419,8 @@ def _fetch(resource): 'searchlistresponse', 'subscriptionlistresponse', 'videolistresponse', + # plugin kinds + 'pluginlistresponse', } @@ -395,12 +431,12 @@ def response_to_items(provider, reverse=False, process_next_page=True, item_filter=None): - is_youtube, kind = _parse_kind(json_data) - if not is_youtube: - context.log_debug('v3 response: Response discarded, is_youtube=False') + kind, is_youtube, is_plugin, kind_type = _parse_kind(json_data) + if not is_youtube and not is_plugin: + context.log_debug('v3 response discarded: |%s|' % kind) return [] - if kind in _KNOWN_RESPONSE_KINDS: + if kind_type in _KNOWN_RESPONSE_KINDS: item_filter = context.get_settings().item_filter(item_filter) result = _process_list_response( provider, context, json_data, item_filter @@ -475,7 +511,9 @@ def response_to_items(provider, def _parse_kind(item): - parts = item.get('kind', '').split('#') + kind = item.get('kind', '') + parts = kind.split('#') is_youtube = parts[0] == 'youtube' - kind = parts[1 if len(parts) > 1 else 0].lower() - return is_youtube, kind + is_plugin = parts[0] == 'plugin' + kind_type = parts[1 if len(parts) > 1 else 0].lower() + return kind, is_youtube, is_plugin, kind_type From e76a9c5355a4defd49507f2f6a55e8ac8015df14 Mon Sep 17 00:00:00 2001 From: MoojMidge <56883549+MoojMidge@users.noreply.github.com> Date: Thu, 4 Jul 2024 20:39:12 +1000 Subject: [PATCH 12/15] Ensure context menu items are not overwritten unnecessarily and are seperated from Kodi items --- resources/lib/youtube_plugin/youtube/helper/utils.py | 12 ++++++------ resources/lib/youtube_plugin/youtube/provider.py | 6 +++--- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/resources/lib/youtube_plugin/youtube/helper/utils.py b/resources/lib/youtube_plugin/youtube/helper/utils.py index 84a96fd8f..072136ade 100644 --- a/resources/lib/youtube_plugin/youtube/helper/utils.py +++ b/resources/lib/youtube_plugin/youtube/helper/utils.py @@ -227,7 +227,8 @@ def update_channel_infos(provider, context, channel_id_dict, ) if context_menu: - channel_item.add_context_menu(context_menu, replace=True) + context_menu.append(menu_items.separator()) + channel_item.add_context_menu(context_menu) # update channel mapping if channel_items_dict is not None: @@ -339,7 +340,8 @@ def update_playlist_infos(provider, context, playlist_id_dict, ) if context_menu: - playlist_item.add_context_menu(context_menu, replace=True) + context_menu.append(menu_items.separator()) + playlist_item.add_context_menu(context_menu) # update channel mapping if channel_items_dict is not None: @@ -808,10 +810,8 @@ def update_video_infos(provider, context, video_id_dict, ) if context_menu: - context_menu.append( - menu_items.separator(), - ) - video_item.add_context_menu(context_menu, replace=True) + context_menu.append(menu_items.separator()) + video_item.add_context_menu(context_menu) def update_play_info(provider, context, video_id, video_item, video_stream, diff --git a/resources/lib/youtube_plugin/youtube/provider.py b/resources/lib/youtube_plugin/youtube/provider.py index a17a5bb49..29a00a006 100644 --- a/resources/lib/youtube_plugin/youtube/provider.py +++ b/resources/lib/youtube_plugin/youtube/provider.py @@ -1242,7 +1242,7 @@ def on_root(self, context, re_match): context, watch_later_id ) ] - watch_later_item.add_context_menu(context_menu, replace=True) + watch_later_item.add_context_menu(context_menu) result.append(watch_later_item) else: watch_history_item = DirectoryItem( @@ -1267,7 +1267,7 @@ def on_root(self, context, re_match): context, playlists['likes'] ) ] - liked_videos_item.add_context_menu(context_menu, replace=True) + liked_videos_item.add_context_menu(context_menu) result.append(liked_videos_item) # disliked videos @@ -1292,7 +1292,7 @@ def on_root(self, context, re_match): context, history_id ) ] - watch_history_item.add_context_menu(context_menu, replace=True) + watch_history_item.add_context_menu(context_menu) result.append(watch_history_item) elif local_history: watch_history_item = DirectoryItem( From 985c88a6a7cc03617da6d0747cd7980eb9ee27de Mon Sep 17 00:00:00 2001 From: MoojMidge <56883549+MoojMidge@users.noreply.github.com> Date: Fri, 5 Jul 2024 06:05:19 +1000 Subject: [PATCH 13/15] Fix not reloading WL playlist if open after playback of video in playlist --- .../youtube/helper/yt_playlist.py | 33 ++++++++++++------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/resources/lib/youtube_plugin/youtube/helper/yt_playlist.py b/resources/lib/youtube_plugin/youtube/helper/yt_playlist.py index fc637c94b..2f7440891 100644 --- a/resources/lib/youtube_plugin/youtube/helper/yt_playlist.py +++ b/resources/lib/youtube_plugin/youtube/helper/yt_playlist.py @@ -12,6 +12,7 @@ from .utils import get_thumbnail from ...kodion import KodionException +from ...kodion.compatibility import parse_qsl, urlsplit from ...kodion.constants import CHANNEL_ID, PATHS, PLAYLISTITEM_ID, PLAYLIST_ID from ...kodion.utils import find_video_id @@ -74,7 +75,7 @@ def _process_remove_video(provider, video_id=None, video_name=None, confirmed=None): - listitem_path = context.get_listitem_info('FileNameAndPath') + container_uri = context.get_infolabel('Container.FolderPath') listitem_playlist_id = context.get_listitem_property(PLAYLIST_ID) listitem_video_id = context.get_listitem_property(PLAYLISTITEM_ID) listitem_video_name = context.get_listitem_info('Title') @@ -130,21 +131,31 @@ def _process_remove_video(provider, if not success: return False - path = params.pop('reload_path', False if confirmed else None) - if keymap_action and not confirmed: - context.get_ui().set_focus_next_item() - elif path is not False and context.is_plugin_path(listitem_path): - provider.reroute( - context, - path=path, - params=dict(params, refresh=params.get('refresh', 0) + 1), - ) - context.get_ui().show_notification( message=context.localize('playlist.removed_from'), time_ms=2500, audible=False ) + + if not context.is_plugin_path(container_uri): + return True + + if (keymap_action or video_id == listitem_video_id) and not confirmed: + context.get_ui().set_focus_next_item() + + if playlist_id in container_uri: + container_uri = urlsplit(container_uri) + path = container_uri.path + params = dict(parse_qsl(container_uri.query)) + else: + path = params.pop('reload_path', False if confirmed else None) + + if path is not False: + provider.reroute( + context, + path=path, + params=dict(params, refresh=int(params.get('refresh', 0)) + 1), + ) return True return False From ace0e799fb5b459c7a2e19e8c602eecfabc882b1 Mon Sep 17 00:00:00 2001 From: MoojMidge <56883549+MoojMidge@users.noreply.github.com> Date: Fri, 5 Jul 2024 06:12:06 +1000 Subject: [PATCH 14/15] Update method to register routes to allow removal of some wrapper methods --- .../kodion/abstract_provider.py | 14 +++-- .../kodion/monitors/player_monitor.py | 2 + .../youtube/helper/yt_playlist.py | 20 ++++++- .../youtube/helper/yt_specials.py | 17 +++++- .../youtube/helper/yt_subscriptions.py | 10 +++- .../youtube_plugin/youtube/helper/yt_video.py | 7 ++- .../lib/youtube_plugin/youtube/provider.py | 54 +++++++------------ 7 files changed, 82 insertions(+), 42 deletions(-) diff --git a/resources/lib/youtube_plugin/kodion/abstract_provider.py b/resources/lib/youtube_plugin/kodion/abstract_provider.py index 1dc39cda1..f297039fb 100644 --- a/resources/lib/youtube_plugin/kodion/abstract_provider.py +++ b/resources/lib/youtube_plugin/kodion/abstract_provider.py @@ -108,7 +108,12 @@ def register_path(self, re_path, method): :param method: method to be registered :return: """ - self._dict_path[re.compile(re_path, re.UNICODE)] = method + self._dict_path[re.compile(re_path, re.UNICODE)] = { + 'method': method, + 'bound': isinstance(getattr(method, '__self__', None), + self.__class__), + } + return method def run_wizard(self, context): settings = context.get_settings() @@ -142,7 +147,7 @@ def get_wizard_steps(self, context): def navigate(self, context): path = context.get_path() - for re_path, method in self._dict_path.items(): + for re_path, handler in self._dict_path.items(): re_match = re_path.search(path) if not re_match: continue @@ -151,7 +156,10 @@ def navigate(self, context): self.RESULT_CACHE_TO_DISC: True, self.RESULT_UPDATE_LISTING: False, } - result = method(context, re_match) + if handler['bound']: + result = handler['method'](context, re_match) + else: + result = handler['method'](self, context, re_match) if isinstance(result, tuple): result, new_options = result options.update(new_options) diff --git a/resources/lib/youtube_plugin/kodion/monitors/player_monitor.py b/resources/lib/youtube_plugin/kodion/monitors/player_monitor.py index 03e435beb..af6ca2f8d 100644 --- a/resources/lib/youtube_plugin/kodion/monitors/player_monitor.py +++ b/resources/lib/youtube_plugin/kodion/monitors/player_monitor.py @@ -238,6 +238,7 @@ def run(self): ) if playlist_item_id: self._provider.on_playlist_x( + self._provider, self._context, method='remove', category='video', @@ -268,6 +269,7 @@ def run(self): '/{0}/{1}/'.format(self.video_id, rating) ) self._provider.on_video_x( + self._provider, self._context, rating_match, method='rate', diff --git a/resources/lib/youtube_plugin/youtube/helper/yt_playlist.py b/resources/lib/youtube_plugin/youtube/helper/yt_playlist.py index 2f7440891..d0c04e965 100644 --- a/resources/lib/youtube_plugin/youtube/helper/yt_playlist.py +++ b/resources/lib/youtube_plugin/youtube/helper/yt_playlist.py @@ -343,20 +343,38 @@ def _playlist_id_change(context, playlist, method): return False -def process(method, category, provider, context, **kwargs): +def process(provider, + context, + re_match=None, + method=None, + category=None, + **kwargs): + if re_match: + if method is None: + method = re_match.group('method') + if category is None: + category = re_match.group('category') + if method == 'add' and category == 'video': return _process_add_video(provider, context) + if method == 'remove' and category == 'video': return _process_remove_video(provider, context, **kwargs) + if method == 'remove' and category == 'playlist': return _process_remove_playlist(provider, context) + if method == 'select' and category == 'playlist': return _process_select_playlist(provider, context) + if method == 'rename' and category == 'playlist': return _process_rename_playlist(provider, context) + if method in {'set', 'remove'} and category == 'watch_later': return _playlist_id_change(context, category, method) + if method in {'set', 'remove'} and category == 'history': return _playlist_id_change(context, category, method) + raise KodionException('Unknown category |{0}| or method |{1}|' .format(category, method)) diff --git a/resources/lib/youtube_plugin/youtube/helper/yt_specials.py b/resources/lib/youtube_plugin/youtube/helper/yt_specials.py index c2305fd2e..d9565fd00 100644 --- a/resources/lib/youtube_plugin/youtube/helper/yt_specials.py +++ b/resources/lib/youtube_plugin/youtube/helper/yt_specials.py @@ -314,44 +314,59 @@ def _process_my_subscriptions(provider, context, client, filtered=False): return v3.response_to_items(provider, context, json_data) -def process(category, provider, context): +def process(provider, context, re_match): + category = re_match.group('category') + # required for provider.is_logged_in() client = provider.get_client(context) if category == 'related_videos': return _process_related_videos(provider, context, client) + if category == 'popular_right_now': return _process_trending(provider, context, client) + if category == 'recommendations': return _process_recommendations(provider, context, client) + if category == 'browse_channels': return _process_browse_channels(provider, context, client) + if category.startswith(('my_subscriptions', 'new_uploaded_videos_tv')): return _process_my_subscriptions( provider, context, client, filtered=category.endswith('_filtered'), ) + if category == 'disliked_videos': if provider.is_logged_in(): return _process_disliked_videos(provider, context, client) return UriItem(context.create_uri(('sign', 'in'))) + if category == 'live': return _process_live_events( provider, context, client, event_type='live' ) + if category == 'upcoming_live': return _process_live_events( provider, context, client, event_type='upcoming' ) + if category == 'completed_live': return _process_live_events( provider, context, client, event_type='completed' ) + if category == 'description_links': return _process_description_links(provider, context) + if category == 'parent_comments': return _process_parent_comments(provider, context, client) + if category == 'child_comments': return _process_child_comments(provider, context, client) + if category == 'saved_playlists': return _process_saved_playlists_tv(provider, context, client) + raise KodionException('YouTube special category "%s" not found' % category) diff --git a/resources/lib/youtube_plugin/youtube/helper/yt_subscriptions.py b/resources/lib/youtube_plugin/youtube/helper/yt_subscriptions.py index aad82587a..f9cea5627 100644 --- a/resources/lib/youtube_plugin/youtube/helper/yt_subscriptions.py +++ b/resources/lib/youtube_plugin/youtube/helper/yt_subscriptions.py @@ -12,11 +12,12 @@ from ..helper import v3 from ...kodion import KodionException -from ...kodion.constants import CHANNEL_ID, SUBSCRIPTION_ID +from ...kodion.constants import CHANNEL_ID, CONTENT, SUBSCRIPTION_ID from ...kodion.items import UriItem def _process_list(provider, context, client): + context.set_content(CONTENT.LIST_CONTENT) json_data = client.get_subscription( 'mine', page_token=context.get_param('page_token', '') ) @@ -80,7 +81,9 @@ def _process_remove(_provider, context, client): return True -def process(method, provider, context): +def process(provider, context, re_match): + method = re_match.group('method') + # we need a login client = provider.get_client(context) if not provider.is_logged_in(): @@ -88,8 +91,11 @@ def process(method, provider, context): if method == 'list': return _process_list(provider, context, client) + if method == 'add': return _process_add(provider, context, client) + if method == 'remove': return _process_remove(provider, context, client) + raise KodionException('Unknown subscriptions method: %s' % method) diff --git a/resources/lib/youtube_plugin/youtube/helper/yt_video.py b/resources/lib/youtube_plugin/youtube/helper/yt_video.py index eb270a457..a877a2b9a 100644 --- a/resources/lib/youtube_plugin/youtube/helper/yt_video.py +++ b/resources/lib/youtube_plugin/youtube/helper/yt_video.py @@ -116,9 +116,14 @@ def _process_more_for_video(context): context.execute(result) -def process(method, provider, context, re_match): +def process(provider, context, re_match=None, method=None): + if re_match and method is None: + method = re_match.group('method') + if method == 'rate': return _process_rate_video(provider, context, re_match) + if method == 'more': return _process_more_for_video(context) + raise KodionException('Unknown method: %s' % method) diff --git a/resources/lib/youtube_plugin/youtube/provider.py b/resources/lib/youtube_plugin/youtube/provider.py index 29a00a006..9310de6f1 100644 --- a/resources/lib/youtube_plugin/youtube/provider.py +++ b/resources/lib/youtube_plugin/youtube/provider.py @@ -64,6 +64,26 @@ def __init__(self): self._api_check = None self._logged_in = False + self.on_video_x = self.register_path( + '^/video/(?P[^/]+)/?$', + yt_video.process, + ) + + self.on_playlist_x = self.register_path( + '^/playlist/(?P[^/]+)/(?P[^/]+)/?$', + yt_playlist.process, + ) + + self.register_path( + '^/special/(?P[^/]+)/?$', + yt_specials.process, + ) + + self.register_path( + '^/subscriptions/(?P[^/]+)/?$', + yt_subscriptions.process, + ) + atexit.register(self.tear_down) def get_wizard_steps(self, context): @@ -706,40 +726,6 @@ def on_play(self, context, re_match): return yt_play.play_channel_live(self, context) return False - @RegisterProviderPath('^/video/(?P[^/]+)/?$') - def on_video_x(self, context, re_match=None, method=None): - if method is None: - method = re_match.group('method') - return yt_video.process(method, self, context, re_match) - - @RegisterProviderPath('^/playlist/(?P[^/]+)/(?P[^/]+)/?$') - def on_playlist_x(self, - context, - re_match=None, - method=None, - category=None, - **kwargs): - if method is None: - method = re_match.group('method') - if category is None: - category = re_match.group('category') - return yt_playlist.process(method, category, self, context, **kwargs) - - @RegisterProviderPath('^/subscriptions/(?P[^/]+)/?$') - def _on_subscriptions(self, context, re_match): - method = re_match.group('method') - subscriptions = yt_subscriptions.process(method, self, context) - - if method == 'list': - context.set_content(CONTENT.LIST_CONTENT) - - return subscriptions - - @RegisterProviderPath('^/special/(?P[^/]+)/?$') - def _on_yt_specials(self, context, re_match): - category = re_match.group('category') - return yt_specials.process(category, self, context) - @RegisterProviderPath('^/users/(?P[^/]+)/?$') def _on_users(self, _context, re_match): action = re_match.group('action') From 72d7d118c26737496d969cd52691608e85c651f2 Mon Sep 17 00:00:00 2001 From: MoojMidge <56883549+MoojMidge@users.noreply.github.com> Date: Fri, 5 Jul 2024 16:56:37 +1000 Subject: [PATCH 15/15] Version bump v7.0.9+beta.2 --- addon.xml | 2 +- changelog.txt | 21 ++++++++++++++++++++- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/addon.xml b/addon.xml index 8a4169d11..e12e26256 100644 --- a/addon.xml +++ b/addon.xml @@ -1,5 +1,5 @@ - + diff --git a/changelog.txt b/changelog.txt index a982ebc6f..af54f637c 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,7 +1,26 @@ +## v7.0.9+beta.2 +### Fixed +- Fix ask for video quality setting being inconsistently applied +- Fix http server not sleeping after initial start of service #746 #801 +- Fix not reloading WL playlist if open after playback of video in WL + +### Changed +- Rename My Subscriptions plugin url + - From: + - plugin://plugin.video.youtube/special/new_uploaded_videos_tv + - plugin://plugin.video.youtube/special/new_uploaded_videos_tv_filtered + - To: + - plugin://plugin.video.youtube/special/my_subscriptions + - plugin://plugin.video.youtube/special/my_subscriptions_filtered + - Old url retained for backwards compatibility, to be removed with Kodi v22 + +### New +- Add notifications for creating/removing/clearing bookmarks #720 + ## v7.0.9+beta.1 ### Fixed - Fix renaming playlists -- Improve https server wakeup #746 #801 +- Improve http server wakeup #746 #801 ### Changed - Make live query parameter optional when playing channel live stream