diff --git a/addon.xml b/addon.xml index a486d567b..e90ab8319 100644 --- a/addon.xml +++ b/addon.xml @@ -1,5 +1,5 @@ - + diff --git a/changelog.txt b/changelog.txt index f5c804f98..45bb50308 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,3 +1,19 @@ +## v7.1.1+beta.4 +### Fixed +- Fix incorrectly determining Kodi release name +- Fix search window history navigation when using direct links + +### Changed +- Improve https server sleep and wakeup #810 #951 +- Update Setup Wizard to disable all alternative player settings if not required #938 + +### New +- Improvements to searching + - Add context menu items for changing sort order of saved searches #172 + - Allow search parameters to be stored per search #172 #689 + - Allow additional optional search parameters to be used #689 + - Improve parsing of date offset when searching for completed live events + ## v7.1.1+beta.3 ### Fixed - Fix not listing full stream details in selection dialog diff --git a/resources/language/resource.language.en_gb/strings.po b/resources/language/resource.language.en_gb/strings.po index d00ce3398..3a3eb6957 100644 --- a/resources/language/resource.language.en_gb/strings.po +++ b/resources/language/resource.language.en_gb/strings.po @@ -39,7 +39,7 @@ msgstr "" # Kodion Settings msgctxt "#30000" -msgid "General" +msgid "" msgstr "" msgctxt "#30001" @@ -153,7 +153,7 @@ msgid "Configure %s?" msgstr "" msgctxt "#30031" -msgid "Advanced" +msgid "" msgstr "" msgctxt "#30032" @@ -220,7 +220,7 @@ msgid "Watch Later" msgstr "" msgctxt "#30108" -msgid "Remove" +msgid "" msgstr "" msgctxt "#30109" @@ -240,7 +240,7 @@ msgid "Sign Out" msgstr "" msgctxt "#30113" -msgid "Rename" +msgid "" msgstr "" msgctxt "#30114" @@ -260,7 +260,7 @@ msgid "Remove \"%s\"?" msgstr "" msgctxt "#30118" -msgid "Delete" +msgid "" msgstr "" msgctxt "#30119" @@ -306,11 +306,11 @@ msgstr "" # empty strings from id 30206 to 30499 msgctxt "#30500" -msgid "Channels" +msgid "" msgstr "" msgctxt "#30501" -msgid "Playlists" +msgid "" msgstr "" msgctxt "#30502" @@ -390,15 +390,15 @@ msgid "Add to..." msgstr "" msgctxt "#30521" -msgid "Select playlist" +msgid "" msgstr "" msgctxt "#30522" -msgid "New playlist..." +msgid "" msgstr "" msgctxt "#30523" -msgid "Language" +msgid "" msgstr "" msgctxt "#30524" @@ -430,11 +430,11 @@ msgid "I dislike this" msgstr "" msgctxt "#30531" -msgid "Play all" +msgid "" msgstr "" msgctxt "#30532" -msgid "Default" +msgid "" msgstr "" msgctxt "#30533" @@ -442,7 +442,7 @@ msgid "Reverse" msgstr "" msgctxt "#30534" -msgid "Shuffle" +msgid "" msgstr "" msgctxt "#30535" @@ -462,7 +462,7 @@ msgid "Disliked Videos" msgstr "" msgctxt "#30539" -msgid "Live" +msgid "" msgstr "" msgctxt "#30540" @@ -478,7 +478,7 @@ msgid "rtmpe streams are not supported" msgstr "" msgctxt "#30543" -msgid "Refresh" +msgid "" msgstr "" msgctxt "#30544" @@ -498,7 +498,7 @@ msgid "You may be prompted to enable two applications so that YouTube is functio msgstr "" msgctxt "#30548" -msgid "More..." +msgid "" msgstr "" msgctxt "#30549" @@ -506,7 +506,7 @@ msgid "No videos streams found" msgstr "" msgctxt "#30550" -msgid "Region" +msgid "" msgstr "" msgctxt "#30551" @@ -546,11 +546,11 @@ msgid "Delete settings.xml" msgstr "" msgctxt "#30560" -msgid "Subtitle language" +msgid "" msgstr "" msgctxt "#30561" -msgid "None" +msgid "" msgstr "" msgctxt "#30562" @@ -570,7 +570,7 @@ msgid "" msgstr "" msgctxt "#30566" -msgid "Prompt" +msgid "" msgstr "" msgctxt "#30567" @@ -614,7 +614,7 @@ msgid "Failed" msgstr "" msgctxt "#30577" -msgid "Settings" +msgid "" msgstr "" msgctxt "#30578" @@ -638,7 +638,7 @@ msgid "Autoplay suggested videos" msgstr "" msgctxt "#30583" -msgid "Automatic" +msgid "" msgstr "" msgctxt "#30584" @@ -698,7 +698,7 @@ msgid "Updated: %s" msgstr "" msgctxt "#30598" -msgid "Personal API keys enabled" +msgid "" msgstr "" msgctxt "#30599" @@ -706,7 +706,7 @@ msgid "Failed to enable personal API keys. Missing: %s" msgstr "" msgctxt "#30600" -msgid "Subtitles" +msgid "" msgstr "" msgctxt "#30601" @@ -766,7 +766,7 @@ msgid "" msgstr "" msgctxt "#30615" -msgid "Cancel" +msgid "" msgstr "" msgctxt "#30616" @@ -782,7 +782,7 @@ msgid "" msgstr "" msgctxt "#30619" -msgid "Port" +msgid "" msgstr "" msgctxt "#30620" @@ -826,7 +826,7 @@ msgid "IP whitelist (comma delimited)" msgstr "" msgctxt "#30630" -msgid "Save" +msgid "" msgstr "" msgctxt "#30631" @@ -1118,7 +1118,7 @@ msgid "Play with subtitles" msgstr "" msgctxt "#30703" -msgid "Are you sure?" +msgid "" msgstr "" msgctxt "#30704" @@ -1254,11 +1254,11 @@ msgid "Shorts (1 minute or less)" msgstr "" msgctxt "#30737" -msgid "Episodes" +msgid "" msgstr "" msgctxt "#30738" -msgid "Videos" +msgid "" msgstr "" msgctxt "#30739" diff --git a/resources/lib/youtube_plugin/kodion/constants/__init__.py b/resources/lib/youtube_plugin/kodion/constants/__init__.py index 796765928..845353169 100644 --- a/resources/lib/youtube_plugin/kodion/constants/__init__.py +++ b/resources/lib/youtube_plugin/kodion/constants/__init__.py @@ -36,7 +36,11 @@ '0': False, '1': True, 'false': False, + 'False': False, 'true': True, + 'True': True, + 'None': None, + 'null': None, } # Flags diff --git a/resources/lib/youtube_plugin/kodion/context/abstract_context.py b/resources/lib/youtube_plugin/kodion/context/abstract_context.py index add2755ff..76ba7a02e 100644 --- a/resources/lib/youtube_plugin/kodion/context/abstract_context.py +++ b/resources/lib/youtube_plugin/kodion/context/abstract_context.py @@ -120,6 +120,33 @@ class AbstractContext(Logger): _STRING_BOOL_PARAMS = { 'reload_path', } + _SEARCH_PARAMS = { + 'forMine', + 'channelId', + 'channelType', + 'eventType', + 'location', + 'locationRadius', + 'maxResults', + 'order', + 'pageToken' + 'publishedAfter', + 'publishedBefore', + 'q', + 'safeSearch', + 'topicId', + 'type', + 'videoCaption', + 'videoCategoryId', + 'videoDefinition', + 'videoDimension', + 'videoDuration', + 'videoEmbeddable', + 'videoLicense', + 'videoPaidProductPlacement', + 'videoSyndicated', + 'videoType', + } def __init__(self, path='/', params=None, plugin_id=''): self._access_manager = None @@ -322,15 +349,13 @@ def parse_params(self, params, update=True): for param, value in params.items(): try: if param in self._BOOL_PARAMS: - parsed_value = VALUE_FROM_STR.get(str(value).lower(), False) + parsed_value = VALUE_FROM_STR.get(str(value), False) elif param in self._INT_PARAMS: - parsed_value = None - if param in self._INT_BOOL_PARAMS: - parsed_value = VALUE_FROM_STR.get(str(value).lower()) - if parsed_value is None: - parsed_value = int(value) - else: - parsed_value = int(parsed_value) + parsed_value = int( + (VALUE_FROM_STR.get(str(value), value) or 0) + if param in self._INT_BOOL_PARAMS else + value + ) elif param in self._FLOAT_PARAMS: parsed_value = float(value) elif param in self._LIST_PARAMS: @@ -343,7 +368,7 @@ def parse_params(self, params, update=True): parsed_value = to_str(value) if param in self._STRING_BOOL_PARAMS: parsed_value = VALUE_FROM_STR.get( - parsed_value.lower(), parsed_value + parsed_value, parsed_value ) # process and translate deprecated parameters elif param == 'action': @@ -357,8 +382,15 @@ def parse_params(self, params, update=True): elif params == 'playlist': to_delete.append(param) param = 'playlist_id' + elif param in self._SEARCH_PARAMS: + parsed_value = to_str(value) + parsed_value = VALUE_FROM_STR.get( + parsed_value, parsed_value + ) + if not parsed_value: + raise ValueError else: - self.log_debug('Unknown parameter - |{0}: {1}|'.format( + self.log_debug('Unknown parameter - |{0}: {1!r}|'.format( param, value )) to_delete.append(param) 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 1c5651747..8d58df5d6 100644 --- a/resources/lib/youtube_plugin/kodion/context/xbmc/xbmc_context.py +++ b/resources/lib/youtube_plugin/kodion/context/xbmc/xbmc_context.py @@ -50,14 +50,21 @@ class XbmcContext(AbstractContext): _KODI_UI_SUBTITLE_OPTIONS = None LOCAL_MAP = { + 'api.config': 30634, + 'api.config.bookmark': 30638, + 'api.config.not_updated': 30635, + 'api.config.save': 190, + 'api.config.updated': 30631, 'api.id': 30202, 'api.key': 30201, 'api.key.incorrect': 30648, - 'api.personal.enabled': 30598, + 'api.personal.enabled': 30636, + 'api.personal.disabled': 30637, 'api.personal.failed': 30599, 'api.secret': 30203, 'archive': 30105, - 'are_you_sure': 30703, + 'are_you_sure': 750, + 'ask': 863, 'auto_remove_watch_later': 30515, 'bookmark': 30101, 'bookmark.channel': 30803, @@ -67,8 +74,9 @@ class XbmcContext(AbstractContext): 'bookmarks.clear': 30801, 'bookmarks.clear.confirm': 30802, 'browse_channels': 30512, - 'cancel': 30615, - 'channels': 30500, + 'cancel': 222, + 'channel': 19029, + 'channels': 19019, 'client.id.incorrect': 30649, 'client.ip': 30700, 'client.ip.failed': 30701, @@ -95,7 +103,7 @@ class XbmcContext(AbstractContext): 'datetime.two_days_ago': 30683, 'datetime.two_hours_ago': 30680, 'datetime.yesterday_at': 30682, - 'delete': 30118, + 'delete': 117, 'disliked.video': 30717, 'error.no_video_streams_found': 30549, 'error.rtmpe_not_supported': 30542, @@ -110,8 +118,9 @@ class XbmcContext(AbstractContext): 'history.list.set.confirm': 30574, 'history.mark.unwatched': 30669, 'history.mark.watched': 30670, - 'history.remove': 30108, + 'history.remove': 15015, 'history.reset.resume_point': 30674, + 'home': 10000, 'httpd.not.running': 30699, 'inputstreamhelper.is_installed': 30625, 'isa.enable.confirm': 30579, @@ -119,7 +128,7 @@ class XbmcContext(AbstractContext): 'latest_videos': 30109, 'library': 30103, 'liked.video': 30716, - 'live': 30539, + 'live': 839, 'live.completed': 30647, 'live.upcoming': 30646, 'maintenance.bookmarks': 30800, @@ -137,50 +146,55 @@ class XbmcContext(AbstractContext): 'my_subscriptions.filter.remove': 30588, 'my_subscriptions.filter.removed': 30590, 'my_subscriptions.filtered': 30584, - 'none': 30561, + 'none': 231, 'page.back': 30815, 'page.choose': 30806, 'page.empty': 30816, 'page.next': 30106, 'playlist.added_to': 30714, - 'playlist.create': 30522, - 'playlist.play.all': 30531, - 'playlist.play.default': 30532, + 'playlist.create': 525, + 'playlist.play.all': 22083, + 'playlist.play.default': 571, 'playlist.play.from_here': 30537, 'playlist.play.reverse': 30533, 'playlist.play.select': 30535, - 'playlist.play.shuffle': 30534, + 'playlist.play.shuffle': 191, 'playlist.podcast': 30820, 'playlist.progress.updating': 30536, 'playlist.removed_from': 30715, - 'playlist.select': 30521, + 'playlist.select': 524, 'playlist.view.all': 30562, - 'playlists': 30501, + 'playlists': 136, 'please_wait': 30119, - 'prompt': 30566, 'purchases': 30622, 'recommendations': 30551, - 'refresh': 30543, + 'refresh': 184, 'refresh.settings.confirm': 30818, 'related_videos': 30514, - 'remove': 30108, + 'remove': 15015, 'removed': 30666, - 'rename': 30113, + 'rename': 118, 'renamed': 30667, 'reset.access_manager.confirm': 30581, 'retry': 30612, 'saved.playlists': 30611, - 'search': 30102, + 'search': 137, 'search.clear': 30556, 'search.new': 30110, 'search.quick': 30605, 'search.quick.incognito': 30606, - 'search.remove': 30108, - 'search.rename': 30113, - 'search.title': 30102, + 'search.remove': 15015, + 'search.rename': 118, + 'search.sort': 550, + 'search.sort.date': 552, + 'search.sort.rating': 563, + 'search.sort.relevance': 420, + 'search.sort.title': 369, + 'search.sort.viewCount': 30767, + 'search.title': 137, 'select.listen.ip': 30644, 'select_video_quality': 30010, - 'settings': 30577, + 'settings': 10004, 'setup_wizard': 30526, 'setup_wizard.capabilities': 30786, 'setup_wizard.capabilities.720p30': 30787, @@ -198,27 +212,28 @@ class XbmcContext(AbstractContext): 'setup_wizard.prompt.import_search_history': 30779, 'setup_wizard.prompt.locale': 30527, 'setup_wizard.prompt.my_location': 30653, - 'setup_wizard.prompt.settings': 30577, + 'setup_wizard.prompt.settings': 10004, 'setup_wizard.prompt.settings.defaults': 30783, 'setup_wizard.prompt.settings.list_details': 30784, 'setup_wizard.prompt.settings.performance': 30785, 'setup_wizard.prompt.settings.refresh': 30817, - 'setup_wizard.prompt.subtitles': 30600, + 'setup_wizard.prompt.subtitles': 287, 'sign.enter_code': 30519, 'sign.go_to': 30502, 'sign.in': 30111, 'sign.out': 30112, 'sign.multi.text': 30547, 'sign.multi.title': 30546, + 'start': 335, 'stats.commentCount': 30732, # 'stats.favoriteCount': 1036, - 'stats.itemCount': 30737, + 'stats.itemCount': 20360, 'stats.likeCount': 30733, 'stats.subscriberCount': 30739, - 'stats.videoCount': 30738, + 'stats.videoCount': 3, 'stats.viewCount': 30767, 'stream.alternate': 30747, - 'stream.automatic': 30583, + 'stream.automatic': 36588, 'stream.descriptive': 30746, 'stream.dubbed': 30745, 'stream.multi_audio': 30763, @@ -231,7 +246,7 @@ class XbmcContext(AbstractContext): 'subtitles.download': 30705, 'subtitles.download.pre': 30706, 'subtitles.all': 30774, - 'subtitles.language': 30560, + 'subtitles.language': 21448, 'subtitles.no_asr': 30602, 'subtitles.translation': 30775, 'subtitles.with_fallback': 30601, @@ -245,7 +260,7 @@ class XbmcContext(AbstractContext): 'updated_': 30597, 'uploads': 30726, 'user.changed': 30659, - 'user.default': 30532, + 'user.default': 571, 'user.enter_name': 30658, 'user.new': 30656, 'user.remove': 30662, @@ -262,7 +277,7 @@ class XbmcContext(AbstractContext): 'video.description.links.not_found': 30545, 'video.disliked': 30538, 'video.liked': 30508, - 'video.more': 30548, + 'video.more': 22082, 'video.play.ask_for_quality': 30730, 'video.play.audio_only': 30708, 'video.play.timeshift': 30819, @@ -272,7 +287,7 @@ class XbmcContext(AbstractContext): 'video.rate': 30528, 'video.rate.dislike': 30530, 'video.rate.like': 30529, - 'video.rate.none': 30108, + 'video.rate.none': 15015, 'watch_later': 30107, 'watch_later.add': 30107, 'watch_later.added_to': 30713, @@ -282,7 +297,7 @@ class XbmcContext(AbstractContext): 'watch_later.list.remove.confirm': 30569, 'watch_later.list.set': 30567, 'watch_later.list.set.confirm': 30570, - 'watch_later.remove': 30108, + 'watch_later.remove': 15015, 'youtube': 30003, } diff --git a/resources/lib/youtube_plugin/kodion/items/menu_items.py b/resources/lib/youtube_plugin/kodion/items/menu_items.py index 98b2069ee..892de36a3 100644 --- a/resources/lib/youtube_plugin/kodion/items/menu_items.py +++ b/resources/lib/youtube_plugin/kodion/items/menu_items.py @@ -564,7 +564,7 @@ def bookmark_add_channel(context, channel_id, channel_name=''): return ( (context.localize('bookmark.channel') % ( context.get_ui().bold(channel_name) if channel_name else - context.localize(19029) # "Channel" + context.localize('channel') )), context.create_uri( (PATHS.BOOKMARKS, 'add',), @@ -636,6 +636,21 @@ def search_clear(context): ) +def search_sort_by(context, params, order): + selected = params.get('order', 'relevance') == order + order_label = context.localize('search.sort.' + order) + return ( + context.localize('search.sort').format( + context.get_ui().bold(order_label) if selected else order_label + ), + context.create_uri( + (PATHS.ROUTE, PATHS.SEARCH, 'query',), + params=dict(params, order=order), + run=True, + ), + ) + + def separator(): return ( '--------', @@ -645,7 +660,7 @@ def separator(): def goto_home(context): return ( - context.localize(10000), # "Home" + context.localize('home'), context.create_uri( (PATHS.ROUTE, PATHS.HOME,), { diff --git a/resources/lib/youtube_plugin/kodion/items/search_history_item.py b/resources/lib/youtube_plugin/kodion/items/search_history_item.py index 737aa6701..99ef78d8d 100644 --- a/resources/lib/youtube_plugin/kodion/items/search_history_item.py +++ b/resources/lib/youtube_plugin/kodion/items/search_history_item.py @@ -20,7 +20,11 @@ def __init__(self, context, query, image=None, fanart=None, location=False): if image is None: image = '{media}/search.png' - params = {'q': query} + if isinstance(query, dict): + params = query + query = params['q'] + else: + params = {'q': query} if location: params['location'] = location @@ -36,5 +40,12 @@ def __init__(self, context, query, image=None, fanart=None, location=False): menu_items.search_remove(context, query), menu_items.search_rename(context, query), menu_items.search_clear(context), + menu_items.separator(), + menu_items.search_sort_by(context, params, 'relevance'), + menu_items.search_sort_by(context, params, 'rating'), + menu_items.search_sort_by(context, params, 'viewCount'), + menu_items.search_sort_by(context, params, 'date'), + menu_items.search_sort_by(context, params, 'title'), + menu_items.separator(), ] self.add_context_menu(context_menu) diff --git a/resources/lib/youtube_plugin/kodion/monitors/player_monitor.py b/resources/lib/youtube_plugin/kodion/monitors/player_monitor.py index 1ad502aee..c63763bb5 100644 --- a/resources/lib/youtube_plugin/kodion/monitors/player_monitor.py +++ b/resources/lib/youtube_plugin/kodion/monitors/player_monitor.py @@ -26,7 +26,7 @@ class PlayerMonitorThread(threading.Thread): - def __init__(self, player, provider, context, monitor, playback_data): + def __init__(self, player, provider, context, monitor, player_data): super(PlayerMonitorThread, self).__init__() self._stopped = threading.Event() @@ -37,10 +37,10 @@ def __init__(self, player, provider, context, monitor, playback_data): self._context = context self._monitor = monitor - self.playback_data = playback_data - self.video_id = playback_data.get('video_id') - self.channel_id = playback_data.get('channel_id') - self.video_status = playback_data.get('video_status') + self.player_data = player_data + self.video_id = player_data.get('video_id') + self.channel_id = player_data.get('channel_id') + self.video_status = player_data.get('video_status') self.current_time = 0.0 self.total_time = 0.0 @@ -55,13 +55,13 @@ def abort_now(self): or self.stopped()) def run(self): - playing_file = self.playback_data.get('playing_file') - play_count = self.playback_data.get('play_count', 0) - use_remote_history = self.playback_data.get('use_remote_history', False) - use_local_history = self.playback_data.get('use_local_history', False) - playback_stats = self.playback_data.get('playback_stats', {}) - refresh_only = self.playback_data.get('refresh_only', False) - clip = self.playback_data.get('clip', False) + playing_file = self.player_data.get('playing_file') + play_count = self.player_data.get('play_count', 0) + use_remote_history = self.player_data.get('use_remote_history', False) + use_local_history = self.player_data.get('use_local_history', False) + playback_stats = self.player_data.get('playback_stats', {}) + refresh_only = self.player_data.get('refresh_only', False) + clip = self.player_data.get('clip', False) self._context.log_debug('PlayerMonitorThread[{0}]: Starting' .format(self.video_id)) @@ -208,7 +208,7 @@ def run(self): 'played_time': self.current_time, 'played_percent': self.progress, } - self.playback_data['play_data'] = play_data + self.player_data['play_data'] = play_data if logged_in and report_url: client.update_watch_history( @@ -221,7 +221,7 @@ def run(self): self._context.get_playback_history().set_item(self.video_id, play_data) - self._context.send_notification(PLAYBACK_STOPPED, self.playback_data) + self._context.send_notification(PLAYBACK_STOPPED, self.player_data) self._context.log_debug('Playback stopped [{video_id}]:' ' {played_time:.3f} secs of {total_time:.3f}' ' @ {played_percent}%,' @@ -361,21 +361,22 @@ def onPlayBackStarted(self): if self._ui.get_property(PLAY_WITH): self._context.execute('Action(SwitchPlayer)') self._context.execute('Action(Stop)') + return def onAVStarted(self): if self._ui.get_property(PLAY_WITH): return - playback_data = self._ui.pop_property(PLAYER_DATA) - if not playback_data: + player_data = self._ui.pop_property(PLAYER_DATA) + if not player_data: return self.cleanup_threads() - playback_data = json.loads(playback_data) + player_data = json.loads(player_data) try: - self.seek_time = float(playback_data.get('seek_time')) - self.start_time = float(playback_data.get('start_time')) - self.end_time = float(playback_data.get('end_time')) + self.seek_time = float(player_data.get('seek_time')) + self.start_time = float(player_data.get('start_time')) + self.end_time = float(player_data.get('end_time')) self.current_time = max(0.0, self.getTime()) self.total_time = max(0.0, self.getTotalTime()) except (ValueError, TypeError, RuntimeError): @@ -389,7 +390,7 @@ def onAVStarted(self): self._provider, self._context, self._monitor, - playback_data)) + player_data)) def onPlayBackEnded(self): if not self._ui.busy_dialog_active(): diff --git a/resources/lib/youtube_plugin/kodion/monitors/service_monitor.py b/resources/lib/youtube_plugin/kodion/monitors/service_monitor.py index 1d502d6cd..fe553017b 100644 --- a/resources/lib/youtube_plugin/kodion/monitors/service_monitor.py +++ b/resources/lib/youtube_plugin/kodion/monitors/service_monitor.py @@ -12,11 +12,12 @@ import json import threading -from ..compatibility import xbmc, xbmcgui +from ..compatibility import urlsplit, xbmc, xbmcgui from ..constants import ( ADDON_ID, CHECK_SETTINGS, CONTAINER_FOCUS, + PATHS, PLUGIN_WAKEUP, REFRESH_CONTAINER, RELOAD_ACCESS_MANAGER, @@ -46,6 +47,7 @@ def __init__(self, context): self.httpd_sleep_allowed = True self.system_idle = False + self.system_sleep = False self.refresh = False self.interrupt = False @@ -83,6 +85,35 @@ def refresh_container(self, force=False): self.refresh = True def onNotification(self, sender, method, data): + if sender == 'xbmc': + if method == 'System.OnSleep': + self.system_idle = True + self.system_sleep = True + + elif method in { + 'GUI.OnScreensaverActivated', + 'GUI.OnDPMSActivated', + }: + self.system_idle = True + + elif method in { + 'GUI.OnScreensaverDeactivated', + 'GUI.OnDPMSDeactivated', + 'System.OnWake', + }: + self.onWake() + + elif method == 'Player.OnPlay': + player = xbmc.Player() + try: + playing_file = urlsplit(player.getPlayingFile()) + if playing_file.path in {PATHS.MPD, PATHS.REDIRECT}: + self.onWake() + except RuntimeError: + pass + + return + if sender != ADDON_ID: return @@ -129,20 +160,6 @@ def onNotification(self, sender, method, data): self._context.reload_access_manager() self.refresh_container() - def onScreensaverActivated(self): - self.system_idle = True - - def onScreensaverDeactivated(self): - self.system_idle = False - self.interrupt = True - - def onDPMSActivated(self): - self.system_idle = True - - def onDPMSDeactivated(self): - self.system_idle = False - self.interrupt = True - def onSettingsChanged(self, force=False): context = self._context @@ -201,6 +218,16 @@ def onSettingsChanged(self, force=False): elif httpd_started: self.shutdown_httpd() + def onWake(self): + self.system_idle = False + self.system_sleep = False + self.interrupt = True + + if not self.httpd and self.httpd_required(): + self.start_httpd() + if self.httpd_sleep_allowed: + self.httpd_sleep_allowed = None + def httpd_address_sync(self): self._old_httpd_address = self._httpd_address self._old_httpd_port = self._httpd_port @@ -228,9 +255,11 @@ def start_httpd(self): .format(ip=address[0], port=address[1])) - def shutdown_httpd(self, sleep=False): + def shutdown_httpd(self): if self.httpd: - if sleep and self.httpd_required(while_sleeping=True): + if (not self.system_sleep + and self.system_idle + and self.httpd_required(while_idle=True)): return self._context.log_debug('HTTPServer: Shutting down |{ip}:{port}|' .format(ip=self._old_httpd_address, @@ -255,8 +284,8 @@ def restart_httpd(self): def ping_httpd(self): return self.httpd and httpd_status(self._context) - def httpd_required(self, settings=None, while_sleeping=False): - if while_sleeping: + def httpd_required(self, settings=None, while_idle=False): + if while_idle: settings = self._context.get_settings() return (settings.api_config_page() or settings.support_alternative_player()) diff --git a/resources/lib/youtube_plugin/kodion/network/http_server.py b/resources/lib/youtube_plugin/kodion/network/http_server.py index 2542f6f5e..d461d7a85 100644 --- a/resources/lib/youtube_plugin/kodion/network/http_server.py +++ b/resources/lib/youtube_plugin/kodion/network/http_server.py @@ -171,7 +171,7 @@ def do_GET(self): api_secret = params.get('api_secret') # Bookmark this page if api_key and api_id and api_secret: - footer = localize(30638) + footer = localize('api.config.bookmark') else: footer = '' @@ -184,27 +184,27 @@ def do_GET(self): if api_key is not None and api_key != settings.api_key(): settings.api_key(new_key=api_key) - updated.append(localize(30201)) # API Key + updated.append(localize('api.key')) if api_id is not None and api_id != settings.api_id(): settings.api_id(new_id=api_id) - updated.append(localize(30202)) # API ID + updated.append(localize('api.id')) if api_secret is not None and api_secret != settings.api_secret(): settings.api_secret(new_secret=api_secret) - updated.append(localize(30203)) # API Secret + updated.append(localize('api.secret')) if api_key and api_id and api_secret: - enabled = localize(30636) # Personal keys enabled + enabled = localize('api.personal.enabled') else: - enabled = localize(30637) # Personal keys disabled + enabled = localize('api.personal.disabled') if updated: # Successfully updated - updated = localize(30631) % ', '.join(updated) + updated = localize('api.config.updated') % ', '.join(updated) else: # No changes, not updated - updated = localize(30635) + updated = localize('api.config.not_updated') html = self.api_submit_page(updated, enabled, footer) html = html.encode('utf-8') @@ -376,15 +376,15 @@ def api_config_page(cls): css = Pages.api_configuration.get('css') html = html.format( css=css, - title=localize(30634), # YouTube Add-on API Configuration - api_key_head=localize(30201), # API Key - api_id_head=localize(30202), # API ID - api_secret_head=localize(30203), # API Secret + title=localize('api.config'), + api_key_head=localize('api.key'), + api_id_head=localize('api.id'), + api_secret_head=localize('api.secret'), api_id_value=api_id, api_key_value=api_key, api_secret_value=api_secret, - submit=localize(30630), # Save - header=localize(30634), # YouTube Add-on API Configuration + submit=localize('api.config.save'), + header=localize('api.config'), ) return html @@ -395,11 +395,11 @@ def api_submit_page(cls, updated_keys, enabled, footer): css = Pages.api_submit.get('css') html = html.format( css=css, - title=localize(30634), # YouTube Add-on API Configuration + title=localize('api.config'), updated=updated_keys, enabled=enabled, footer=footer, - header=localize(30634), # YouTube Add-on API Configuration + header=localize('api.config'), ) return html diff --git a/resources/lib/youtube_plugin/kodion/plugin/xbmc/xbmc_plugin.py b/resources/lib/youtube_plugin/kodion/plugin/xbmc/xbmc_plugin.py index 05a6c2a9a..277dbd787 100644 --- a/resources/lib/youtube_plugin/kodion/plugin/xbmc/xbmc_plugin.py +++ b/resources/lib/youtube_plugin/kodion/plugin/xbmc/xbmc_plugin.py @@ -27,7 +27,6 @@ REFRESH_CONTAINER, RELOAD_ACCESS_MANAGER, REROUTE_PATH, - SERVER_WAKEUP, VIDEO_ID, ) from ...exceptions import KodionException @@ -224,7 +223,6 @@ def run(self, provider, context, focused=None): playlist_player = context.get_playlist_player() playlist_player.play_item(item=uri, listitem=item) else: - context.wakeup(SERVER_WAKEUP, timeout=5) xbmcplugin.setResolvedUrl(handle, succeeded=result, listitem=item) diff --git a/resources/lib/youtube_plugin/kodion/plugin_runner.py b/resources/lib/youtube_plugin/kodion/plugin_runner.py index b009e19af..8e8ea93f3 100644 --- a/resources/lib/youtube_plugin/kodion/plugin_runner.py +++ b/resources/lib/youtube_plugin/kodion/plugin_runner.py @@ -54,9 +54,9 @@ def run(context=_context, system_version = context.get_system_version() context.log_notice('Plugin: Running |v{version}|' - '\n\tKodi: |v{kodi}|' + '\n\tKodi: |v{kodi}|' '\n\tPython: |v{python}|' - '\n\tPath: |{path}|' + '\n\tPath: |{path}|' '\n\tParams: |{params}|' .format(version=context.get_version(), kodi=str(system_version), diff --git a/resources/lib/youtube_plugin/kodion/script_actions.py b/resources/lib/youtube_plugin/kodion/script_actions.py index 9705cf1d5..d03ffa338 100644 --- a/resources/lib/youtube_plugin/kodion/script_actions.py +++ b/resources/lib/youtube_plugin/kodion/script_actions.py @@ -67,7 +67,7 @@ def _config_actions(context, action, *_args): sub_opts = [ localize('none'), - localize('prompt'), + localize('ask'), localize('subtitles.with_fallback') % (preferred, fallback), preferred, '%s (%s)' % (preferred, localize('subtitles.no_asr')), @@ -480,11 +480,11 @@ def run(argv): system_version = context.get_system_version() context.log_notice('Script: Running |v{version}|' - '\n\tKodi: |v{kodi}|' - '\n\tPython: |v{python}|' + '\n\tKodi: |v{kodi}|' + '\n\tPython: |v{python}|' '\n\tCategory: |{category}|' - '\n\tAction: |{action}|' - '\n\tParams: |{params}|' + '\n\tAction: |{action}|' + '\n\tParams: |{params}|' .format(version=context.get_version(), kodi=str(system_version), python=system_version.get_python_version(), diff --git a/resources/lib/youtube_plugin/kodion/service_runner.py b/resources/lib/youtube_plugin/kodion/service_runner.py index 4666858d7..5c29f941b 100644 --- a/resources/lib/youtube_plugin/kodion/service_runner.py +++ b/resources/lib/youtube_plugin/kodion/service_runner.py @@ -31,7 +31,7 @@ def run(): system_version = context.get_system_version() context.log_notice('Service: Starting |v{version}|' - '\n\tKodi: |v{kodi}|' + '\n\tKodi: |v{kodi}|' '\n\tPython: |v{python}|' .format(version=context.get_version(), kodi=str(system_version), @@ -75,8 +75,13 @@ def run(): while not monitor.abortRequested(): is_idle = monitor.system_idle or monitor.get_idle_time() >= loop_period + is_asleep = monitor.system_sleep - if is_idle: + if is_asleep: + plugin_idle_time_ms = 0 + if not plugin_is_idle: + plugin_is_idle = set_property(PLUGIN_SLEEPING) + elif is_idle: if plugin_idle_time_ms >= plugin_idle_timeout_ms: plugin_idle_time_ms = 0 if not plugin_is_idle: @@ -88,11 +93,14 @@ def run(): if not monitor.httpd: httpd_idle_time_ms = 0 + elif is_asleep: + httpd_idle_time_ms = 0 + monitor.shutdown_httpd() elif is_idle: if monitor.httpd_sleep_allowed: if httpd_idle_time_ms >= httpd_idle_timeout_ms: httpd_idle_time_ms = 0 - monitor.shutdown_httpd(sleep=True) + monitor.shutdown_httpd() elif monitor.httpd_sleep_allowed is None: monitor.httpd_sleep_allowed = True httpd_idle_time_ms = 0 @@ -116,6 +124,12 @@ def run(): wait_time_ms = 0 while not monitor.abortRequested(): + if (not monitor.httpd + and not monitor.system_sleep + and not (monitor.system_idle + or monitor.get_idle_time() >= loop_period)): + monitor.onWake() + if monitor.refresh and all(container.values()): monitor.refresh_container(force=True) monitor.refresh = False diff --git a/resources/lib/youtube_plugin/kodion/settings/abstract_settings.py b/resources/lib/youtube_plugin/kodion/settings/abstract_settings.py index fa47c1a41..a3c7582a4 100644 --- a/resources/lib/youtube_plugin/kodion/settings/abstract_settings.py +++ b/resources/lib/youtube_plugin/kodion/settings/abstract_settings.py @@ -191,10 +191,15 @@ def get_thumbnail_size(self, value=None): return self._THUMB_SIZES[value] return self._THUMB_SIZES[default] + _SAFE_SEARCH_LEVELS = { + 0: 'moderate', + 1: 'none', + 2: 'strict', + } + def safe_search(self): index = self.get_int(SETTINGS.SAFE_SEARCH, 0) - values = {0: 'moderate', 1: 'none', 2: 'strict'} - return values[index] + return self._SAFE_SEARCH_LEVELS[index] def age_gate(self): return self.get_bool(SETTINGS.AGE_GATE, True) diff --git a/resources/lib/youtube_plugin/kodion/settings/xbmc/xbmc_plugin_settings.py b/resources/lib/youtube_plugin/kodion/settings/xbmc/xbmc_plugin_settings.py index 6065af900..1e8051cb8 100644 --- a/resources/lib/youtube_plugin/kodion/settings/xbmc/xbmc_plugin_settings.py +++ b/resources/lib/youtube_plugin/kodion/settings/xbmc/xbmc_plugin_settings.py @@ -144,7 +144,7 @@ def get_bool(self, setting, default=None, echo=None): except (TypeError, ValueError) as exc: error = exc try: - value = self.get_string(setting, echo=False).lower() + value = self.get_string(setting, echo=False) value = VALUE_FROM_STR.get(value, default) except TypeError as exc: error = exc 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 3ebe58121..cb8a32eaa 100644 --- a/resources/lib/youtube_plugin/kodion/sql_store/search_history.py +++ b/resources/lib/youtube_plugin/kodion/sql_store/search_history.py @@ -35,11 +35,23 @@ def get_items(self, process=None): def _make_id(search_text): return md5(search_text.encode('utf-8')).hexdigest() - def add_item(self, search_text): - self._set(self._make_id(search_text), search_text) - - def del_item(self, search_text): - self._remove(self._make_id(search_text)) - - def update_item(self, search_text, timestamp=None): - self._update(self._make_id(search_text), search_text, timestamp) + def add_item(self, query): + if isinstance(query, dict): + params = query + query = params['q'] + else: + params = {'q': query} + self._set(self._make_id(query), params) + + def del_item(self, query): + if isinstance(query, dict): + query = query['q'] + self._remove(self._make_id(query)) + + def update_item(self, query, timestamp=None): + if isinstance(query, dict): + params = query + query = params['q'] + else: + params = {'q': query} + self._update(self._make_id(query), params, timestamp) diff --git a/resources/lib/youtube_plugin/kodion/utils/system_version.py b/resources/lib/youtube_plugin/kodion/utils/system_version.py index 7711fe580..a6e8b15b7 100644 --- a/resources/lib/youtube_plugin/kodion/utils/system_version.py +++ b/resources/lib/youtube_plugin/kodion/utils/system_version.py @@ -17,32 +17,32 @@ class SystemVersion(object): - RELEASE_MAP = { - (22, 0): 'Piers', - (21, 0): 'Omega', - (20, 0): 'Nexus', - (19, 0): 'Matrix', - (18, 0): 'Leia', - (17, 0): 'Krypton', - (16, 0): 'Jarvis', - (15, 0): 'Isengard', - (14, 0): 'Helix', - (13, 0): 'Gotham', - (12, 0): 'Frodo', + RELEASE_NAME_MAP = { + 22: 'Piers', + 21: 'Omega', + 20: 'Nexus', + 19: 'Matrix', + 18: 'Leia', + 17: 'Krypton', + 16: 'Jarvis', + 15: 'Isengard', + 14: 'Helix', + 13: 'Gotham', + 12: 'Frodo', } - def __init__(self, version=None, releasename=None, appname=None): + def __init__(self, version=None, release_name=None, app_name=None): if isinstance(version, tuple): self._version = version else: version = None - if appname and isinstance(appname, string_type): - self._appname = appname + if app_name and isinstance(app_name, string_type): + self._app_name = app_name else: - appname = None + app_name = None - if version is None or appname is None: + if version is None or app_name is None: try: result = jsonrpc( method='Application.GetProperties', @@ -56,32 +56,32 @@ def __init__(self, version=None, releasename=None, appname=None): self._version = (version.get('major', 1), version.get('minor', 0)) - if appname is None: - self._appname = result.get('name', 'Unknown application') + if app_name is None: + self._app_name = result.get('name', 'Unknown application') - if releasename and isinstance(releasename, string_type): - self._releasename = releasename + if release_name and isinstance(release_name, string_type): + self._release_name = release_name else: - version = (self._version[0], self._version[1]) - self._releasename = self.RELEASE_MAP.get(version, 'Unknown release') + self._release_name = self.RELEASE_NAME_MAP.get(self._version[0], + 'Unknown release') self._python_version = python_version() def __str__(self): - return '{version[0]}.{version[1]} ({appname} {releasename})'.format( - releasename=self._releasename, - appname=self._appname, + return '{version[0]}.{version[1]} ({app_name} {release_name})'.format( + release_name=self._release_name, + app_name=self._app_name, version=self._version ) def get_release_name(self): - return self._releasename + return self._release_name def get_version(self): return self._version def get_app_name(self): - return self._appname + return self._app_name def get_python_version(self): return self._python_version diff --git a/resources/lib/youtube_plugin/youtube/client/login_client.py b/resources/lib/youtube_plugin/youtube/client/login_client.py index 23f1fdf2b..fa9f0373a 100644 --- a/resources/lib/youtube_plugin/youtube/client/login_client.py +++ b/resources/lib/youtube_plugin/youtube/client/login_client.py @@ -134,8 +134,8 @@ def refresh_token(self, token_type, refresh_token=None): 'grant_type': 'refresh_token'} config_type = self._get_config_type(client_id, client_secret) - client = (('\n\tconfig_type: |{config_type}|' - '\n\tclient_id: |{id_start}...{id_end}|' + client = (('\n\tconfig_type: |{config_type}|' + '\n\tclient_id: |{id_start}...{id_end}|' '\n\tclient_secret: |{secret_start}...{secret_end}|') .format(config_type=config_type, id_start=client_id[:3], @@ -184,8 +184,8 @@ def request_access_token(self, token_type, code=None): 'grant_type': 'http://oauth.net/grant_type/device/1.0'} config_type = self._get_config_type(client_id, client_secret) - client = (('\n\tconfig_type: |{config_type}|' - '\n\tclient_id: |{id_start}...{id_end}|' + client = (('\n\tconfig_type: |{config_type}|' + '\n\tclient_id: |{id_start}...{id_end}|' '\n\tclient_secret: |{secret_start}...{secret_end}|') .format(config_type=config_type, id_start=client_id[:3], @@ -231,7 +231,7 @@ def request_device_and_user_code(self, token_type): config_type = self._get_config_type(client_id) client = (('\n\tconfig_type: |{config_type}|' - '\n\tclient_id: |{id_start}...{id_end}|') + '\n\tclient_id: |{id_start}...{id_end}|') .format(config_type=config_type, id_start=client_id[:3], id_end=client_id[-5:])) diff --git a/resources/lib/youtube_plugin/youtube/client/youtube.py b/resources/lib/youtube_plugin/youtube/client/youtube.py index 295d2f926..d192e1a27 100644 --- a/resources/lib/youtube_plugin/youtube/client/youtube.py +++ b/resources/lib/youtube_plugin/youtube/client/youtube.py @@ -10,6 +10,7 @@ from __future__ import absolute_import, division, unicode_literals +import json import threading import xml.etree.ElementTree as ET from functools import partial @@ -193,12 +194,13 @@ def get_streams(self, ask_for_quality=False, audio_only=False, use_mpd=True): - return StreamInfo(context, - access_token=(self._access_token - or self._access_token_tv), - ask_for_quality=ask_for_quality, - audio_only=audio_only, - use_mpd=use_mpd).load_stream_info(video_id) + return StreamInfo( + context, + access_token=(self._access_token or self._access_token_tv), + ask_for_quality=ask_for_quality, + audio_only=audio_only, + use_mpd=use_mpd, + ).load_stream_info(video_id) def remove_playlist(self, playlist_id, **kwargs): params = {'id': playlist_id, @@ -1036,7 +1038,13 @@ def get_live_events(self, params['pageToken'] = page_token if after: - params['publishedAfter'] = after + if isinstance(after, string_type) and after.startswith('{'): + after = json.loads(after) + params['publishedAfter'] = ( + datetime_parser.yt_datetime_offset(**after) + if isinstance(after, dict) else + after + ) return self.api_request(method='GET', path='search', @@ -1315,82 +1323,184 @@ def get_channel_videos(self, channel_id, page_token='', **kwargs): def search(self, q, search_type=None, - event_type='', - channel_id='', + event_type=None, + channel_id=None, order='relevance', safe_search='moderate', page_token='', location=False, **kwargs): """ + Returns a collection of search results that match the query parameters specified in the API request. By default, a search result set identifies matching video, channel, and playlist resources, but you can also configure queries to only retrieve a specific type of resource. - :param q: - :param search_type: acceptable values are: 'video' | 'channel' | 'playlist' - :param event_type: 'live', 'completed', 'upcoming' - :param channel_id: limit search to channel id - :param order: one of: 'date', 'rating', 'relevance', 'title', 'videoCount', 'viewCount' - :param safe_search: one of: 'moderate', 'none', 'strict' - :param page_token: can be '' - :param location: bool, use geolocation + + :param str q: The q parameter specifies the query term to search for. Query can also use the Boolean NOT (-) + and OR (|) operators to exclude videos or to find videos that are associated with one of several search + terms. + :param str search_type: Acceptable values are: 'video', 'channel' or 'playlist' + :param str event_type: Restricts a search to broadcast events. If you specify a value for this parameter, you + must also set the type parameter's value to video. + Acceptable values are: + - `live` + - `completed` + - `upcoming` + :param str channel_id: limit search to channel id + :param str channel_type: Restrict a search to a particular type of channel. + Acceptable values are: + - `any` : return all channels. + - `show` : only retrieve shows. + :param str order: Specifies the method that will be used to order resources in the API response. The default + value is relevance. + Acceptable values are: + - `date` : reverse chronological order based on the date created. + - `rating` : highest to lowest rating. + - `relevance` : sorted based on their relevance to the search query. + - `title` : alphabetically by title. + - `videoCount` : channels are sorted in descending order of their number of uploaded videos. + - `viewCount` : highest to lowest number of views or concurrent viewers for live broadcasts. + :param str safe_search: one of: 'moderate', 'none', 'strict' + :param str page_token: can be '' + :param bool location: use geolocation + :param str video_type: Restrict a search to a particular type of videos. If you specify a value for this + parameter, you must also set the type parameter's value to video. + Acceptable values are: + - `any` : return all videos. + - `episode` : only retrieve episodes of shows. + - `movie` : only retrieve movies. :return: """ - if search_type is None: - search_type = ['video', 'channel', 'playlist'] - - # prepare search type - if not search_type: - search_type = '' - if not isinstance(search_type, string_type): - search_type = ','.join(search_type) - - # prepare page token - if not page_token: - page_token = '' - # prepare params - params = {'q': q, + params = {'q': q.replace('|', '%7C') if '|' in q else q, 'part': 'snippet', 'regionCode': self._region, 'hl': self._language, 'relevanceLanguage': self._language, 'maxResults': str(self.max_results())} - if event_type and event_type in {'live', 'upcoming', 'completed'}: - params['eventType'] = event_type + if search_type is None: + search_type = ('video', 'channel', 'playlist') + if isinstance(search_type, (list, tuple)): + search_type = ','.join(search_type) if search_type: params['type'] = search_type + + if event_type and event_type in {'live', 'upcoming', 'completed'}: + params['eventType'] = event_type + params['type'] = 'video' + if channel_id: params['channelId'] = channel_id + if order: params['order'] = order + if safe_search: params['safeSearch'] = safe_search + if page_token: params['pageToken'] = page_token - video_only_params = ['eventType', 'videoCaption', 'videoCategoryId', 'videoDefinition', - 'videoDimension', 'videoDuration', 'videoEmbeddable', 'videoLicense', - 'videoSyndicated', 'videoType', 'relatedToVideoId', 'forMine'] - for key in video_only_params: - if params.get(key) is not None: - params['type'] = 'video' - break - - if params['type'] == 'video' and location: + if location: settings = self._context.get_settings() location = settings.get_location() if location: params['location'] = location params['locationRadius'] = settings.get_location_radius() + params['type'] = 'video' return self.api_request(method='GET', path='search', params=params, **kwargs) + def search_with_params(self, params, **kwargs): + settings = self._context.get_settings() + + # prepare default params + search_params = { + 'part': 'snippet', + 'regionCode': self._region, + 'hl': self._language, + 'relevanceLanguage': self._language, + 'maxResults': str(self.max_results()), + } + + search_query = params.get('q') + if '|' in search_query: + search_params['q'] = search_query.replace('|', '%7C') + + search_type = params.get('type') + if isinstance(search_type, (list, tuple)): + search_params['type'] = ','.join(search_type) + + location = params.get('location') + if location is True: + location = settings.get_location() + if location: + search_params['location'] = location + search_params['locationRadius'] = settings.get_location_radius() + + if 'safeSearch' not in params: + search_params['safeSearch'] = settings.safe_search() + + published = params.get('publishedBefore') + if published: + if isinstance(published, string_type) and published.startswith('{'): + published = json.loads(published) + search_params['publishedBefore'] = ( + datetime_parser.yt_datetime_offset(**published) + if isinstance(published, dict) else + published + ) + + published = params.get('publishedAfter') + if published: + if isinstance(published, string_type) and published.startswith('{'): + published = json.loads(published) + search_params['publishedAfter'] = ( + datetime_parser.yt_datetime_offset(**published) + if isinstance(published, dict) else + published + ) + + params_to_delete = [] + for param, value in params.items(): + if value: + if param not in search_params: + search_params[param] = value + else: + params_to_delete.append(param) + + for param in params_to_delete: + del params[param] + + video_only_params = { + 'eventType', + 'forMine' + 'location', + 'relatedToVideoId', + 'videoCaption', + 'videoCategoryId', + 'videoDefinition', + 'videoDimension', + 'videoDuration', + 'videoEmbeddable', + 'videoLicense', + 'videoSyndicated', + 'videoType', + } + if not video_only_params.isdisjoint(search_params.keys()): + search_params['type'] = 'video' + + return (params, + self.api_request(method='GET', + path='search', + params=search_params, + **kwargs)) + def get_my_subscriptions(self, page_token=1, logged_in=False, @@ -2006,7 +2116,7 @@ def _error_hook(self, **kwargs): time_ms=timeout) info = ('API error: {reason}' - '\n\texc: |{exc!r}|' + '\n\texc: |{exc!r}|' '\n\tmessage: |{message}|') details = {'reason': reason, 'message': message} return '', info, details, data, False, exception @@ -2086,12 +2196,12 @@ def api_request(self, context = self._context context.log_debug('API request:' - '\n\tversion: |{version}|' - '\n\tmethod: |{method}|' - '\n\tpath: |{path}|' - '\n\tparams: |{params}|' + '\n\tversion: |{version}|' + '\n\tmethod: |{method}|' + '\n\tpath: |{path}|' + '\n\tparams: |{params}|' '\n\tpost_data: |{data}|' - '\n\theaders: |{headers}|' + '\n\theaders: |{headers}|' .format(version=version, method=method, path=path, diff --git a/resources/lib/youtube_plugin/youtube/helper/stream_info.py b/resources/lib/youtube_plugin/youtube/helper/stream_info.py index 160f2a982..e18f4dfbe 100644 --- a/resources/lib/youtube_plugin/youtube/helper/stream_info.py +++ b/resources/lib/youtube_plugin/youtube/helper/stream_info.py @@ -776,8 +776,8 @@ def _error_hook(**kwargs): message = details.get('message', 'Unknown error') info = ('exc: |{exc!r}|' - '\n\treason: |{reason}|' - '\n\tmessage: |{message}|' + '\n\treason: |{reason}|' + '\n\tmessage: |{message}|' '\n\tvideo_id: {video_id}, client: {client}, auth: {auth}') kwargs['message'] = message kwargs['reason'] = reason @@ -1233,7 +1233,7 @@ def _process_signature_cipher(self, stream_map): except Exception as exc: self._context.log_error('VideoInfo._process_signature_cipher - ' 'failed to extract URL from |{sig}|' - '\n\texc: |{exc!r}|' + '\n\texc: |{exc!r}|' '\n\tdetails: |{details}|'.format( sig=encrypted_signature, exc=exc, diff --git a/resources/lib/youtube_plugin/youtube/helper/utils.py b/resources/lib/youtube_plugin/youtube/helper/utils.py index 50a3c0cdb..a7144c478 100644 --- a/resources/lib/youtube_plugin/youtube/helper/utils.py +++ b/resources/lib/youtube_plugin/youtube/helper/utils.py @@ -157,7 +157,7 @@ def update_channel_infos(provider, context, channel_id_dict, show_details = settings.show_detailed_description() localize = context.localize - channel_role = localize(19029) # "Channel" + channel_role = localize('channel') untitled = localize('untitled') path = context.get_path() @@ -337,7 +337,7 @@ def update_playlist_infos(provider, context, playlist_id_dict, item_count_color = settings.get_label_color('itemCount') localize = context.localize - channel_role = localize(19029) # "Channel" + channel_role = localize('channel') episode_count_label = localize('stats.itemCount') video_count_label = localize('stats.videoCount') podcast_label = context.localize('playlist.podcast') @@ -550,7 +550,7 @@ def update_video_infos(provider, context, video_id_dict, use_play_data = settings.use_local_history() localize = context.localize - channel_role = localize(19029) # "Channel" + channel_role = localize('channel') untitled = localize('untitled') path = context.get_path() @@ -691,7 +691,7 @@ def update_video_infos(provider, context, video_id_dict, elif media_item.live: type_label = localize('live') else: - type_label = localize(335) # "Start" + type_label = localize('start') start_at = ' '.join(( type_label, datetime_parser.get_scheduled_start(context, local_datetime), diff --git a/resources/lib/youtube_plugin/youtube/helper/yt_login.py b/resources/lib/youtube_plugin/youtube/helper/yt_login.py index b7495139b..7eaaff141 100644 --- a/resources/lib/youtube_plugin/youtube/helper/yt_login.py +++ b/resources/lib/youtube_plugin/youtube/helper/yt_login.py @@ -128,10 +128,10 @@ def _do_login(token_type): refresh_token = None context.log_debug('YouTube Login:' - '\n\tType: |{0}|' - '\n\tAccess token: |{1}|' + '\n\tType: |{0}|' + '\n\tAccess token: |{1}|' '\n\tRefresh token: |{2}|' - '\n\tExpires: |{3}|' + '\n\tExpires: |{3}|' .format(token, bool(access_token), bool(refresh_token), diff --git a/resources/lib/youtube_plugin/youtube/helper/yt_play.py b/resources/lib/youtube_plugin/youtube/helper/yt_play.py index fd2e71ae5..904af7661 100644 --- a/resources/lib/youtube_plugin/youtube/helper/yt_play.py +++ b/resources/lib/youtube_plugin/youtube/helper/yt_play.py @@ -282,11 +282,12 @@ def _play_channel_live(provider, context): index = context.get_param('live', 1) - 1 if index < 0: index = 0 - json_data = provider.get_client(context).search(q='', - search_type='video', - event_type='live', - channel_id=channel_id, - safe_search=False) + _, json_data = provider.get_client(context).search_with_params(params={ + 'type': 'video', + 'eventType': 'live', + 'channelId': channel_id, + 'safeSearch': 'none', + }) if not json_data: 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 c2e2ac93a..6b211459b 100644 --- a/resources/lib/youtube_plugin/youtube/helper/yt_setup_wizard.py +++ b/resources/lib/youtube_plugin/youtube/helper/yt_setup_wizard.py @@ -78,7 +78,10 @@ def process_default_settings(context, step, steps, **_kwargs): else: settings.live_stream_type(1) if not xbmcvfs.exists('special://profile/playercorefactory.xml'): + settings.support_alternative_player(False) settings.default_player_web_urls(False) + settings.alternative_player_web_urls(False) + settings.alternative_player_adaptive(False) if settings.cache_size() < 20: settings.cache_size(20) if settings.use_isa() and not httpd_status(context): diff --git a/resources/lib/youtube_plugin/youtube/helper/yt_specials.py b/resources/lib/youtube_plugin/youtube/helper/yt_specials.py index c19958e38..feabbc150 100644 --- a/resources/lib/youtube_plugin/youtube/helper/yt_specials.py +++ b/resources/lib/youtube_plugin/youtube/helper/yt_specials.py @@ -15,7 +15,6 @@ from ...kodion.constants import CONTENT from ...kodion.items import DirectoryItem, UriItem from ...kodion.utils import strip_html_from_text -from ...kodion.utils.datetime_parser import yt_datetime_offset def _process_related_videos(provider, context, client): @@ -149,9 +148,7 @@ def _process_live_events(provider, context, client, event_type='live'): order='date' if event_type == 'upcoming' else 'viewCount', page_token=context.get_param('page_token', ''), location=context.get_param('location', False), - after=(yt_datetime_offset(days=3) - if event_type == 'completed' else - None), + after={'days': 3} if event_type == 'completed' else None, ) if not json_data: diff --git a/resources/lib/youtube_plugin/youtube/provider.py b/resources/lib/youtube_plugin/youtube/provider.py index 3cfc5c987..e0a526c30 100644 --- a/resources/lib/youtube_plugin/youtube/provider.py +++ b/resources/lib/youtube_plugin/youtube/provider.py @@ -786,6 +786,9 @@ def _search_channel_or_playlist(self, context, identifier): return False def on_search_run(self, context, search_text): + data_cache = context.get_data_cache() + data_cache.del_item('search_query') + # Search by url to access unlisted videos if search_text.startswith(('https://', 'http://')): return self.on_uri2addon(provider=self, @@ -813,10 +816,6 @@ def on_search_run(self, context, search_text): search_type = params.get('search_type', 'video') safe_search = context.get_settings().safe_search() - context.get_data_cache().set_item('search_query', search_text) - if not params.get('incognito') and not params.get('channel_id'): - context.get_search_history().add_item(search_text) - if search_type == 'video': context.set_content(CONTENT.VIDEO_CONTENT) else: @@ -865,20 +864,33 @@ def on_search_run(self, context, search_text): ) result.append(live_item) + search_params = { + 'q': search_text, + 'order': order, + 'channelId': channel_id, + 'type': search_type, + 'eventType': event_type, + 'safeSearch': safe_search, + 'pageToken': page_token, + 'location': location, + } + function_cache = context.get_function_cache() - json_data = function_cache.run(self.get_client(context).search, - function_cache.ONE_MINUTE * 10, - _refresh=params.get('refresh'), - q=search_text, - search_type=search_type, - event_type=event_type, - safe_search=safe_search, - page_token=page_token, - channel_id=channel_id, - order=order, - location=location) + search_params, json_data = function_cache.run( + self.get_client(context).search_with_params, + function_cache.ONE_MINUTE * 10, + _refresh=params.get('refresh'), + params=search_params, + ) if not json_data: return False + + # Store current search query for Kodi window history navigation + if not params.get('incognito'): + if not params.get('channel_id'): + context.get_search_history().add_item(search_params) + data_cache.set_item('search_query', search_text) + result.extend(v3.response_to_items( self, context, json_data, item_filter={ diff --git a/resources/settings.xml b/resources/settings.xml index d11565eb7..607bcd33d 100644 --- a/resources/settings.xml +++ b/resources/settings.xml @@ -1,7 +1,7 @@
- + 0 @@ -32,7 +32,7 @@ 3 - + @@ -78,7 +78,7 @@ 2 - + @@ -198,7 +198,7 @@ - + 0 true @@ -242,7 +242,7 @@ - + @@ -284,7 +284,7 @@ - + @@ -406,7 +406,7 @@ true - + 0 true @@ -493,7 +493,7 @@ 30038 - + 0 true @@ -529,7 +529,7 @@ true - + 0 true @@ -549,7 +549,7 @@ true - + 0 false @@ -592,7 +592,7 @@ - + 0 @@ -868,20 +868,20 @@ true - + 0 en-US false - 30523 + 248 - + 0 US false - 30550 + 20026 @@ -1075,7 +1075,7 @@ true - + 0 50152 @@ -1083,7 +1083,7 @@ 65535 - 30619 + 730