Compare commits

...

3 commits

Author SHA1 Message Date
MoojMidge
ae9699c0cc Add ViewManager
- Updated to be more self-contained and work better with unsupported skins
- TODO: Add support for setting default sort order and sort direction

Fix content type not being set to episodes

- Fix #586, #589

Update for restructure of xbmc_plugin

- Only set view mode if directory items successfully added

Fix preselect on view_manager view lists

Update to match new setup wizard

Update for reorganised/renamed constants fca610c

Update for updated XbmcContext.apply_content 8a8247a

Update for new localize and logging methods

Update to fix setting view mode not working if container is still updating

Update to handle sort method and order and workaround #1243

Update to handle localised sort order #1309
2025-11-20 05:48:44 +11:00
MoojMidge
cdbcf5b517 Version bump v7.3.0 2025-11-20 05:48:44 +11:00
MoojMidge
14d446a8e4 Revert change to busy polling interval #1339 2025-11-19 07:37:29 +11:00
16 changed files with 647 additions and 7 deletions

View file

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<addon id="plugin.video.youtube" name="YouTube" version="7.3.0+beta.10" provider-name="anxdpanic, bromix, MoojMidge">
<addon id="plugin.video.youtube" name="YouTube" version="7.3.0" provider-name="anxdpanic, bromix, MoojMidge">
<requires>
<import addon="xbmc.python" version="3.0.0"/>
<import addon="script.module.requests" version="2.27.1"/>

View file

@ -1,3 +1,188 @@
## v7.3.0
### Fixed
- Revert change to busy polling interval #1339
- Prune invalid entries from DB when closing connection #1331
- Fix regressions with SQLite db operations #1331
- Disable label masks being used in Kodi 18 #1327
- Python 2 compatibility workaround for lack of timeout when trying to acquire an RLock #1327
- More expansive handling of inconsistent urllib3 exception re-raising
- Fix regression in handling audio only setting after d154325c5b672dccc6a17413063cfdeb32256ffd
- Fix comments not using correct sort methods
- Fix incorrectly using playlist cache entries that have been invalidated by playlist modification
- Fix some context menu actions failing for video item bookmarks
- Ensure listings and items added by the addon have correct sort order
- Fix resetting client region when playing media with subtitles enabled
- Only add playable items to playlist when adding related items
- Fix using invalid default end limit with Playlist.GetItems JSONRPC method
- Fix conversion of SRT subtitles to WebVTT #1256
- Workaround playback failure of progressive streams
- Fix re-sorting live search lists
- Disable use of custom thumbnail urls #1245
- Workaround addon service not starting prior to plugin invocation #1298
- Fix unofficial version using localised sort order #1309
- Fix parsing of logged_in query parameter
- Fix typo in YouTubePlayerClient error hook
- Fix not resolving single playable items when using the uri2addon plugin endpoint #1300
- Correctly check whether access tokens are available to be used for player requests
- Fix not correctly resetting client instance
- Dont restore container position on forced refresh when playback ends
- Better handle urllib3 re-raising low level errors but sometimes not
- Ignore unused parameters in item constructors #1282
- Various misc fixes for focus and position loss on refresh
- Fix possible unnecessary listing refresh after playlist action
- Don't check items added to non music or video playlists
- Re-enable setting for displaying saved playlists #1023
- Fix exceptions with using non-existent request response as context manager #1279
- Use different default player client request which provides more captions in response #1250
- Exclude retrying player clients that do not support authentication if authentication is required #1273
- Only request authenticated player request once, if not otherwise required #1273
- Fix not updating breadcrumb after certain context menu actions
- Fix setting focus on items in listing when parent item is not shown #1012
- Reduce CPU usage of service runner loop when idle
- Simplify window history fallback for search inputs #1070 #1266
- Fix MPD quality selection #1268
- Fix stream feature for disabling HFR at max resolution #539
- Don't re-raise BrokenPipeError in RequestHandler.handle_one_request #1259
- Fix including details in label2 mask when video details in listings is disabled #1265
- Fix incorrect modification of custom thumbnails #1245
- Refresh stale cached entries if new player data is available #1259
- Disable use of captions from clients that are sometimes aggressively rate limited #1250
- Switch browse client for recommended videos #1254
- Ignore failing player requests that require signing in but won't accept OAuth2 authentication #1254
- Improve querying of GUI info to work with widgets and custom windows #1243
- Fix using locale specific abbreviations for weekday and month in If-Modified-Since header #1246
- Workaround various Kodi 18 and Python 2 issues #1246
- Fix inconsistencies between item IDs used as params that could result in exceptions
- Fix generated page token not working for first page in listing
- Don't replace non-standard JPEG thumbnails with WebP thumbnails #1245
- Fix not parsing infolabels used in plugin url path #1239 #1243
- Fix not parsing infolabels used as plugin url query params #1239
- Fix unnecessarily processing window properties #1238
- Workaround for distributions that patch Kodi to disable System.InternetState #1224
- Workaround issue with search not returning items if no search query is used
- Misc updates to try and prevent some GVS requests from failing #1222
- Fix not correctly updating channel details of subscriptions #1226
- Redact IP address in stream urls when not using InputStream.Adaptive
- Fix implementation of logging module debugging property
- Python 2 compatibility fix to automatically handle timezones when determining timestamps
- Fix not skipping invalid items in plugin list response
- Fix not showing Create bookmark item if bookmarks list is empty
- Python 2 compatibility fixes for pretty print logging and lack of datetime.datetime.timestamp
- Fix typo resulting in spatial audio streams not being correctly enabled/disabled
- Ensure DELETE API requests are properly authorised #1226
- Update XbmcContext.localize to handle string interpolation #1225
- Attempt to mitigate possible memory leaks associated with use of Python Requests
- Ensure plugin folders in channel listings are not processed as YouTube items
- Fix not refreshing cached data when not logged in #1224
- Fix potentially misidentifying vp9.2 video streams #1222
- Fix not fully redacting logged stream data
- Fix clip start and end time parsing
- Fix possible exception when fetching subtitles after http server fails to wakeup
- Improve concurrent JSON file IO operations #1218
- Change pruning of sqlite databases to avoid potential deadlocks #1161
- Avoid potential deadlock in v3._process_list_response #1161
- Improve listing pre and post fill methods #1161
- Fix repeating previous V1 browse/next requests when no continuation is available #1161
- Fix typo in allowable search parameters
- Ensure best available quality is used for thumbnails as fanart #1212
- Fix double forward slash in channel url in description
- Fix toggling watched status on non-forced refresh
- Ensure logged in status is properly (re)set when client is reset #1210
- Don't rely on dict insertion order for client groups #1185
- Ensure authenticated requests are used only when necessary #1210 #1196
- Workaround playlistNotFound error in Related Videos #1210 #1196
- Fix stream quality checks incorrectly identifying 480p streams as 360p
- Add workarounds for various Kodi/ISA subtitle incompatibilities and formatting issues #489 #1147
- Identify API requests requiring authentication
- Fix incorrect page number used when post filling listings
- Prevent unnecessary API requests when refreshing listing that uses channel filters
- Improve loading of malformed/partial access_manager.json #1173
### Changed
- Improve robustness of fetching recommended and related videos
- Improve workarounds for SQLite concurrency issues
- Remove possibly invalid access token if an authentication error occurs
- Better organise and use standard labels for http server address and port settings
- Try to make http server IP address selection even more obvious when running Setup Wizard #1320
- Improve logging of errors caused by localised strings that have been incorrectly translated
- Improve offline access to cached data
- Updates to SQLite database lock handling
- Ignore player request failures that may incorrectly indicate a need to sign-in #1312
- Include playlist_id listitem property for items from virtual playlists
- Don't list users own playlists in listing of saved playlists
- Allow sign-in when partially logged in without needing to sign-out
- Identify if user is only partially logged in
- Use persistent visitor data where possible except when incognito
- Allow additional query parameters to be inherited from parent listing #1282
- Improve process for initial player request if remote history not enabled #1273
- Disable unusable player clients #1273
- Disable multiple busy dialog crash workarounds in Kodi 22
- Include visitorData in subtitle request headers along with referer #1250
- Revert use of WEBP thumbnails #1245
- Improve notification of player request errors #1254 #1262
- Add notification if reCaptcha check is required
- Improve workarounds for failing authorised player requests #1254
- Allow plugin url query parameters to hide folders in search and channel playlist listing #1251
- Use WebP thumbnails instead of JPEG thumbnails
- Update Setup Wizard for the various new Stream Features that have been added
- Don't cache playback associated requests
- Bypass requests cache when a listing is refreshed
- Update order and wording of prompts used when adding custom bookmark
- Allow bookmarking channel of videos within Bookmarks list
- Return empty listing on failure to parse YouTube url
- Update known itag details
- Do not show notification when automatically removing played video from Watch Later list
- Add visitor ID to stream headers #1222
- Improve logging of adaptive streams from player requests #1222
- Identify and de-prioritise DRC audio streams
- Specifically use elapsed time for plugin profiling if available
- Use new BookmarkItem class for custom bookmarks to allow updating details like normal bookmarks
- Default to list as path command if not given when navigating to various internal listings
- Re-enable http server idle shutdown for all platforms
- Update default cache size to 50 MiB
- Enable caching of YouTube virtual lists #1220
- Improve fetching of cached playlist items #1220
- Allow for use of last known good value if JSON file read IPC timeout occurs #1220
- Limit JSON-RPC usage when interacting with Kodi playlist #1220
- Improve syncing local history with Kodi play count
- api_keys.json not backwards compatible with older addon versions
- Set default live stream type to adaptive HLS
- Allow saving of play count and last played date for live streams
- Disable script.trakt scrobbling when playing YouTube videos
- Allow failure of unauthorised player requests to be ignored #1211
- Try to prevent misuse of strm files #1208
- Update thumbnails size settings and selection logic #1204
- Only show folders in channel playlists listing on first page and if not hidden
- Improve cache performance and reliability
- Improve management of user data stored as JSON files
### New
- Add refresh to context menu of playlists
- Allow watch urls from music.youtube.com to be directly handled by the addon
- Allow urls from www.youtubekids.com to be directly handled by the addon
- Add support for listing members only content of channels
- Add selections to hide various folders from listings #1282
- Add support for additional OAuth2 client to allow playback of age restricted videos #1273
- Add dubbed audio preferences to stream features #1036 #1228 #1232
- Add support for directly adding YouTube URL as custom bookmark
- Add support for 3D/VR video and spatial audio in stream selections
- Add Setup Wizard steps to change custom Watch History and Watch Later playlists to internal YouTube lists #1210
- Add support for viewing YouTube history list (HL)
- Add support for podcasts in related videos #1161
- Add notification on successfully adding item to local Watch Later list #1210
- Add support for saving/removing playlists to/from YouTube library (Saved Playlists) #1023
- Add support for removing liked videos from YouTube Liked videos list
- Add support for auto removing videos from YouTube Watch Later list after playback #1210
- Add support for removing videos from YouTube Watch Later list #1210
- Add support for adding videos to YouTube Watch Later list #1210
- Initial support for viewing Saved Playlists #1023
- Initial support for viewing YouTube Watch Later list (WL) #1210
- Implement request cache
- Add ability to create custom bookmarks #1208
- Add subtitle type selection to stream features
- Add plugin execution timeout to forcibly terminate execution after set time limit #1161
- Improve addon logging using customised Python logging module
## v7.3.0+beta.10
### Fixed
- Prune invalid entries from DB when closing connection #1331

View file

@ -144,6 +144,7 @@ class AbstractProvider(object):
if last_run and last_run > 1:
self.pre_run_wizard_step(provider=self, context=context)
wizard_steps = self.get_wizard_steps()
wizard_steps.extend(ui.get_view_manager().get_wizard_steps())
step = 0
steps = len(wizard_steps)

View file

@ -186,6 +186,8 @@ PAGE = 'page'
PLAYLIST_IDS = 'playlist_ids'
SCREENSAVER = 'screensaver'
SEEK = 'seek'
SORT_DIR = 'sort_dir'
SORT_METHOD = 'sort_method'
START = 'start'
VIDEO_IDS = 'video_ids'
@ -346,6 +348,8 @@ __all__ = (
'PLAYLIST_IDS',
'SCREENSAVER',
'SEEK',
'SORT_DIR',
'SORT_METHOD',
'START',
'VIDEO_IDS',

View file

@ -11,8 +11,8 @@
from __future__ import absolute_import, division, unicode_literals
VIDEO_CONTENT = 'videos'
LIST_CONTENT = 'files'
VIDEO_CONTENT = 'episodes'
LIST_CONTENT = 'default'
COMMENTS = 'comments'
HISTORY = 'history'

View file

@ -14,6 +14,7 @@ import sys
from . import const_content_types as CONTENT
from ..compatibility import (
xbmc,
xbmcplugin,
)
@ -77,12 +78,31 @@ methods = [
('VIDEO_ORIGINAL_TITLE', 20376, 57),
('VIDEO_ORIGINAL_TITLE_IGNORE_THE', 20376, None),
]
SORT_ID_MAPPING = {}
SORT = sys.modules[__name__]
name = label_id = sort_by = sort_method = None
for name, label_id, sort_by in methods:
sort_method = getattr(xbmcplugin, 'SORT_METHOD_' + name, 0)
setattr(SORT, name, sort_method)
if sort_by is not None:
SORT_ID_MAPPING.update((
(name, sort_by),
(xbmc.getLocalizedString(label_id), sort_by),
(sort_method, sort_by if sort_method else 0),
))
SORT_ID_MAPPING.update((
(CONTENT.VIDEO_CONTENT.join(('__', '__')), SORT.UNSORTED),
(CONTENT.LIST_CONTENT.join(('__', '__')), SORT.LABEL),
(CONTENT.COMMENTS.join(('__', '__')), SORT.CHANNEL),
(CONTENT.HISTORY.join(('__', '__')), SORT.LASTPLAYED),
))
SORT_DIR = {
xbmc.getLocalizedString(584): 'ascending',
xbmc.getLocalizedString(585): 'descending',
}
# Label mask token details:
# https://github.com/xbmc/xbmc/blob/master/xbmc/utils/LabelFormatter.cpp#L33-L105
@ -179,6 +199,7 @@ COMMENTS_CONTENT_SIMPLE = (
del (
sys,
CONTENT,
xbmc,
xbmcplugin,
methods,
SORT,

View file

@ -61,6 +61,8 @@ from ..constants import (
PLAY_USING,
SCREENSAVER,
SEEK,
SORT_DIR,
SORT_METHOD,
START,
SUBSCRIPTION_ID,
VIDEO_ID,
@ -169,6 +171,8 @@ class AbstractContext(object):
'q',
'rating',
'reload_path',
SORT_DIR,
SORT_METHOD,
'search_type',
SUBSCRIPTION_ID,
'uri',

View file

@ -705,6 +705,7 @@ class XbmcContext(AbstractContext):
path=self.get_path())
if content_type != 'default':
xbmcplugin.setContent(self._plugin_handle, content_type)
ui.get_view_manager().set_view_mode(content_type)
if category_label is None:
category_label = self.get_param('category_label')

View file

@ -34,6 +34,8 @@ from ...constants import (
REFRESH_CONTAINER,
RELOAD_ACCESS_MANAGER,
REROUTE_PATH,
SORT_DIR,
SORT_METHOD,
SYNC_LISTITEM,
TRAKT_PAUSE_FLAG,
VIDEO_ID,
@ -417,7 +419,31 @@ class XbmcPlugin(AbstractPlugin):
container = ui.get_property(CONTAINER_ID)
position = ui.get_property(CONTAINER_POSITION)
# set alternative view mode
view_manager = ui.get_view_manager()
if view_manager.is_override_view_enabled():
post_run_actions.append((
view_manager.apply_view_mode,
{
'context': context,
},
))
if is_same_path:
sort_method = kwargs.get(SORT_METHOD)
sort_dir = kwargs.get(SORT_DIR)
if sort_method and sort_dir:
post_run_actions.append((
view_manager.apply_sort_method,
{
'context': context,
SORT_METHOD: sort_method,
SORT_DIR: sort_dir,
CONTAINER_POSITION: position if forced else None,
},
))
position = None
if (container and position
and (forced or position == 'current')
and (not played_video_id or route)):
@ -439,7 +465,7 @@ class XbmcPlugin(AbstractPlugin):
@staticmethod
def post_run(context, ui, *actions, **kwargs):
timeout = kwargs.get('timeout', 30)
interval = kwargs.get('interval', 0.01)
interval = kwargs.get('interval', 0.1)
for action in actions:
while not ui.get_container(container_type=None, check_ready=True):
timeout -= interval

View file

@ -17,6 +17,8 @@ from .constants import (
CHECK_SETTINGS,
FOLDER_URI,
PATHS,
SORT_DIR,
SORT_METHOD,
)
from .context import XbmcContext
from .debug import Profiler
@ -103,6 +105,20 @@ def run(context=_context,
refresh = context.refresh_requested(force=True, off=True, params=params)
new_params['refresh'] = refresh if refresh else 0
sort_method = (
params.get(SORT_METHOD)
or ui.get_infolabel('Container.SortMethod')
)
if sort_method:
new_kwargs[SORT_METHOD] = sort_method
sort_dir = (
params.get(SORT_DIR)
or ui.get_infolabel('Container.SortOrder')
)
if sort_dir:
new_kwargs[SORT_DIR] = sort_dir
if new_params:
context.set_params(**new_params)
@ -112,7 +128,7 @@ def run(context=_context,
log_params[key] = '<redacted>'
system_version = context.get_system_version()
log.info(('Running v{version}',
log.info(('Running v{version} (unofficial)',
'Kodi: v{kodi}',
'Python: v{python}',
'Handle: {handle}',

View file

@ -493,7 +493,7 @@ def run(argv):
log.verbose_logging = False
system_version = context.get_system_version()
log.info(('Running v{version}',
log.info(('Running v{version} (unofficial)',
'Kodi: v{kodi}',
'Python: v{python}',
'Category: {category!r}',

View file

@ -51,7 +51,7 @@ def run():
monitor=monitor)
system_version = context.get_system_version()
logging.info(('Starting v{version}',
logging.info(('Starting v{version} (unofficial)',
'Kodi: v{kodi}',
'Python: v{python}'),
version=context.get_version(),

View file

@ -22,6 +22,9 @@ class AbstractContextUI(object):
message_template=None):
raise NotImplementedError()
def get_view_manager(self):
raise NotImplementedError()
@staticmethod
def on_keyboard_input(title, default='', hidden=False):
raise NotImplementedError()

View file

@ -0,0 +1,342 @@
# -*- coding: utf-8 -*-
"""
Copyright (C) 2014-2016 bromix (plugin.video.youtube)
Copyright (C) 2016-2025 plugin.video.youtube
SPDX-License-Identifier: GPL-2.0-only
See LICENSES/GPL-2.0-only for more information.
"""
from __future__ import absolute_import, division, unicode_literals
from ... import logging
from ...compatibility import xbmc
from ...constants import (
CONTAINER_POSITION,
CONTENT,
SORT,
SORT_DIR,
SORT_METHOD,
)
class ViewManager(object):
log = logging.getLogger(__name__)
SETTINGS = {
'override': 'kodion.view.override', # (bool)
'view_default': 'kodion.view.default', # (int)
'view_type': 'kodion.view.{0}', # (int)
}
SUPPORTED_TYPES_MAP = {
CONTENT.LIST_CONTENT: 'default',
CONTENT.VIDEO_CONTENT: 'episodes',
}
STRING_MAP = {
'prompt': 30777,
'unsupported_skin': 10109,
'supported_skin': 14240,
'albums': 30035,
'artists': 30034,
'default': 30027,
'episodes': 30028,
'movies': 30029,
'songs': 30033,
'tvshows': 30032,
}
SKIN_DATA = {
'skin.confluence': {
'default': (
{'name': 'List', 'id': 50},
{'name': 'Big List', 'id': 51},
{'name': 'Thumbnail', 'id': 500}
),
'movies': (
{'name': 'List', 'id': 50},
{'name': 'Big List', 'id': 51},
{'name': 'Thumbnail', 'id': 500},
{'name': 'Media info', 'id': 504},
{'name': 'Media info 2', 'id': 503}
),
'episodes': (
{'name': 'List', 'id': 50},
{'name': 'Big List', 'id': 51},
{'name': 'Thumbnail', 'id': 500},
{'name': 'Media info', 'id': 504},
{'name': 'Media info 2', 'id': 503}
),
'tvshows': (
{'name': 'List', 'id': 50},
{'name': 'Big List', 'id': 51},
{'name': 'Thumbnail', 'id': 500},
{'name': 'Poster', 'id': 500},
{'name': 'Wide', 'id': 505},
{'name': 'Media info', 'id': 504},
{'name': 'Media info 2', 'id': 503},
{'name': 'Fanart', 'id': 508}
),
'musicvideos': (
{'name': 'List', 'id': 50},
{'name': 'Big List', 'id': 51},
{'name': 'Thumbnail', 'id': 500},
{'name': 'Media info', 'id': 504},
{'name': 'Media info 2', 'id': 503}
),
'songs': (
{'name': 'List', 'id': 50},
{'name': 'Big List', 'id': 51},
{'name': 'Thumbnail', 'id': 500},
{'name': 'Media info', 'id': 506}
),
'albums': (
{'name': 'List', 'id': 50},
{'name': 'Big List', 'id': 51},
{'name': 'Thumbnail', 'id': 500},
{'name': 'Media info', 'id': 506}
),
'artists': (
{'name': 'List', 'id': 50},
{'name': 'Big List', 'id': 51},
{'name': 'Thumbnail', 'id': 500},
{'name': 'Media info', 'id': 506}
)
},
'skin.aeon.nox.5': {
'default': (
{'name': 'List', 'id': 50},
{'name': 'Episodes', 'id': 502},
{'name': 'LowList', 'id': 501},
{'name': 'BannerWall', 'id': 58},
{'name': 'Shift', 'id': 57},
{'name': 'Posters', 'id': 56},
{'name': 'ShowCase', 'id': 53},
{'name': 'Landscape', 'id': 52},
{'name': 'InfoWall', 'id': 51}
)
},
'skin.xperience1080+': {
'default': (
{'name': 'List', 'id': 50},
{'name': 'Thumbnail', 'id': 500},
),
'episodes': (
{'name': 'List', 'id': 50},
{'name': 'Info list', 'id': 52},
{'name': 'Fanart', 'id': 502},
{'name': 'Landscape', 'id': 54},
{'name': 'Poster', 'id': 55},
{'name': 'Thumbnail', 'id': 500},
{'name': 'Banner', 'id': 60}
),
},
'skin.xperience1080': {
'default': (
{'name': 'List', 'id': 50},
{'name': 'Thumbnail', 'id': 500},
),
'episodes': (
{'name': 'List', 'id': 50},
{'name': 'Info list', 'id': 52},
{'name': 'Fanart', 'id': 502},
{'name': 'Landscape', 'id': 54},
{'name': 'Poster', 'id': 55},
{'name': 'Thumbnail', 'id': 500},
{'name': 'Banner', 'id': 60}
),
},
'skin.estuary': {
'default': (
{'name': 'IconWall', 'id': 52},
{'name': 'WideList', 'id': 55},
),
'videos': (
{'name': 'Shift', 'id': 53},
{'name': 'InfoWall', 'id': 54},
{'name': 'WideList', 'id': 55},
{'name': 'Wall', 'id': 500},
),
'episodes': (
{'name': 'InfoWall', 'id': 54},
{'name': 'Wall', 'id': 500},
{'name': 'WideList', 'id': 55},
)
}
}
def __init__(self, context):
self._context = context
self._view_mode = None
def is_override_view_enabled(self):
return self._context.get_settings().get_bool(self.SETTINGS['override'])
def get_wizard_steps(self):
return (self.run,)
def run(self, context, step, steps, **_kwargs):
localize = context.localize
skin_id = xbmc.getSkinDir()
if skin_id in self.SKIN_DATA:
status = localize(self.STRING_MAP['supported_skin'])
else:
status = localize(self.STRING_MAP['unsupported_skin'])
prompt_text = localize(self.STRING_MAP['prompt'], (skin_id, status))
step += 1
if context.get_ui().on_yes_no_input(
'{youtube} - {setup_wizard} ({step}/{steps})'.format(
youtube=localize('youtube'),
setup_wizard=localize('setup_wizard'),
step=step,
steps=steps,
),
localize('setup_wizard.prompt.x', prompt_text)
):
for view_type in self.SUPPORTED_TYPES_MAP:
self.update_view_mode(skin_id, view_type)
return step
def get_view_mode(self):
if self._view_mode is None:
self.set_view_mode()
return self._view_mode
def set_view_mode(self, view_type='default'):
settings = self._context.get_settings()
default = settings.get_int(self.SETTINGS['view_default'], 50)
if view_type == 'default':
view_mode = default
else:
view_type = self.SUPPORTED_TYPES_MAP.get(view_type, 'default')
view_mode = settings.get_int(
self.SETTINGS['view_type'].format(view_type), default
)
self._view_mode = view_mode
def update_view_mode(self, skin_id, view_type='default'):
view_id = -1
settings = self._context.get_settings()
ui = self._context.get_ui()
content_type = self.SUPPORTED_TYPES_MAP[view_type]
if content_type not in self.STRING_MAP:
self.log.warning('Unsupported content type: %r', content_type)
return False
title = self._context.localize(self.STRING_MAP[content_type])
view_setting = self.SETTINGS['view_type'].format(content_type)
current_value = settings.get_int(view_setting)
if current_value == -1:
self.log.warning('No setting for content type: %r', content_type)
return False
skin_data = self.SKIN_DATA.get(skin_id, {})
view_type_data = skin_data.get(view_type) or skin_data.get(content_type)
if view_type_data:
items = []
preselect = -1
for view_data in view_type_data:
view_id = view_data['id']
items.append((view_data['name'], view_id))
if view_id == current_value:
preselect = len(items) - 1
view_id = ui.on_select(title, items, preselect=preselect)
else:
self.log.warning('Unsupported view: %r', view_type)
if view_id == -1:
result, view_id = ui.on_numeric_input(title, current_value)
if not result:
return False
if view_id > -1:
settings.set_int(view_setting, view_id)
settings.set_bool(self.SETTINGS['override'], True)
return True
return False
def apply_view_mode(self, context):
view_mode = self.get_view_mode()
if view_mode is None:
return
self.log.debug('Applying view mode: %r', view_mode)
context.execute('Container.SetViewMode(%s)' % view_mode)
@classmethod
def apply_sort_method(cls, context, **kwargs):
execute = context.execute
get_infobool = xbmc.getCondVisibility
sort_method = (
kwargs.get(SORT_METHOD)
or CONTENT.VIDEO_CONTENT.join(('__', '__'))
)
sort_id = SORT.SORT_ID_MAPPING.get(sort_method)
if sort_id is None:
cls.log.warning('Unknown sort method: %r', sort_method)
return
sort_dir = kwargs.get(SORT_DIR)
_sort_dir = SORT.SORT_DIR.get(sort_dir)
if _sort_dir is None:
cls.log.warning('Invalid sort direction: %r', sort_dir)
return
position = kwargs.get(CONTAINER_POSITION)
if position is not None:
context.get_ui().focus_container(position=position)
# Workaround for Container.SetSortMethod failing for some sort methods
num_attempts = 0
while num_attempts < 4:
# Workaround for Container.SetSortMethod(0) being a noop
# https://github.com/xbmc/xbmc/blob/7e1a55cb861342cd9062745161d88aca08dcead1/xbmc/windows/GUIMediaWindow.cpp#L502
if sort_id == 0:
# Sort by track number to reset sort order to default order
if not num_attempts % 2:
_sort_method = 'TRACKNUM'
_sort_id = SORT.SORT_ID_MAPPING.get(_sort_method)
sort_action = 'Container.SetSortMethod(%s)' % _sort_id
# Then switch to previous sort method which is default/unsorted
# as per the order set in XbmcContext.apply_content
else:
_sort_method = 'UNSORTED'
_sort_id = SORT.SORT_ID_MAPPING.get(_sort_method)
sort_action = 'Container.PreviousSortMethod'
else:
_sort_method = sort_method
_sort_id = sort_id
sort_action = 'Container.SetSortMethod(%s)' % _sort_id
cls.log.debug('Applying sort method: {method!r} ({id})',
method=_sort_method,
id=_sort_id)
execute(sort_action)
context.sleep(0.1)
if not get_infobool('Container.SortDirection(%s)' % _sort_dir):
cls.log.debug('Applying sort direction: %r', sort_dir)
# This builtin should be Container.SortDirection but has been
# broken since Kodi v16
# https://github.com/xbmc/xbmc/commit/ac870b64b16dfd0fc2bd0496c14529cf6d563f41
execute('Container.SetSortDirection')
context.sleep(0.1)
num_attempts += 1
if get_infobool('Container.SortMethod(%s)' % sort_id):
break
else:
cls.log.warning('Unable to apply sorting:'
' {sort_method!r} ({sort_id}) {sort_dir!r}',
sort_method=sort_method,
sort_id=sort_id,
sort_dir=sort_dir)

View file

@ -12,6 +12,7 @@ from __future__ import absolute_import, division, unicode_literals
from weakref import proxy
from .view_manager import ViewManager
from ..abstract_context_ui import AbstractContextUI
from ... import logging
from ...compatibility import string_type, xbmc, xbmcgui
@ -47,6 +48,7 @@ class XbmcContextUI(AbstractContextUI):
def __init__(self, context):
super(XbmcContextUI, self).__init__()
self._context = context
self._view_manager = None
def create_progress_dialog(self,
heading,
@ -77,6 +79,12 @@ class XbmcContextUI(AbstractContextUI):
),
)
def get_view_manager(self):
if self._view_manager is None:
self._view_manager = ViewManager(self._context)
return self._view_manager
@staticmethod
def on_keyboard_input(title, default='', hidden=False):
# Starting with Gotham (13.X > ...)

View file

@ -900,6 +900,35 @@
</constraints>
<control format="string" type="spinner"/>
</setting>
<setting id="kodion.view.override" type="boolean" label="30026" help="">
<level>0</level>
<default>false</default>
<control type="toggle"/>
</setting>
<setting id="kodion.view.default" type="integer" parent="kodion.view.override" label="30027" help="">
<level>0</level>
<default>55</default>
<dependencies>
<dependency type="enable">
<condition setting="kodion.view.override" operator="is">true</condition>
</dependency>
</dependencies>
<control format="integer" type="edit">
<heading>30027</heading>
</control>
</setting>
<setting id="kodion.view.episodes" type="integer" parent="kodion.view.override" label="30028" help="">
<level>0</level>
<default>55</default>
<dependencies>
<dependency type="enable">
<condition setting="kodion.view.override" operator="is">true</condition>
</dependency>
</dependencies>
<control format="integer" type="edit">
<heading>30028</heading>
</control>
</setting>
</group>
<group id="regional" label="14222">
<setting id="youtube.language_region.configure" type="action" label="30527" help="">