diff --git a/addon.xml b/addon.xml index 7715fa22e..035f2bd08 100644 --- a/addon.xml +++ b/addon.xml @@ -1,10 +1,11 @@ - + + video diff --git a/changelog.txt b/changelog.txt index ff8bb8af5..b320b6093 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,3 +1,17 @@ +## v7.1.0+beta.4 +### Fixed +- Fix playback history related context menu items not being shown #904 +- Fix new resume details not being saved in plugin local playback history #904 + +### Changed +- Allow default thumbnail selection fallbacks for all results +- Simplify handling of local history + - Incognito will no longer prevent existing local history from being shown + - Incognito will continue to prevent any update to local history + +### New +- Add support for proxy settings #884 + ## v7.1.0+beta.3 ### Fixed - Fix various timing and sync issues with script, service, plugin and Kodi settings diff --git a/resources/lib/youtube_plugin/kodion/constants/const_settings.py b/resources/lib/youtube_plugin/kodion/constants/const_settings.py index 54acaab2a..ed8ffef57 100644 --- a/resources/lib/youtube_plugin/kodion/constants/const_settings.py +++ b/resources/lib/youtube_plugin/kodion/constants/const_settings.py @@ -78,6 +78,14 @@ CONNECT_TIMEOUT = 'requests.timeout.connect' # (int) READ_TIMEOUT = 'requests.timeout.read' # (int) +PROXY_SOURCE = 'requests.proxy.source' # (int) +PROXY_ENABLED = 'requests.proxy.enabled' # (bool) +PROXY_TYPE = 'requests.proxy.type' # (int) +PROXY_SERVER = 'requests.proxy.server' # (str) +PROXY_PORT = 'requests.proxy.port' # (int) +PROXY_USERNAME = 'requests.proxy.username' # (str) +PROXY_PASSWORD = 'requests.proxy.password' # (str) + HTTPD_PORT = 'kodion.http.port' # (int) HTTPD_LISTEN = 'kodion.http.listen' # (str) HTTPD_WHITELIST = 'kodion.http.ip.whitelist' # (str) diff --git a/resources/lib/youtube_plugin/kodion/monitors/player_monitor.py b/resources/lib/youtube_plugin/kodion/monitors/player_monitor.py index 41f906552..1ad502aee 100644 --- a/resources/lib/youtube_plugin/kodion/monitors/player_monitor.py +++ b/resources/lib/youtube_plugin/kodion/monitors/player_monitor.py @@ -218,8 +218,8 @@ def run(self): status=(segment_end, segment_end, segment_end, 'stopped'), ) if use_local_history: - self._context.get_playback_history().update_item(self.video_id, - play_data) + self._context.get_playback_history().set_item(self.video_id, + play_data) self._context.send_notification(PLAYBACK_STOPPED, self.playback_data) self._context.log_debug('Playback stopped [{video_id}]:' diff --git a/resources/lib/youtube_plugin/kodion/network/requests.py b/resources/lib/youtube_plugin/kodion/network/requests.py index 16c4963b5..803adeae0 100644 --- a/resources/lib/youtube_plugin/kodion/network/requests.py +++ b/resources/lib/youtube_plugin/kodion/network/requests.py @@ -58,7 +58,8 @@ class BaseRequestsClass(object): def __init__(self, context, exc_type=None): settings = context.get_settings() self._verify = settings.verify_ssl() - self._timeout = settings.get_timeout() + self._timeout = settings.requests_timeout() + self._proxy = settings.proxy_settings() if isinstance(exc_type, tuple): self._default_exc = (RequestException,) + exc_type @@ -89,6 +90,8 @@ def request(self, url, method='GET', timeout = self._timeout if verify is None: verify = self._verify + if proxies is None: + proxies = self._proxy if allow_redirects is None: allow_redirects = True diff --git a/resources/lib/youtube_plugin/kodion/settings/abstract_settings.py b/resources/lib/youtube_plugin/kodion/settings/abstract_settings.py index 56c94b8fb..f644a4769 100644 --- a/resources/lib/youtube_plugin/kodion/settings/abstract_settings.py +++ b/resources/lib/youtube_plugin/kodion/settings/abstract_settings.py @@ -13,7 +13,11 @@ import sys from ..constants import SETTINGS -from ..utils import current_system_version, validate_ip_address +from ..utils import ( + current_system_version, + get_kodi_setting_value, + validate_ip_address, +) class AbstractSettings(object): @@ -194,18 +198,171 @@ def safe_search(self): def age_gate(self): return self.get_bool(SETTINGS.AGE_GATE, True) - def verify_ssl(self): + def verify_ssl(self, value=None): + if value is not None: + return self.set_bool(SETTINGS.VERIFY_SSL, value) + if sys.version_info <= (2, 7, 9): verify = False else: verify = self.get_bool(SETTINGS.VERIFY_SSL, True) return verify - def get_timeout(self): + def requests_timeout(self, value=None): + if value is not None: + self.set_int(SETTINGS.CONNECT_TIMEOUT, value[0]) + self.set_int(SETTINGS.READ_TIMEOUT, value[1]) + return value + connect_timeout = self.get_int(SETTINGS.CONNECT_TIMEOUT, 9) + 0.5 read_timout = self.get_int(SETTINGS.READ_TIMEOUT, 27) return connect_timeout, read_timout + _PROXY_TYPE_SCHEME = { + 0: 'http', + 1: 'socks4', + 2: 'socks4a', + 3: 'socks5', + 4: 'socks5h', + 5: 'https', + } + + _PROXY_SETTINGS = { + SETTINGS.PROXY_ENABLED: { + 'value': None, + 'type': bool, + 'default': False, + 'kodi_name': 'network.usehttpproxy', + }, + SETTINGS.PROXY_TYPE: { + 'value': None, + 'type': int, + 'default': 0, + 'kodi_name': 'network.httpproxytype', + }, + SETTINGS.PROXY_SERVER: { + 'value': None, + 'type': str, + 'default': '', + 'kodi_name': 'network.httpproxyserver', + }, + SETTINGS.PROXY_PORT: { + 'value': None, + 'type': int, + 'default': 8080, + 'kodi_name': 'network.httpproxyport', + }, + SETTINGS.PROXY_USERNAME: { + 'value': None, + 'type': str, + 'default': '', + 'kodi_name': 'network.httpproxyusername', + }, + SETTINGS.PROXY_PASSWORD: { + 'value': None, + 'type': str, + 'default': '', + 'kodi_name': 'network.httpproxypassword', + }, + } + + def proxy_settings(self, value=None, as_mapping=True): + if value is not None: + for setting_name, setting in value.items(): + setting_value = setting.get('value') + if setting_value is None: + continue + + setting_type = setting.get('type', int) + if setting_type is int: + self.set_int(setting_name, setting_value) + elif setting_type is str: + self.set_string(setting_name, setting_value) + else: + self.set_bool(setting_name, setting_value) + return value + + proxy_source = self.get_int(SETTINGS.PROXY_SOURCE, 1) + if not proxy_source: + return None + + settings = {} + for setting_name, setting in self._PROXY_SETTINGS.items(): + setting_default = setting.get('default') + setting_type = setting.get('type', int) + if proxy_source == 1: + setting_value = get_kodi_setting_value( + setting.get('kodi_name'), + process=setting_type, + ) or setting_default + elif setting_type is int: + setting_value = self.get_int(setting_name, setting_default) + elif setting_type is str: + setting_value = self.get_string(setting_name, setting_default) + else: + setting_value = self.get_bool(setting_name, setting_default) + + settings[setting_name] = { + 'value': setting_value, + 'type': setting_type, + 'default': setting_default, + } + + if not as_mapping: + return settings + + if proxy_source == 1 and not settings[SETTINGS.PROXY_ENABLED]['value']: + return None + + scheme = self._PROXY_TYPE_SCHEME[settings[SETTINGS.PROXY_TYPE]['value']] + if scheme.startswith('socks'): + from ..compatibility import xbmc, xbmcaddon + + pysocks = None + install_attempted = False + while not pysocks: + try: + pysocks = xbmcaddon.Addon('script.module.pysocks') + except RuntimeError: + if install_attempted: + break + xbmc.executebuiltin( + 'InstallAddon(script.module.pysocks)', + wait=True, + ) + install_attempted = True + if pysocks: + del pysocks + else: + return None + + host = settings[SETTINGS.PROXY_SERVER]['value'] + if not host: + return None + + port = settings[SETTINGS.PROXY_PORT]['value'] + if port: + host_port_string = ':'.join((host, str(port))) + else: + host_port_string = host + + username = settings[SETTINGS.PROXY_USERNAME]['value'] + if username: + password = settings[SETTINGS.PROXY_PASSWORD]['value'] + if password: + auth_string = ':'.join((username, password)) + else: + auth_string = username + auth_string += '@' + else: + auth_string = '' + + proxy_string = ''.join((scheme, '://', auth_string, host_port_string)) + return { + 'http': proxy_string, + 'https': proxy_string, + } + def allow_dev_keys(self): return self.get_bool(SETTINGS.ALLOW_DEV_KEYS, False) diff --git a/resources/lib/youtube_plugin/youtube/helper/tv.py b/resources/lib/youtube_plugin/youtube/helper/tv.py index 4dde234fd..f00bea1af 100644 --- a/resources/lib/youtube_plugin/youtube/helper/tv.py +++ b/resources/lib/youtube_plugin/youtube/helper/tv.py @@ -16,35 +16,32 @@ def tv_videos_to_items(provider, context, json_data): - incognito = context.get_param('incognito') - settings = context.get_settings() - use_play_data = not incognito and settings.use_local_history() - item_filter = settings.item_filter() - item_params = { 'video_id': None, } - if incognito: + if context.get_param('incognito'): item_params['incognito'] = True + video_id_dict = {} - channel_item_dict = {} + channel_items_dict = {} + for item in json_data.get('items', []): video_id = item['id'] item_params['video_id'] = video_id - video_item = VideoItem( + video_id_dict[video_id] = VideoItem( item['title'], context.create_uri((PATHS.PLAY,), item_params) ) - if incognito: - video_item.set_play_count(0) - video_id_dict[video_id] = video_item - - utils.update_video_infos(provider, - context, - video_id_dict, - channel_items_dict=channel_item_dict, - use_play_data=use_play_data, - item_filter=item_filter) - utils.update_fanarts(provider, context, channel_item_dict) + + item_filter = context.get_settings().item_filter() + + utils.update_video_infos( + provider, + context, + video_id_dict, + channel_items_dict=channel_items_dict, + item_filter=item_filter, + ) + utils.update_fanarts(provider, context, channel_items_dict) if item_filter: result = utils.filter_videos(video_id_dict.values(), **item_filter) @@ -80,7 +77,7 @@ def saved_playlists_to_items(provider, context, json_data): title = item['title'] channel_id = item['channel_id'] playlist_id = item['id'] - image = utils.get_thumbnail(thumb_size, item.get('thumbnails', {})) + image = utils.get_thumbnail(thumb_size, item.get('thumbnails')) if channel_id: item_uri = context.create_uri( 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 737720607..c92242b77 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 @@ -191,12 +191,13 @@ def get_video_items(self, provider, context, skip_title=False): if self._video_items: return self._video_items - use_play_data = not context.get_param('incognito', False) - channel_id_dict = {} - utils.update_video_infos(provider, context, self._video_id_dict, - channel_items_dict=channel_id_dict, - use_play_data=use_play_data) + utils.update_video_infos( + provider, + context, + self._video_id_dict, + channel_items_dict=channel_id_dict, + ) utils.update_fanarts(provider, context, channel_id_dict) self._video_items = [ diff --git a/resources/lib/youtube_plugin/youtube/helper/utils.py b/resources/lib/youtube_plugin/youtube/helper/utils.py index f8b9fdb20..e8d080fbb 100644 --- a/resources/lib/youtube_plugin/youtube/helper/utils.py +++ b/resources/lib/youtube_plugin/youtube/helper/utils.py @@ -15,7 +15,7 @@ from math import log10 from ...kodion.constants import CONTENT, LICENSE_TOKEN, LICENSE_URL, PATHS -from ...kodion.items import AudioItem, DirectoryItem, CommandItem, menu_items +from ...kodion.items import AudioItem, CommandItem, DirectoryItem, menu_items from ...kodion.utils import ( datetime_parser, friendly_number, @@ -192,7 +192,7 @@ def update_channel_infos(provider, context, channel_id_dict, channel_item.set_name(title) # image - image = get_thumbnail(thumb_size, snippet.get('thumbnails', {})) + image = get_thumbnail(thumb_size, snippet.get('thumbnails')) channel_item.set_image(image) # - update context menu @@ -288,7 +288,7 @@ def update_playlist_infos(provider, context, playlist_id_dict, title = snippet['title'] playlist_item.set_name(title) - image = get_thumbnail(thumb_size, snippet.get('thumbnails', {})) + image = get_thumbnail(thumb_size, snippet.get('thumbnails')) playlist_item.set_image(image) channel_id = 'mine' if in_my_playlists else snippet['channelId'] @@ -363,7 +363,6 @@ def update_video_infos(provider, context, video_id_dict, playlist_item_id_dict=None, channel_items_dict=None, live_details=True, - use_play_data=True, item_filter=None, data=None): video_ids = list(video_id_dict) @@ -399,6 +398,7 @@ def update_video_infos(provider, context, video_id_dict, subtitles_prompt = settings.get_subtitle_selection() == 1 thumb_size = settings.get_thumbnail_size() thumb_stamp = get_thumb_timestamp() + use_play_data = settings.use_local_history() channel_role = localize(19029) untitled = localize('untitled') @@ -499,7 +499,11 @@ def update_video_infos(provider, context, video_id_dict, ): continue - if not media_item.live and play_data: + if media_item.live: + media_item.set_play_count(0) + use_play_data = False + play_data = None + elif play_data: if 'play_count' in play_data: media_item.set_play_count(play_data['play_count']) @@ -511,8 +515,6 @@ def update_video_infos(provider, context, video_id_dict, if 'last_played' in play_data: media_item.set_last_played(play_data['last_played']) - elif media_item.live: - media_item.set_play_count(0) if start_at: datetime = datetime_parser.parse(start_at) @@ -653,7 +655,7 @@ def update_video_infos(provider, context, video_id_dict, # try to find a better resolution for the image image = media_item.get_image() if not image: - image = get_thumbnail(thumb_size, snippet.get('thumbnails', {})) + image = get_thumbnail(thumb_size, snippet.get('thumbnails')) if image.endswith('_live.jpg'): image = ''.join((image, '?ct=', thumb_stamp)) media_item.set_image(image) @@ -766,18 +768,17 @@ def update_video_infos(provider, context, video_id_dict, ) ) - if not media_item.live and play_data: + if use_play_data: context_menu.append( menu_items.history_mark_unwatched( context, video_id - ) if play_data.get('play_count') else + ) if play_data and play_data.get('play_count') else menu_items.history_mark_watched( context, video_id ) ) - - if (play_data.get('played_percent', 0) > 0 - or play_data.get('played_time', 0) > 0): + if play_data and (play_data.get('played_percent', 0) > 0 + or play_data.get('played_time', 0) > 0): context_menu.append( menu_items.history_reset_resume( context, video_id @@ -832,13 +833,9 @@ def update_video_infos(provider, context, video_id_dict, media_item.add_context_menu(context_menu) -def update_play_info(provider, context, video_id, media_item, video_stream, - use_play_data=True): +def update_play_info(provider, context, video_id, media_item, video_stream): media_item.video_id = video_id - update_video_infos(provider, - context, - {video_id: media_item}, - use_play_data=use_play_data) + update_video_infos(provider, context, {video_id: media_item}) settings = context.get_settings() ui = context.get_ui() @@ -848,7 +845,7 @@ def update_play_info(provider, context, video_id, media_item, video_stream, media_item.live = meta_data.get('status', {}).get('live', False) media_item.set_subtitles(meta_data.get('subtitles', None)) image = get_thumbnail(settings.get_thumbnail_size(), - meta_data.get('thumbnails', {})) + meta_data.get('thumbnails')) if image: if media_item.live: image = ''.join((image, '?ct=', get_thumb_timestamp())) diff --git a/resources/lib/youtube_plugin/youtube/helper/v3.py b/resources/lib/youtube_plugin/youtube/helper/v3.py index a747a6892..8891b2c01 100644 --- a/resources/lib/youtube_plugin/youtube/helper/v3.py +++ b/resources/lib/youtube_plugin/youtube/helper/v3.py @@ -51,16 +51,13 @@ def _process_list_response(provider, context, json_data, item_filter): new_params = {} params = context.get_params() - incognito = params.get('incognito', False) - if incognito: - new_params['incognito'] = incognito + if params.get('incognito'): + new_params['incognito'] = True addon_id = params.get('addon_id', '') if addon_id: new_params['addon_id'] = addon_id settings = context.get_settings() - use_play_data = not incognito and settings.use_local_history() - thumb_size = settings.get_thumbnail_size() fanart_type = params.get('fanart_type') if fanart_type is None: @@ -85,7 +82,7 @@ def _process_list_response(provider, context, json_data, item_filter): title = snippet.get('title', context.localize('untitled')) thumbnails = snippet.get('thumbnails') - if not thumbnails and yt_item.get('_partial'): + if not thumbnails: thumbnails = { thumb_type: { 'url': thumb['url'].format(item_id, ''), @@ -251,8 +248,6 @@ def _process_list_response(provider, context, json_data, item_filter): if isinstance(item, VideoItem): item.video_id = item_id - if incognito: - item.set_play_count(0) # Set track number from playlist, or set to current list length to # match "Default" (unsorted) sort order position = snippet.get('position') or len(result) @@ -289,7 +284,6 @@ def _process_list_response(provider, context, json_data, item_filter): 'upd_kwargs': { 'data': None, 'live_details': True, - 'use_play_data': use_play_data, 'item_filter': item_filter, }, 'complete': False, diff --git a/resources/lib/youtube_plugin/youtube/helper/yt_play.py b/resources/lib/youtube_plugin/youtube/helper/yt_play.py index 86790565b..fef2d338d 100644 --- a/resources/lib/youtube_plugin/youtube/helper/yt_play.py +++ b/resources/lib/youtube_plugin/youtube/helper/yt_play.py @@ -130,10 +130,9 @@ def _play_stream(provider, context): use_history = not (screensaver or incognito or stream.get('live')) use_remote_history = use_history and settings.use_remote_history() - use_play_data = use_history and settings.use_local_history() + use_local_history = use_history and settings.use_local_history() - utils.update_play_info(provider, context, video_id, media_item, - stream, use_play_data=use_play_data) + utils.update_play_info(provider, context, video_id, media_item, stream) seek_time = 0.0 if params.get('resume') else params.get('seek', 0.0) start_time = params.get('start', 0.0) @@ -146,7 +145,7 @@ def _play_stream(provider, context): # if end_time: # video_item.set_duration_from_seconds(end_time) - play_count = use_play_data and media_item.get_play_count() or 0 + play_count = use_local_history and media_item.get_play_count() or 0 playback_stats = stream.get('playback_stats') playback_data = { @@ -156,7 +155,7 @@ def _play_stream(provider, context): 'playing_file': media_item.get_uri(), 'play_count': play_count, 'use_remote_history': use_remote_history, - 'use_local_history': use_play_data, + 'use_local_history': use_local_history, 'playback_stats': playback_stats, 'seek_time': seek_time, 'start_time': start_time, diff --git a/resources/lib/youtube_plugin/youtube/helper/yt_playlist.py b/resources/lib/youtube_plugin/youtube/helper/yt_playlist.py index c28890577..720343eac 100644 --- a/resources/lib/youtube_plugin/youtube/helper/yt_playlist.py +++ b/resources/lib/youtube_plugin/youtube/helper/yt_playlist.py @@ -253,7 +253,7 @@ def _process_select_playlist(provider, context): snippet = playlist.get('snippet', {}) title = snippet.get('title', '') description = snippet.get('description', '') - thumbnail = get_thumbnail(thumb_size, snippet.get('thumbnails', {})) + thumbnail = get_thumbnail(thumb_size, snippet.get('thumbnails')) playlist_id = playlist.get('id', '') if title and playlist_id: items.append(( diff --git a/resources/settings.xml b/resources/settings.xml index ee9e3b98b..3d27f3270 100644 --- a/resources/settings.xml +++ b/resources/settings.xml @@ -973,6 +973,89 @@ 14045 + + 0 + 1 + + + + + + + + + + + 4 + false + + + + 0 + 0 + + + + + + + + + + + + 2 + + + + + 0 + + + true + + + 2 + + + + + 0 + 8080 + + 1 + 1 + 65535 + + + 2 + + + + + 0 + + + true + + + 2 + + + + + 0 + + + true + + + 2 + + + true + +