From b9296e699ad0cf21c2e69d2afdf777e11b273e8a Mon Sep 17 00:00:00 2001
From: MoojMidge <56883549+MoojMidge@users.noreply.github.com>
Date: Sun, 8 Oct 2023 14:34:23 +1100
Subject: [PATCH 001/141] Fix error with subtitle selection prompt
---
resources/lib/youtube_plugin/youtube/helper/subtitles.py | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/resources/lib/youtube_plugin/youtube/helper/subtitles.py b/resources/lib/youtube_plugin/youtube/helper/subtitles.py
index b6e077985..2286d2c3d 100644
--- a/resources/lib/youtube_plugin/youtube/helper/subtitles.py
+++ b/resources/lib/youtube_plugin/youtube/helper/subtitles.py
@@ -166,9 +166,9 @@ def _prompt(self):
translations = [(track.get('languageCode'), self._get_language_name(track)) for track in self.translation_langs]
languages = tracks + translations
if languages:
- choice = self.context.get_ui().on_select(self.context.localize(30560), [language_name for language, language_name in languages])
+ choice = self.context.get_ui().on_select(self.context.localize(30560), [language for _, language in languages])
if choice != -1:
- return self._get(language=languages[choice][0], language_name=languages[choice][1])
+ return self._get(lang_code=languages[choice][0], language=languages[choice][1])
self.context.log_debug('Subtitle selection cancelled')
return []
self.context.log_debug('No subtitles found for prompt')
From 6acd239e4236381df07b749dc65ad66d62f5cf20 Mon Sep 17 00:00:00 2001
From: MoojMidge <56883549+MoojMidge@users.noreply.github.com>
Date: Sun, 8 Oct 2023 14:40:02 +1100
Subject: [PATCH 002/141] Misc minor fixes
---
.../lib/youtube_plugin/kodion/impl/abstract_context.py | 2 +-
.../youtube_plugin/kodion/impl/abstract_context_ui.py | 4 ++--
.../lib/youtube_plugin/kodion/impl/xbmc/xbmc_items.py | 1 -
.../lib/youtube_plugin/kodion/json_store/__init__.py | 1 -
.../youtube/helper/signature/__init__.py | 2 --
.../lib/youtube_plugin/youtube/helper/url_resolver.py | 2 +-
resources/lib/youtube_plugin/youtube/helper/utils.py | 2 +-
resources/lib/youtube_plugin/youtube/helper/v3.py | 8 ++++----
resources/lib/youtube_plugin/youtube/provider.py | 10 +++++-----
resources/lib/youtube_requests.py | 2 +-
10 files changed, 15 insertions(+), 19 deletions(-)
diff --git a/resources/lib/youtube_plugin/kodion/impl/abstract_context.py b/resources/lib/youtube_plugin/kodion/impl/abstract_context.py
index 99d1cb702..6cc2541cf 100644
--- a/resources/lib/youtube_plugin/kodion/impl/abstract_context.py
+++ b/resources/lib/youtube_plugin/kodion/impl/abstract_context.py
@@ -54,7 +54,7 @@ def format_time(self, time_obj):
def get_language(self):
raise NotImplementedError()
- def get_language_name(self):
+ def get_language_name(self, lang_id=None):
raise NotImplementedError()
def get_region(self):
diff --git a/resources/lib/youtube_plugin/kodion/impl/abstract_context_ui.py b/resources/lib/youtube_plugin/kodion/impl/abstract_context_ui.py
index 7ed3feffe..eb7419174 100644
--- a/resources/lib/youtube_plugin/kodion/impl/abstract_context_ui.py
+++ b/resources/lib/youtube_plugin/kodion/impl/abstract_context_ui.py
@@ -25,7 +25,7 @@ def on_keyboard_input(self, title, default='', hidden=False):
def on_numeric_input(self, title, default=''):
raise NotImplementedError()
- def on_yes_no_input(self, title, text):
+ def on_yes_no_input(self, title, text, nolabel='', yeslabel=''):
raise NotImplementedError()
def on_ok(self, title, text):
@@ -40,7 +40,7 @@ def on_select(self, title, items=None):
def open_settings(self):
raise NotImplementedError()
- def show_notification(self, message, header='', image_uri='', time_milliseconds=5000):
+ def show_notification(self, message, header='', image_uri='', time_milliseconds=5000, audible=True):
raise NotImplementedError()
@staticmethod
diff --git a/resources/lib/youtube_plugin/kodion/impl/xbmc/xbmc_items.py b/resources/lib/youtube_plugin/kodion/impl/xbmc/xbmc_items.py
index c1341a023..5f9e601d2 100644
--- a/resources/lib/youtube_plugin/kodion/impl/xbmc/xbmc_items.py
+++ b/resources/lib/youtube_plugin/kodion/impl/xbmc/xbmc_items.py
@@ -35,7 +35,6 @@ def set_info(self, *args, **kwargs):
def to_play_item(context, play_item):
context.log_debug('Converting PlayItem |%s|' % play_item.get_uri())
-
is_strm = str(context.get_param('strm', False)).lower() == 'true'
thumb = play_item.get_image() if play_item.get_image() else u'DefaultVideo.png'
diff --git a/resources/lib/youtube_plugin/kodion/json_store/__init__.py b/resources/lib/youtube_plugin/kodion/json_store/__init__.py
index d4ff961a5..278ded74a 100644
--- a/resources/lib/youtube_plugin/kodion/json_store/__init__.py
+++ b/resources/lib/youtube_plugin/kodion/json_store/__init__.py
@@ -12,4 +12,3 @@
from .login_tokens import LoginTokenStore
__all__ = ['JSONStore', 'APIKeyStore', 'LoginTokenStore']
-
diff --git a/resources/lib/youtube_plugin/youtube/helper/signature/__init__.py b/resources/lib/youtube_plugin/youtube/helper/signature/__init__.py
index a5d8eca2f..a1da71f71 100644
--- a/resources/lib/youtube_plugin/youtube/helper/signature/__init__.py
+++ b/resources/lib/youtube_plugin/youtube/helper/signature/__init__.py
@@ -11,5 +11,3 @@
from ....youtube.helper.signature.cipher import Cipher
__all__ = ['Cipher']
-
-
diff --git a/resources/lib/youtube_plugin/youtube/helper/url_resolver.py b/resources/lib/youtube_plugin/youtube/helper/url_resolver.py
index 2d09a4139..1b4ea4ff0 100644
--- a/resources/lib/youtube_plugin/youtube/helper/url_resolver.py
+++ b/resources/lib/youtube_plugin/youtube/helper/url_resolver.py
@@ -144,7 +144,7 @@ def _loop(_url, tries=5):
_next_query = parse_qs(_nc.query) # query string encoded inside next_url
del _query['next_url'] # remove next_url from top level query string
_next_query.update(_query) # add/overwrite all other params from top level query string
- _next_query = dict(map(lambda kv : (kv[0], kv[1][0]), _next_query.items())) # flatten to only use first argument of each param
+ _next_query = dict(map(lambda kv: (kv[0], kv[1][0]), _next_query.items())) # flatten to only use first argument of each param
_next_url = urlunsplit((_nc.scheme, _nc.netloc, _nc.path, urlencode(_next_query), _nc.fragment)) # build new URL from these components
return _next_url
diff --git a/resources/lib/youtube_plugin/youtube/helper/utils.py b/resources/lib/youtube_plugin/youtube/helper/utils.py
index 734e0a12f..386b4b7a0 100644
--- a/resources/lib/youtube_plugin/youtube/helper/utils.py
+++ b/resources/lib/youtube_plugin/youtube/helper/utils.py
@@ -50,7 +50,7 @@ def make_comment_item(context, provider, snippet, uri, total_replies=0):
label_props = None
plot_props = None
is_edited = (snippet['publishedAt'] != snippet['updatedAt'])
-
+
str_likes = ('%.1fK' % (snippet['likeCount'] / 1000.0)) if snippet['likeCount'] > 1000 else str(snippet['likeCount'])
str_replies = ('%.1fK' % (total_replies / 1000.0)) if total_replies > 1000 else str(total_replies)
diff --git a/resources/lib/youtube_plugin/youtube/helper/v3.py b/resources/lib/youtube_plugin/youtube/helper/v3.py
index b0427088f..403cfc5b4 100644
--- a/resources/lib/youtube_plugin/youtube/helper/v3.py
+++ b/resources/lib/youtube_plugin/youtube/helper/v3.py
@@ -157,7 +157,7 @@ def _process_list_response(provider, context, json_data):
video_item.set_track_number(snippet['position'] + 1)
result.append(video_item)
video_id_dict[video_id] = video_item
-
+
elif kind == 'activity':
snippet = yt_item['snippet']
details = yt_item['contentDetails']
@@ -186,7 +186,7 @@ def _process_list_response(provider, context, json_data):
video_item.set_fanart(provider.get_fanart(context))
result.append(video_item)
video_id_dict[video_id] = video_item
-
+
elif kind == 'commentthread':
thread_snippet = yt_item['snippet']
total_replies = thread_snippet['totalReplyCount']
@@ -197,10 +197,10 @@ def _process_list_response(provider, context, json_data):
else:
item_uri = ''
result.append(utils.make_comment_item(context, provider, snippet, item_uri, total_replies))
-
+
elif kind == 'comment':
result.append(utils.make_comment_item(context, provider, yt_item['snippet'], uri=''))
-
+
elif kind == 'searchresult':
_, kind = _parse_kind(yt_item.get('id', {}))
diff --git a/resources/lib/youtube_plugin/youtube/provider.py b/resources/lib/youtube_plugin/youtube/provider.py
index bdab66582..9fe12991d 100644
--- a/resources/lib/youtube_plugin/youtube/provider.py
+++ b/resources/lib/youtube_plugin/youtube/provider.py
@@ -689,9 +689,9 @@ def _on_my_location(self, context, re_match):
path for playlist: '/play/?playlist_id=XXXXXXX&mode=[OPTION]'
OPTION: [normal(default)|reverse|shuffle]
-
+
path for channel live streams: '/play/?channel_id=UCXXXXXXX&live=X
- OPTION:
+ OPTION:
live parameter required, live=1 for first live stream
live = index of live stream if channel has multiple live streams
"""
@@ -758,7 +758,7 @@ def on_play(self, context, re_match):
context.log_debug('Redirecting playback, handle is -1')
context.execute(builtin % context.create_uri(['play'], {'video_id': params['video_id']}))
return
-
+
if 'playlist_id' in params and (context.get_handle() != -1):
builtin = 'RunPlugin(%s)'
stream_url = context.create_uri(['play'], params)
@@ -1375,12 +1375,12 @@ def on_root(self, context, re_match):
if self.is_logged_in() and settings.get_bool('youtube.folder.my_subscriptions.show', True):
# my subscription
-
+
#clear cache
cache = context.get_data_cache()
cache_items_key = 'my-subscriptions-items'
cache.set(cache_items_key, '[]')
-
+
my_subscriptions_item = DirectoryItem(
context.get_ui().bold(context.localize(self.LOCAL_MAP['youtube.my_subscriptions'])),
context.create_uri(['special', 'new_uploaded_videos_tv']),
diff --git a/resources/lib/youtube_requests.py b/resources/lib/youtube_requests.py
index cf062d196..5f8c3831a 100644
--- a/resources/lib/youtube_requests.py
+++ b/resources/lib/youtube_requests.py
@@ -16,7 +16,7 @@
def __get_core_components(addon_id=None):
"""
:param addon_id: addon id associated with developer keys to use for requests
- :return: addon provider, context and client
+ :return: addon provider, context and client
"""
provider = Provider()
if addon_id is not None:
From cd43d3271001fcb6ab98e585fa131906e338008c Mon Sep 17 00:00:00 2001
From: MoojMidge <56883549+MoojMidge@users.noreply.github.com>
Date: Sun, 8 Oct 2023 14:50:32 +1100
Subject: [PATCH 003/141] Updates to allow backports for Kodi 18
---
.../kodion/impl/xbmc/xbmc_items.py | 4 +--
.../lib/youtube_plugin/kodion/utils/ip_api.py | 4 +--
.../youtube_plugin/youtube/client/youtube.py | 2 +-
.../youtube/helper/ratebypass/ratebypass.py | 6 ++--
.../youtube/helper/video_info.py | 29 ++++++++++---------
5 files changed, 23 insertions(+), 22 deletions(-)
diff --git a/resources/lib/youtube_plugin/kodion/impl/xbmc/xbmc_items.py b/resources/lib/youtube_plugin/kodion/impl/xbmc/xbmc_items.py
index 5f9e601d2..45beda6df 100644
--- a/resources/lib/youtube_plugin/kodion/impl/xbmc/xbmc_items.py
+++ b/resources/lib/youtube_plugin/kodion/impl/xbmc/xbmc_items.py
@@ -13,8 +13,8 @@
try:
from infotagger.listitem import ListItemInfoTag
except ImportError:
- class ListItemInfoTag:
- __slots__ = ('__li__', '__type__' )
+ class ListItemInfoTag(object):
+ __slots__ = ('__li__', '__type__')
def __init__(self, list_item, tag_type):
self.__li__ = list_item
diff --git a/resources/lib/youtube_plugin/kodion/utils/ip_api.py b/resources/lib/youtube_plugin/kodion/utils/ip_api.py
index afb3a7595..dc371483c 100644
--- a/resources/lib/youtube_plugin/kodion/utils/ip_api.py
+++ b/resources/lib/youtube_plugin/kodion/utils/ip_api.py
@@ -10,11 +10,11 @@
import requests
-class Locator:
+class Locator(object):
def __init__(self, context):
self._base_url = 'http://ip-api.com'
- self._response = dict()
+ self._response = {}
self._context = context
def response(self):
diff --git a/resources/lib/youtube_plugin/youtube/client/youtube.py b/resources/lib/youtube_plugin/youtube/client/youtube.py
index 3657cef02..41e7eb3ca 100644
--- a/resources/lib/youtube_plugin/youtube/client/youtube.py
+++ b/resources/lib/youtube_plugin/youtube/client/youtube.py
@@ -1066,7 +1066,7 @@ def _request(self, url, method='GET',
error_msg or 'Request failed', traceback.format_exc()
))
if raise_error:
- raise YouTubeException(error_msg) from error
+ raise YouTubeException(error_msg)
return None
return result
diff --git a/resources/lib/youtube_plugin/youtube/helper/ratebypass/ratebypass.py b/resources/lib/youtube_plugin/youtube/helper/ratebypass/ratebypass.py
index baa8669be..b3632a734 100644
--- a/resources/lib/youtube_plugin/youtube/helper/ratebypass/ratebypass.py
+++ b/resources/lib/youtube_plugin/youtube/helper/ratebypass/ratebypass.py
@@ -14,7 +14,7 @@
try:
from ....kodion import logger
except:
- class logger:
+ class logger(object):
@staticmethod
def log_debug(txt):
print(txt)
@@ -24,7 +24,7 @@ def throttling_reverse(arr):
"""Reverses the input list.
Needs to do an in-place reversal so that the passed list gets changed.
To accomplish this, we create a reversed copy, and then change each
- indvidual element.
+ individual element.
"""
reverse_copy = arr[::-1]
for i in range(len(reverse_copy)):
@@ -221,7 +221,7 @@ def throttling_splice(d, e):
js_splice(d, e, 1)
-class CalculateN:
+class CalculateN(object):
# References:
# https://github.com/ytdl-org/youtube-dl/issues/29326#issuecomment-894619419
# https://github.com/pytube/pytube/blob/fc9aec5c35829f2ebb4ef8dd599b14a666850d20/pytube/cipher.py
diff --git a/resources/lib/youtube_plugin/youtube/helper/video_info.py b/resources/lib/youtube_plugin/youtube/helper/video_info.py
index 8bdbc6e63..e64701c07 100644
--- a/resources/lib/youtube_plugin/youtube/helper/video_info.py
+++ b/resources/lib/youtube_plugin/youtube/helper/video_info.py
@@ -873,28 +873,27 @@ def _generate_cpn():
cpn_alphabet = ('abcdefghijklmnopqrstuvwxyz'
'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
'0123456789-_')
- # Python 2 compatible method
- # cpn = ''.join(cpn_alphabet[random.randint(0, 63)] for _ in range(16))
- # return cpn
- return ''.join(random.choices(cpn_alphabet, k=16))
+ return ''.join(random.choice(cpn_alphabet) for _ in range(16))
def load_stream_infos(self, video_id):
self.video_id = video_id
return self._get_video_info()
def _build_client(self, client_name, auth_header=False):
- def _merge_dicts(item1, item2):
+ def _merge_dicts(item1, item2, _=Ellipsis):
if not isinstance(item1, dict) or not isinstance(item2, dict):
- return item1 if item2 is ... else item2
+ return item1 if item2 is _ else item2
new = {}
- for key in (item1.keys() | item2.keys()):
- value = _merge_dicts(item1.get(key, ...), item2.get(key, ...))
- if value is ...:
+ keys = set(item1)
+ keys.update(item2)
+ for key in keys:
+ value = _merge_dicts(item1.get(key, _), item2.get(key, _))
+ if value is _:
continue
if isinstance(value, str) and '{' in value:
_format['{0}.{1}'.format(id(new), key)] = (new, key, value)
new[key] = value
- return new or ...
+ return new or _
_format = {}
client = (self.CLIENTS.get(client_name) or self.CLIENTS['web']).copy()
@@ -934,7 +933,7 @@ def _request(self, url, method='GET',
error_msg or 'Request failed', traceback.format_exc()
))
if raise_error:
- raise YouTubeException(error_msg) from error
+ raise YouTubeException(error_msg)
return None
return result
@@ -1321,7 +1320,7 @@ def _get_video_info(self):
client = self._build_client(client_name, auth_header)
result = self._request(
- video_info_url, 'POST', **client,
+ video_info_url, 'POST',
error_msg=(
'Player response failed for video_id: {0},'
' using {1} client ({2})'
@@ -1329,7 +1328,8 @@ def _get_video_info(self):
client_name,
'logged in' if auth_header else 'logged out')
),
- raise_error=True
+ raise_error=True,
+ **client
)
response = result.json()
@@ -1422,9 +1422,10 @@ def _get_video_info(self):
captions['headers'] = client['headers']
elif client.get('_query_subtitles'):
result = self._request(
- video_info_url, 'POST', **self._build_client('smarttv', True),
+ video_info_url, 'POST',
error_msg=('Caption request failed to get player response for'
'video_id: {0}'.format(self.video_id)),
+ **self._build_client('smarttv', True)
)
response = result.json()
From 995a4ee068f7b34010ce817e4d30b82bb18c170c Mon Sep 17 00:00:00 2001
From: MoojMidge <56883549+MoojMidge@users.noreply.github.com>
Date: Sun, 15 Oct 2023 01:05:22 +1100
Subject: [PATCH 004/141] Update client details
---
.../youtube/helper/video_info.py | 26 ++++++++++++-------
1 file changed, 16 insertions(+), 10 deletions(-)
diff --git a/resources/lib/youtube_plugin/youtube/helper/video_info.py b/resources/lib/youtube_plugin/youtube/helper/video_info.py
index e64701c07..f0158f909 100644
--- a/resources/lib/youtube_plugin/youtube/helper/video_info.py
+++ b/resources/lib/youtube_plugin/youtube/helper/video_info.py
@@ -576,6 +576,7 @@ class VideoInfo(object):
'_id': 30,
'_query_subtitles': True,
'json': {
+ 'params': '2AMBCgIQBg',
'context': {
'client': {
'clientName': 'ANDROID_TESTSUITE',
@@ -603,7 +604,7 @@ class VideoInfo(object):
'android': {
'_id': 3,
'json': {
- 'params': 'CgIQBg==',
+ 'params': '2AMBCgIQBg',
'context': {
'client': {
'clientName': 'ANDROID',
@@ -633,6 +634,7 @@ class VideoInfo(object):
'android_embedded': {
'_id': 55,
'json': {
+ 'params': '2AMBCgIQBg',
'context': {
'client': {
'clientName': 'ANDROID_EMBEDDED_PLAYER',
@@ -668,6 +670,7 @@ class VideoInfo(object):
'_id': 29,
'_query_subtitles': True,
'json': {
+ 'params': '2AMBCgIQBg',
'context': {
'client': {
'clientName': 'ANDROID_UNPLUGGED',
@@ -722,15 +725,20 @@ class VideoInfo(object):
},
# Used to requests captions for clients that don't provide them
# Requires handling of nsig to overcome throttling (TODO)
- 'smarttv': {
- '_id': 75,
+ 'smarttv_embedded': {
+ '_id': 85,
'json': {
+ 'params': '2AMBCgIQBg',
'context': {
'client': {
- 'clientName': 'TVHTML5_SIMPLY',
- 'clientVersion': '1.0',
+ 'clientName': 'TVHTML5_SIMPLY_EMBEDDED_PLAYER',
+ 'clientScreen': 'WATCH',
+ 'clientVersion': '2.0',
},
},
+ 'thirdParty': {
+ 'embedUrl': 'https://www.youtube.com',
+ },
},
# Headers from a 2022 Samsung Tizen 6.5 based Smart TV
'headers': {
@@ -1234,8 +1242,7 @@ def _process_url_params(self, url):
new_query = {}
update_url = False
- if (self._calculate_n and 'n' in query
- and query.get('ratebypass', [None])[0] != 'yes'):
+ if self._calculate_n and 'n' in query:
self._player_js = self._player_js or self._get_player_js()
if self._calculate_n is True:
self._context.log_debug('nsig detected')
@@ -1397,8 +1404,7 @@ def _get_video_info(self):
self._context.log_debug(
'Retrieved video info for video_id: {0}, using {1} client ({2})'
- .format(self.video_id,
- client['json']['context']['client']['clientName'],
+ .format(self.video_id, client_name,
'logged in' if auth_header else 'logged out')
)
self._selected_client = client.copy()
@@ -1425,7 +1431,7 @@ def _get_video_info(self):
video_info_url, 'POST',
error_msg=('Caption request failed to get player response for'
'video_id: {0}'.format(self.video_id)),
- **self._build_client('smarttv', True)
+ **self._build_client('smarttv_embedded', True)
)
response = result.json()
From 0d8e1abfb5a8f5f45dc0e48cf4b566cb09539d90 Mon Sep 17 00:00:00 2001
From: MoojMidge <56883549+MoojMidge@users.noreply.github.com>
Date: Tue, 17 Oct 2023 01:08:24 +1100
Subject: [PATCH 005/141] Fix stream quality comparison to quality selections
Iterating over dict in insertion order only available in Python 3.7+
---
.../lib/youtube_plugin/kodion/impl/abstract_settings.py | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/resources/lib/youtube_plugin/kodion/impl/abstract_settings.py b/resources/lib/youtube_plugin/kodion/impl/abstract_settings.py
index 47ec7ed2e..f9594e1ca 100644
--- a/resources/lib/youtube_plugin/kodion/impl/abstract_settings.py
+++ b/resources/lib/youtube_plugin/kodion/impl/abstract_settings.py
@@ -242,7 +242,10 @@ def get_mpd_video_qualities(self):
if not self.use_mpd_videos():
return []
selected = self.get_int(SETTINGS.MPD_QUALITY_SELECTION, 4)
- return [quality for key, quality in self._QUALITY_SELECTIONS.items()
+ return [quality
+ for key, quality in sorted(
+ self._QUALITY_SELECTIONS.items(), reverse=True
+ )
if selected >= key]
def stream_features(self):
From 2b82ca1637fbaac7661a066f910b9f17174dd3e0 Mon Sep 17 00:00:00 2001
From: MoojMidge <56883549+MoojMidge@users.noreply.github.com>
Date: Sat, 21 Oct 2023 07:46:07 +1100
Subject: [PATCH 006/141] Fix inputstream.adaptive dependency for
matrix.unofficial
---
.github/workflows/make-release.yml | 1 +
1 file changed, 1 insertion(+)
diff --git a/.github/workflows/make-release.yml b/.github/workflows/make-release.yml
index 4f515947b..13b2dcc29 100644
--- a/.github/workflows/make-release.yml
+++ b/.github/workflows/make-release.yml
@@ -115,6 +115,7 @@ jobs:
version=$(xmlstarlet sel -t -v 'string(/addon/@version)' addon.xml)
xmlstarlet ed -L -u '/addon/@version' -v "${version}+matrix.unofficial.1" addon.xml
xmlstarlet ed -L -u '/addon/requires/import[@addon="xbmc.python"]/@version' -v '3.0.0' addon.xml
+ xmlstarlet ed -L -u '/addon/requires/import[@addon="inputstream.adaptive"]/@version' -v '19.0.7' addon.xml
xmlstarlet ed -L -d '/addon/requires/import[@addon="script.module.infotagger"]' addon.xml
filename=${{ github.event.repository.name }}-${version}.matrix.unofficial.1.zip
cd ..
From a565b44513059c88f63dc7994b1dfbde94b9989a Mon Sep 17 00:00:00 2001
From: MoojMidge <56883549+MoojMidge@users.noreply.github.com>
Date: Sat, 21 Oct 2023 07:52:41 +1100
Subject: [PATCH 007/141] Test workaround for some MPD live stream manifests
---
resources/lib/youtube_plugin/youtube/helper/video_info.py | 8 ++++++--
1 file changed, 6 insertions(+), 2 deletions(-)
diff --git a/resources/lib/youtube_plugin/youtube/helper/video_info.py b/resources/lib/youtube_plugin/youtube/helper/video_info.py
index f0158f909..e3ac3d70c 100644
--- a/resources/lib/youtube_plugin/youtube/helper/video_info.py
+++ b/resources/lib/youtube_plugin/youtube/helper/video_info.py
@@ -1577,8 +1577,12 @@ def _get_video_info(self):
# MPD structure has segments with additional attributes
# and url has changed from using a query string to using url params
# This breaks the InputStream.Adaptive partial manifest update
- video_stream['url'] = ('{0}?start_seq=$START_NUMBER$'
- .format(video_stream['url']))
+ if '?' in manifest_url:
+ video_stream['url'] = manifest_url + '&mpd_version=5'
+ elif manifest_url.endswith('/'):
+ video_stream['url'] = manifest_url + 'mpd_version/5'
+ else:
+ video_stream['url'] = manifest_url + '/mpd_version/5'
details = self.FORMAT.get('9998')
else:
details = self.FORMAT.get('9999').copy()
From e5b69b430438d23bc2ff0cc24632b92f27598011 Mon Sep 17 00:00:00 2001
From: MoojMidge <56883549+MoojMidge@users.noreply.github.com>
Date: Fri, 27 Oct 2023 02:58:11 +1100
Subject: [PATCH 008/141] Debug test
---
resources/lib/default.py | 8 +-
resources/lib/youtube_plugin/kodion/debug.py | 169 +++++++++++++++++-
resources/lib/youtube_plugin/kodion/logger.py | 2 +-
3 files changed, 172 insertions(+), 7 deletions(-)
diff --git a/resources/lib/default.py b/resources/lib/default.py
index 10385f513..bbc54e2c9 100644
--- a/resources/lib/default.py
+++ b/resources/lib/default.py
@@ -8,8 +8,14 @@
See LICENSES/GPL-2.0-only for more information.
"""
-from youtube_plugin.kodion import runner
+from xbmc import log
+
from youtube_plugin import youtube
+from youtube_plugin.kodion import runner
+from youtube_plugin.kodion.debug import Profiler
+
+profiler = Profiler(enabled=True, lazy=False)
__provider__ = youtube.Provider()
runner.run(__provider__)
+log(profiler.get_stats(), 1)
diff --git a/resources/lib/youtube_plugin/kodion/debug.py b/resources/lib/youtube_plugin/kodion/debug.py
index 1fff38661..cf12d73b4 100644
--- a/resources/lib/youtube_plugin/kodion/debug.py
+++ b/resources/lib/youtube_plugin/kodion/debug.py
@@ -2,7 +2,7 @@
"""
Copyright (C) 2014-2016 bromix (plugin.video.youtube)
- Copyright (C) 2016-2018 plugin.video.youtube
+ Copyright (C) 2016-present plugin.video.youtube
SPDX-License-Identifier: GPL-2.0-only
See LICENSES/GPL-2.0-only for more information.
@@ -11,6 +11,8 @@
import os
import json
+from .logger import log_debug
+
def debug_here(host='localhost'):
import sys
@@ -43,10 +45,7 @@ def runtime(context, addon_version, elapsed, single_file=True):
contents = f.read()
with open(debug_file, 'w') as f:
- if not contents:
- contents = default_contents
- else:
- contents = json.loads(contents)
+ contents = json.loads(contents) if contents else default_contents
if not single_file:
items = contents.get('runtimes', [])
items.append({"path": context.get_path(), "parameters": context.get_params(), "runtime": round(elapsed, 4)})
@@ -56,3 +55,163 @@ def runtime(context, addon_version, elapsed, single_file=True):
items.append({"parameters": context.get_params(), "runtime": round(elapsed, 4)})
contents['runtimes'][context.get_path()] = items
f.write(json.dumps(contents, indent=4))
+
+
+class Profiler(object):
+ """Class used to profile a block of code"""
+
+ __slots__ = ('__weakref__', '_enabled', '_profiler', '_reuse', 'name', )
+
+ from cProfile import Profile as _Profile
+ from pstats import Stats as _Stats
+ try:
+ from StringIO import StringIO as _StringIO
+ except ImportError:
+ from io import StringIO as _StringIO
+ from functools import wraps as _wraps
+ _wraps = staticmethod(_wraps)
+ from weakref import ref as _ref
+
+ class Proxy(_ref):
+ def __call__(self, *args, **kwargs):
+ return super(Profiler.Proxy, self).__call__().__call__(
+ *args, **kwargs
+ )
+
+ def __enter__(self, *args, **kwargs):
+ return super(Profiler.Proxy, self).__call__().__enter__(
+ *args, **kwargs
+ )
+
+ def __exit__(self, *args, **kwargs):
+ return super(Profiler.Proxy, self).__call__().__exit__(
+ *args, **kwargs
+ )
+
+ _instances = set()
+
+ def __new__(cls, *args, **kwargs):
+ self = super(Profiler, cls).__new__(cls)
+ cls._instances.add(self)
+ if not kwargs.get('enabled') or kwargs.get('lazy'):
+ self.__init__(*args, **kwargs)
+ return cls.Proxy(self)
+ return self
+
+ def __init__(self, enabled=True, lazy=True, name=__name__, reuse=False):
+ self._enabled = enabled
+ self._profiler = None
+ self._reuse = reuse
+ self.name = name
+
+ if enabled and not lazy:
+ self._create_profiler()
+
+ def __del__(self):
+ self.__class__._instances.discard(self) # pylint: disable=protected-access
+
+ def __enter__(self):
+ if not self._enabled:
+ return
+
+ if not self._profiler:
+ self._create_profiler()
+
+ def __exit__(self, exc_type=None, exc_value=None, traceback=None):
+ if not self._enabled:
+ return
+
+ log_debug('Profiling stats: {0}'.format(self.get_stats(
+ reuse=self._reuse
+ )))
+ if not self._reuse:
+ self.__del__()
+
+ def __call__(self, func=None, name=__name__, reuse=False):
+ """Decorator used to profile function calls"""
+
+ if not func:
+ self._reuse = reuse
+ self.name = name
+ return self
+
+ @self.__class__._wraps(func) # pylint: disable=protected-access
+ def wrapper(*args, **kwargs):
+ """Wrapper to:
+ 1) create a new Profiler instance;
+ 2) run the function being profiled;
+ 3) print out profiler result to the log; and
+ 4) return result of function call"""
+
+ name = getattr(func, '__qualname__', None)
+ if name:
+ # If __qualname__ is available (Python 3.3+) then use it
+ pass
+
+ elif args and getattr(args[0], func.__name__, None):
+ if isinstance(args[0], type):
+ class_name = args[0].__name__
+ else:
+ class_name = args[0].__class__.__name__
+ name = '{0}.{1}'.format(class_name, func.__name__)
+
+ elif (func.__class__
+ and not isinstance(func.__class__, type)
+ and func.__class__.__name__ != 'function'):
+ name = '{0}.{1}'.format(func.__class__.__name__, func.__name__)
+
+ elif func.__module__:
+ name = '{0}.{1}'.format(func.__module__, func.__name__)
+
+ else:
+ name = func.__name__
+
+ self.name = name
+ with self:
+ result = func(*args, **kwargs)
+
+ return result
+
+ if not self._enabled:
+ self.__del__()
+ return func
+ return wrapper
+
+ def _create_profiler(self):
+ self._profiler = self._Profile()
+ self._profiler.enable()
+
+ def disable(self):
+ if self._profiler:
+ self._profiler.disable()
+
+ def enable(self, flush=False):
+ self._enabled = True
+ if flush or not self._profiler:
+ self._create_profiler()
+ else:
+ self._profiler.enable()
+
+ def get_stats(self, flush=True, reuse=False):
+ if not (self._enabled and self._profiler):
+ return None
+
+ self.disable()
+
+ output_stream = self._StringIO()
+ try:
+ self._Stats(
+ self._profiler,
+ stream=output_stream
+ ).strip_dirs().sort_stats('cumulative', 'time').print_stats(20)
+ # Occurs when no stats were able to be generated from profiler
+ except TypeError:
+ pass
+ output = output_stream.getvalue()
+ output_stream.close()
+
+ if reuse:
+ # If stats are accumulating then enable existing/new profiler
+ self.enable(flush)
+
+ return output
diff --git a/resources/lib/youtube_plugin/kodion/logger.py b/resources/lib/youtube_plugin/kodion/logger.py
index df709b3bb..c97587d21 100644
--- a/resources/lib/youtube_plugin/kodion/logger.py
+++ b/resources/lib/youtube_plugin/kodion/logger.py
@@ -11,7 +11,7 @@
import xbmc
import xbmcaddon
-DEBUG = xbmc.LOGDEBUG
+DEBUG = xbmc.LOGINFO
INFO = xbmc.LOGINFO
NOTICE = INFO
WARNING = xbmc.LOGWARNING
From 8a1d739e9e489536cce9a5c6782ef2fa0c056b39 Mon Sep 17 00:00:00 2001
From: MoojMidge <56883549+MoojMidge@users.noreply.github.com>
Date: Mon, 30 Oct 2023 22:13:08 +1100
Subject: [PATCH 009/141] Decrease inputstream.adaptive dependency version
---
.github/workflows/make-release.yml | 4 ++--
addon.xml | 2 +-
resources/lib/youtube_plugin/kodion/impl/xbmc/xbmc_context.py | 2 +-
3 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/.github/workflows/make-release.yml b/.github/workflows/make-release.yml
index 13b2dcc29..76e1c82ef 100644
--- a/.github/workflows/make-release.yml
+++ b/.github/workflows/make-release.yml
@@ -93,7 +93,7 @@ jobs:
version=$(xmlstarlet sel -t -v 'string(/addon/@version)' addon.xml)
xmlstarlet ed -L -u '/addon/@version' -v "${version}+matrix.1" addon.xml
xmlstarlet ed -L -u '/addon/requires/import[@addon="xbmc.python"]/@version' -v '3.0.0' addon.xml
- xmlstarlet ed -L -u '/addon/requires/import[@addon="inputstream.adaptive"]/@version' -v '19.0.7' addon.xml
+ xmlstarlet ed -L -u '/addon/requires/import[@addon="inputstream.adaptive"]/@version' -v '19.0.0' addon.xml
xmlstarlet ed -L -d '/addon/requires/import[@addon="script.module.infotagger"]' addon.xml
filename=${{ github.event.repository.name }}-${version}.matrix.1.zip
cd ..
@@ -115,7 +115,7 @@ jobs:
version=$(xmlstarlet sel -t -v 'string(/addon/@version)' addon.xml)
xmlstarlet ed -L -u '/addon/@version' -v "${version}+matrix.unofficial.1" addon.xml
xmlstarlet ed -L -u '/addon/requires/import[@addon="xbmc.python"]/@version' -v '3.0.0' addon.xml
- xmlstarlet ed -L -u '/addon/requires/import[@addon="inputstream.adaptive"]/@version' -v '19.0.7' addon.xml
+ xmlstarlet ed -L -u '/addon/requires/import[@addon="inputstream.adaptive"]/@version' -v '19.0.0' addon.xml
xmlstarlet ed -L -d '/addon/requires/import[@addon="script.module.infotagger"]' addon.xml
filename=${{ github.event.repository.name }}-${version}.matrix.unofficial.1.zip
cd ..
diff --git a/addon.xml b/addon.xml
index b2b9fd031..f2b401b55 100644
--- a/addon.xml
+++ b/addon.xml
@@ -3,7 +3,7 @@
-
+
diff --git a/resources/lib/youtube_plugin/kodion/impl/xbmc/xbmc_context.py b/resources/lib/youtube_plugin/kodion/impl/xbmc/xbmc_context.py
index 03b9cee76..9a3eb8e3d 100644
--- a/resources/lib/youtube_plugin/kodion/impl/xbmc/xbmc_context.py
+++ b/resources/lib/youtube_plugin/kodion/impl/xbmc/xbmc_context.py
@@ -307,7 +307,7 @@ def use_inputstream_adaptive(self):
'drm': '2.2.12',
# audio codecs
'vorbis': '2.3.14',
- 'opus': '19.0.7',
+ 'opus': '19.0.0', # unknown when Opus audio support was first implemented
'mp4a': True,
'ac-3': '2.1.15',
'ec-3': '2.1.15',
From a77473bb3bf61b91bf2ba225714fc236a408e7ad Mon Sep 17 00:00:00 2001
From: MoojMidge <56883549+MoojMidge@users.noreply.github.com>
Date: Mon, 30 Oct 2023 23:07:11 +1100
Subject: [PATCH 010/141] Update sort methods
- Remove use of eval
- Remove incrementing dummy values for missing sort methods
- Not sure why it was done this way originally (for old unit tests?)
but the incrementing dummy values can be added back if needed
---
.../kodion/constants/const_sort_methods.py | 120 ++++++++++--------
.../lib/youtube_plugin/youtube/provider.py | 4 +-
2 files changed, 67 insertions(+), 57 deletions(-)
diff --git a/resources/lib/youtube_plugin/kodion/constants/const_sort_methods.py b/resources/lib/youtube_plugin/kodion/constants/const_sort_methods.py
index e33facda7..2838ed93b 100644
--- a/resources/lib/youtube_plugin/kodion/constants/const_sort_methods.py
+++ b/resources/lib/youtube_plugin/kodion/constants/const_sort_methods.py
@@ -8,62 +8,72 @@
See LICENSES/GPL-2.0-only for more information.
"""
-_xbmc = True
+import sys
+from xbmcplugin import __dict__ as xbmcplugin
-try:
- from xbmcplugin import *
-except:
- _xbmc = False
- _count = 0
+namespace = sys.modules[__name__]
+names = [
+ # 'NONE',
+ 'LABEL',
+ 'LABEL_IGNORE_THE',
+ 'DATE',
+ 'SIZE',
+ 'FILE',
+ 'DRIVE_TYPE',
+ 'TRACKNUM',
+ 'DURATION',
+ 'TITLE',
+ 'TITLE_IGNORE_THE',
+ 'ARTIST',
+ # 'ARTIST_AND_YEAR',
+ 'ARTIST_IGNORE_THE',
+ 'ALBUM',
+ 'ALBUM_IGNORE_THE',
+ 'GENRE',
+ 'COUNTRY',
+ # 'YEAR',
+ 'VIDEO_YEAR',
+ 'VIDEO_RATING',
+ 'VIDEO_USER_RATING',
+ 'DATEADDED',
+ 'PROGRAM_COUNT',
+ 'PLAYLIST_ORDER',
+ 'EPISODE',
+ 'VIDEO_TITLE',
+ 'VIDEO_SORT_TITLE',
+ 'VIDEO_SORT_TITLE_IGNORE_THE',
+ 'PRODUCTIONCODE',
+ 'SONG_RATING',
+ 'SONG_USER_RATING',
+ 'MPAA_RATING',
+ 'VIDEO_RUNTIME',
+ 'STUDIO',
+ 'STUDIO_IGNORE_THE',
+ 'FULLPATH',
+ 'LABEL_IGNORE_FOLDERS',
+ 'LASTPLAYED',
+ 'PLAYCOUNT',
+ 'LISTENERS',
+ 'UNSORTED',
+ 'CHANNEL',
+ 'CHANNEL_NUMBER',
+ 'BITRATE',
+ 'DATE_TAKEN',
+ 'CLIENT_CHANNEL_ORDER',
+ 'TOTAL_DISCS',
+ 'ORIG_DATE',
+ 'BPM',
+ 'VIDEO_ORIGINAL_TITLE',
+ 'VIDEO_ORIGINAL_TITLE_IGNORE_THE',
+ 'PROVIDER',
+ 'USER_PREFERENCE',
+ # 'MAX',
+]
-def _const(name):
- if _xbmc:
- return eval(name)
- else:
- global _count
- _count += 1
- return _count
+for name in names:
+ fullname = 'SORT_METHOD_' + name
+ setattr(namespace, name,
+ xbmcplugin[fullname] if fullname in xbmcplugin else -1)
-
-ALBUM = _const('SORT_METHOD_ALBUM')
-ALBUM_IGNORE_THE = _const('SORT_METHOD_ALBUM_IGNORE_THE')
-ARTIST = _const('SORT_METHOD_ARTIST')
-ARTIST_IGNORE_THE = _const('SORT_METHOD_ARTIST_IGNORE_THE')
-BIT_RATE = _const('SORT_METHOD_BITRATE')
-# CHANNEL = _const('SORT_METHOD_CHANNEL')
-# COUNTRY = _const('SORT_METHOD_COUNTRY')
-DATE = _const('SORT_METHOD_DATE')
-DATE_ADDED = _const('SORT_METHOD_DATEADDED')
-# DATE_TAKEN = _const('SORT_METHOD_DATE_TAKEN')
-DRIVE_TYPE = _const('SORT_METHOD_DRIVE_TYPE')
-DURATION = _const('SORT_METHOD_DURATION')
-EPISODE = _const('SORT_METHOD_EPISODE')
-FILE = _const('SORT_METHOD_FILE')
-# FULL_PATH = _const('SORT_METHOD_FULLPATH')
-GENRE = _const('SORT_METHOD_GENRE')
-LABEL = _const('SORT_METHOD_LABEL')
-# LABEL_IGNORE_FOLDERS = _const('SORT_METHOD_LABEL_IGNORE_FOLDERS')
-LABEL_IGNORE_THE = _const('SORT_METHOD_LABEL_IGNORE_THE')
-# LAST_PLAYED = _const('SORT_METHOD_LASTPLAYED')
-LISTENERS = _const('SORT_METHOD_LISTENERS')
-MPAA_RATING = _const('SORT_METHOD_MPAA_RATING')
-NONE = _const('SORT_METHOD_NONE')
-# PLAY_COUNT = _const('SORT_METHOD_PLAYCOUNT')
-PLAYLIST_ORDER = _const('SORT_METHOD_PLAYLIST_ORDER')
-PRODUCTION_CODE = _const('SORT_METHOD_PRODUCTIONCODE')
-PROGRAM_COUNT = _const('SORT_METHOD_PROGRAM_COUNT')
-SIZE = _const('SORT_METHOD_SIZE')
-SONG_RATING = _const('SORT_METHOD_SONG_RATING')
-STUDIO = _const('SORT_METHOD_STUDIO')
-STUDIO_IGNORE_THE = _const('SORT_METHOD_STUDIO_IGNORE_THE')
-TITLE = _const('SORT_METHOD_TITLE')
-TITLE_IGNORE_THE = _const('SORT_METHOD_TITLE_IGNORE_THE')
-TRACK_NUMBER = _const('SORT_METHOD_TRACKNUM')
-UNSORTED = _const('SORT_METHOD_UNSORTED')
-VIDEO_RATING = _const('SORT_METHOD_VIDEO_RATING')
-VIDEO_RUNTIME = _const('SORT_METHOD_VIDEO_RUNTIME')
-VIDEO_SORT_TITLE = _const('SORT_METHOD_VIDEO_SORT_TITLE')
-VIDEO_SORT_TITLE_IGNORE_THE = _const('SORT_METHOD_VIDEO_SORT_TITLE_IGNORE_THE')
-VIDEO_TITLE = _const('SORT_METHOD_VIDEO_TITLE')
-VIDEO_YEAR = _const('SORT_METHOD_VIDEO_YEAR')
+del sys, xbmcplugin, namespace, names, name, fullname
diff --git a/resources/lib/youtube_plugin/youtube/provider.py b/resources/lib/youtube_plugin/youtube/provider.py
index 9fe12991d..ab284c4e3 100644
--- a/resources/lib/youtube_plugin/youtube/provider.py
+++ b/resources/lib/youtube_plugin/youtube/provider.py
@@ -1598,8 +1598,8 @@ def set_content_type(context, content_type):
if content_type == kodion.constants.content_type.VIDEOS:
context.add_sort_method(kodion.constants.sort_method.UNSORTED,
kodion.constants.sort_method.VIDEO_RUNTIME,
- kodion.constants.sort_method.DATE_ADDED,
- kodion.constants.sort_method.TRACK_NUMBER,
+ kodion.constants.sort_method.DATEADDED,
+ kodion.constants.sort_method.TRACKNUM,
kodion.constants.sort_method.VIDEO_TITLE,
kodion.constants.sort_method.DATE)
From 533fc96c54429dab376284b0d43828ca461d56d2 Mon Sep 17 00:00:00 2001
From: MoojMidge <56883549+MoojMidge@users.noreply.github.com>
Date: Tue, 31 Oct 2023 09:57:28 +1100
Subject: [PATCH 011/141] Misc tidy up
No (intended) functional changes:
- extraneous indentation/whitespace
- missing explicit return values
- unnecessary return values
- combine nested if statements
- else/elif after return
- change else if to elif
- change if X: to if not X: return
- use conditional expressions
- simplify redundant conditions in if statements
- change bool if condition else not bool to just condition
- remove u-strings
- remove unused variables
- reduce line lengths
---
.../kodion/abstract_provider.py | 66 +--
.../kodion/impl/abstract_context.py | 6 +-
.../kodion/impl/abstract_context_ui.py | 3 +-
.../kodion/impl/abstract_settings.py | 3 +-
.../kodion/impl/xbmc/xbmc_context.py | 2 +-
.../kodion/impl/xbmc/xbmc_context_ui.py | 5 +-
.../kodion/impl/xbmc/xbmc_items.py | 6 +-
.../kodion/impl/xbmc/xbmc_playlist.py | 16 +-
.../youtube_plugin/kodion/items/audio_item.py | 2 +-
.../youtube_plugin/kodion/items/base_item.py | 9 +-
.../kodion/items/directory_item.py | 2 +-
.../youtube_plugin/kodion/items/image_item.py | 2 +-
.../youtube_plugin/kodion/items/uri_item.py | 2 +-
.../lib/youtube_plugin/kodion/items/utils.py | 8 +-
.../youtube_plugin/kodion/items/video_item.py | 18 +-
.../kodion/json_store/json_store.py | 1 +
.../kodion/utils/access_manager.py | 3 +-
.../kodion/utils/http_server.py | 475 +++++++++---------
.../lib/youtube_plugin/kodion/utils/ip_api.py | 5 +-
.../youtube_plugin/kodion/utils/methods.py | 6 +-
.../youtube_plugin/kodion/utils/monitor.py | 33 +-
.../lib/youtube_plugin/kodion/utils/player.py | 148 +++---
.../youtube_plugin/kodion/utils/storage.py | 12 +-
.../kodion/utils/system_version.py | 24 +-
.../youtube/client/__config__.py | 68 ++-
.../youtube/client/login_client.py | 20 +-
.../youtube/helper/resource_manager.py | 2 +
.../youtube/helper/signature/cipher.py | 2 +-
.../youtube/helper/subtitles.py | 3 +-
.../youtube_plugin/youtube/helper/utils.py | 53 +-
.../youtube/helper/video_info.py | 5 +-
.../youtube/helper/yt_context_menu.py | 7 +-
.../youtube_plugin/youtube/helper/yt_login.py | 19 +-
.../youtube_plugin/youtube/helper/yt_play.py | 12 +-
.../youtube/helper/yt_playlist.py | 39 +-
.../youtube/helper/yt_setup_wizard.py | 88 ++--
.../youtube/helper/yt_specials.py | 29 +-
.../youtube/helper/yt_subscriptions.py | 6 +-
.../youtube_plugin/youtube/helper/yt_video.py | 14 +-
.../lib/youtube_plugin/youtube/provider.py | 154 +++---
resources/lib/youtube_requests.py | 63 ++-
41 files changed, 721 insertions(+), 720 deletions(-)
diff --git a/resources/lib/youtube_plugin/kodion/abstract_provider.py b/resources/lib/youtube_plugin/kodion/abstract_provider.py
index ff8ffd444..925d91ae3 100644
--- a/resources/lib/youtube_plugin/kodion/abstract_provider.py
+++ b/resources/lib/youtube_plugin/kodion/abstract_provider.py
@@ -117,7 +117,7 @@ def on_extra_fanart(context, re_match):
:param re_match:
:return:
"""
- return None
+ return
def _internal_on_extra_fanart(self, context, re_match):
path = re_match.group('path')
@@ -146,12 +146,13 @@ def _internal_favorite(context, re_match):
if command == 'add':
fav_item = items.from_json(params['item'])
context.get_favorite_list().add(fav_item)
- elif command == 'remove':
+ return None
+ if command == 'remove':
fav_item = items.from_json(params['item'])
context.get_favorite_list().remove(fav_item)
context.get_ui().refresh_container()
- elif command == 'list':
-
+ return None
+ if command == 'list':
directory_items = context.get_favorite_list().list()
for directory_item in directory_items:
@@ -161,8 +162,7 @@ def _internal_favorite(context, re_match):
directory_item.set_context_menu(context_menu)
return directory_items
- else:
- pass
+ return None
def _internal_watch_later(self, context, re_match):
self.on_watch_later(context, re_match)
@@ -173,11 +173,13 @@ def _internal_watch_later(self, context, re_match):
if command == 'add':
item = items.from_json(params['item'])
context.get_watch_later_list().add(item)
- elif command == 'remove':
+ return None
+ if command == 'remove':
item = items.from_json(params['item'])
context.get_watch_later_list().remove(item)
context.get_ui().refresh_container()
- elif command == 'list':
+ return None
+ if command == 'list':
video_items = context.get_watch_later_list().list()
for video_item in video_items:
@@ -187,9 +189,7 @@ def _internal_watch_later(self, context, re_match):
video_item.set_context_menu(context_menu)
return video_items
- else:
- # do something
- pass
+ return None
@property
def data_cache(self):
@@ -210,7 +210,7 @@ def _internal_search(self, context, re_match):
search_history.remove(query)
context.get_ui().refresh_container()
return True
- elif command == 'rename':
+ if command == 'rename':
query = params['q']
result, new_query = context.get_ui().on_keyboard_input(context.localize(constants.localize.SEARCH_RENAME),
query)
@@ -218,11 +218,11 @@ def _internal_search(self, context, re_match):
search_history.rename(query, new_query)
context.get_ui().refresh_container()
return True
- elif command == 'clear':
+ if command == 'clear':
search_history.clear()
context.get_ui().refresh_container()
return True
- elif command == 'input':
+ if command == 'input':
self.data_cache = context
folder_path = context.get_ui().get_info_label('Container.FolderPath')
@@ -263,7 +263,7 @@ def _internal_search(self, context, re_match):
query = query.decode('utf-8')
return self.on_search(query, context, re_match)
- elif command == 'query':
+ if command == 'query':
incognito = str(context.get_param('incognito', False)).lower() == 'true'
channel_id = context.get_param('channel_id', '')
query = params['q']
@@ -277,30 +277,30 @@ def _internal_search(self, context, re_match):
if isinstance(query, bytes):
query = query.decode('utf-8')
return self.on_search(query, context, re_match)
- else:
- context.set_content_type(constants.content_type.FILES)
- result = []
- location = str(context.get_param('location', False)).lower() == 'true'
+ context.set_content_type(constants.content_type.FILES)
+ result = []
- # 'New Search...'
- new_search_item = items.NewSearchItem(context, fanart=self.get_alternative_fanart(context), location=location)
- result.append(new_search_item)
+ location = str(context.get_param('location', False)).lower() == 'true'
- for search in search_history.list():
- # little fallback for old history entries
- if isinstance(search, items.DirectoryItem):
- search = search.get_name()
+ # 'New Search...'
+ new_search_item = items.NewSearchItem(context, fanart=self.get_alternative_fanart(context), location=location)
+ result.append(new_search_item)
- # we create a new instance of the SearchItem
- search_history_item = items.SearchHistoryItem(context, search, fanart=self.get_alternative_fanart(context), location=location)
- result.append(search_history_item)
+ for search in search_history.list():
+ # little fallback for old history entries
+ if isinstance(search, items.DirectoryItem):
+ search = search.get_name()
- if search_history.is_empty():
- # context.execute('RunPlugin(%s)' % context.create_uri([constants.paths.SEARCH, 'input']))
- pass
+ # we create a new instance of the SearchItem
+ search_history_item = items.SearchHistoryItem(context, search, fanart=self.get_alternative_fanart(context), location=location)
+ result.append(search_history_item)
+
+ if search_history.is_empty():
+ # context.execute('RunPlugin(%s)' % context.create_uri([constants.paths.SEARCH, 'input']))
+ pass
- return result, {self.RESULT_CACHE_TO_DISC: False}
+ return result, {self.RESULT_CACHE_TO_DISC: False}
def handle_exception(self, context, exception_to_handle):
return True
diff --git a/resources/lib/youtube_plugin/kodion/impl/abstract_context.py b/resources/lib/youtube_plugin/kodion/impl/abstract_context.py
index 6cc2541cf..078314c5c 100644
--- a/resources/lib/youtube_plugin/kodion/impl/abstract_context.py
+++ b/resources/lib/youtube_plugin/kodion/impl/abstract_context.py
@@ -17,7 +17,7 @@
class AbstractContext(object):
- def __init__(self, path=u'/', params=None, plugin_name=u'', plugin_id=u''):
+ def __init__(self, path='/', params=None, plugin_name='', plugin_id=''):
if not params:
params = {}
@@ -137,7 +137,7 @@ def get_system_version(self):
return self._system_version
- def create_uri(self, path=u'/', params=None):
+ def create_uri(self, path='/', params=None):
if not params:
params = {}
@@ -218,7 +218,7 @@ def get_handle(self):
def get_settings(self):
raise NotImplementedError()
- def localize(self, text_id, default_text=u''):
+ def localize(self, text_id, default_text=''):
raise NotImplementedError()
def set_content_type(self, content_type):
diff --git a/resources/lib/youtube_plugin/kodion/impl/abstract_context_ui.py b/resources/lib/youtube_plugin/kodion/impl/abstract_context_ui.py
index eb7419174..efca1c1b2 100644
--- a/resources/lib/youtube_plugin/kodion/impl/abstract_context_ui.py
+++ b/resources/lib/youtube_plugin/kodion/impl/abstract_context_ui.py
@@ -40,7 +40,8 @@ def on_select(self, title, items=None):
def open_settings(self):
raise NotImplementedError()
- def show_notification(self, message, header='', image_uri='', time_milliseconds=5000, audible=True):
+ def show_notification(self, message, header='', image_uri='',
+ time_milliseconds=5000, audible=True):
raise NotImplementedError()
@staticmethod
diff --git a/resources/lib/youtube_plugin/kodion/impl/abstract_settings.py b/resources/lib/youtube_plugin/kodion/impl/abstract_settings.py
index f9594e1ca..14868fab6 100644
--- a/resources/lib/youtube_plugin/kodion/impl/abstract_settings.py
+++ b/resources/lib/youtube_plugin/kodion/impl/abstract_settings.py
@@ -205,8 +205,7 @@ def get_location(self):
latitude = longitude = None
if latitude and longitude:
return '{lat},{long}'.format(lat=latitude, long=longitude)
- else:
- return ''
+ return ''
def set_location(self, value):
self.set_string(SETTINGS.LOCATION, value)
diff --git a/resources/lib/youtube_plugin/kodion/impl/xbmc/xbmc_context.py b/resources/lib/youtube_plugin/kodion/impl/xbmc/xbmc_context.py
index 9a3eb8e3d..f36f60e86 100644
--- a/resources/lib/youtube_plugin/kodion/impl/xbmc/xbmc_context.py
+++ b/resources/lib/youtube_plugin/kodion/impl/xbmc/xbmc_context.py
@@ -186,7 +186,7 @@ def get_native_path(self):
def get_settings(self):
return self._settings
- def localize(self, text_id, default_text=u''):
+ def localize(self, text_id, default_text=''):
result = None
if isinstance(text_id, int):
"""
diff --git a/resources/lib/youtube_plugin/kodion/impl/xbmc/xbmc_context_ui.py b/resources/lib/youtube_plugin/kodion/impl/xbmc/xbmc_context_ui.py
index 147d4e5eb..4f1e50aee 100644
--- a/resources/lib/youtube_plugin/kodion/impl/xbmc/xbmc_context_ui.py
+++ b/resources/lib/youtube_plugin/kodion/impl/xbmc/xbmc_context_ui.py
@@ -44,8 +44,7 @@ def on_keyboard_input(self, title, default='', hidden=False):
if keyboard.isConfirmed() and keyboard.getText():
text = utils.to_unicode(keyboard.getText())
return True, text
- else:
- return False, u''
+ return False, ''
# Starting with Gotham (13.X > ...)
dialog = xbmcgui.Dialog()
@@ -54,7 +53,7 @@ def on_keyboard_input(self, title, default='', hidden=False):
text = utils.to_unicode(result)
return True, text
- return False, u''
+ return False, ''
def on_numeric_input(self, title, default=''):
dialog = xbmcgui.Dialog()
diff --git a/resources/lib/youtube_plugin/kodion/impl/xbmc/xbmc_items.py b/resources/lib/youtube_plugin/kodion/impl/xbmc/xbmc_items.py
index 45beda6df..9c43c5b49 100644
--- a/resources/lib/youtube_plugin/kodion/impl/xbmc/xbmc_items.py
+++ b/resources/lib/youtube_plugin/kodion/impl/xbmc/xbmc_items.py
@@ -37,7 +37,7 @@ def to_play_item(context, play_item):
is_strm = str(context.get_param('strm', False)).lower() == 'true'
- thumb = play_item.get_image() if play_item.get_image() else u'DefaultVideo.png'
+ thumb = play_item.get_image() if play_item.get_image() else 'DefaultVideo.png'
title = play_item.get_title() if play_item.get_title() else play_item.get_name()
fanart = ''
settings = context.get_settings()
@@ -138,7 +138,7 @@ def to_play_item(context, play_item):
def to_video_item(context, video_item):
context.log_debug('Converting VideoItem |%s|' % video_item.get_uri())
- thumb = video_item.get_image() if video_item.get_image() else u'DefaultVideo.png'
+ thumb = video_item.get_image() if video_item.get_image() else 'DefaultVideo.png'
title = video_item.get_title() if video_item.get_title() else video_item.get_name()
fanart = ''
settings = context.get_settings()
@@ -201,7 +201,7 @@ def to_video_item(context, video_item):
def to_audio_item(context, audio_item):
context.log_debug('Converting AudioItem |%s|' % audio_item.get_uri())
- thumb = audio_item.get_image() if audio_item.get_image() else u'DefaultAudio.png'
+ thumb = audio_item.get_image() if audio_item.get_image() else 'DefaultAudio.png'
title = audio_item.get_name()
fanart = ''
settings = context.get_settings()
diff --git a/resources/lib/youtube_plugin/kodion/impl/xbmc/xbmc_playlist.py b/resources/lib/youtube_plugin/kodion/impl/xbmc/xbmc_playlist.py
index e95bfb436..b21130c86 100644
--- a/resources/lib/youtube_plugin/kodion/impl/xbmc/xbmc_playlist.py
+++ b/resources/lib/youtube_plugin/kodion/impl/xbmc/xbmc_playlist.py
@@ -61,12 +61,12 @@ def get_items(self):
if 'items' in response['result']:
return response['result']['items']
return []
+
+ if 'error' in response:
+ message = response['error']['message']
+ code = response['error']['code']
+ error = 'Requested |%s| received error |%s| and code: |%s|' % (rpc_request, message, code)
else:
- if 'error' in response:
- message = response['error']['message']
- code = response['error']['code']
- error = 'Requested |%s| received error |%s| and code: |%s|' % (rpc_request, message, code)
- else:
- error = 'Requested |%s| received error |%s|' % (rpc_request, str(response))
- self._context.log_debug(error)
- return []
+ error = 'Requested |%s| received error |%s|' % (rpc_request, str(response))
+ self._context.log_debug(error)
+ return []
diff --git a/resources/lib/youtube_plugin/kodion/items/audio_item.py b/resources/lib/youtube_plugin/kodion/items/audio_item.py
index 33e5c530d..0d4b012ff 100644
--- a/resources/lib/youtube_plugin/kodion/items/audio_item.py
+++ b/resources/lib/youtube_plugin/kodion/items/audio_item.py
@@ -14,7 +14,7 @@
class AudioItem(BaseItem):
- def __init__(self, name, uri, image=u'', fanart=u''):
+ def __init__(self, name, uri, image='', fanart=''):
BaseItem.__init__(self, name, uri, image, fanart)
self._duration = None
self._track_number = None
diff --git a/resources/lib/youtube_plugin/kodion/items/base_item.py b/resources/lib/youtube_plugin/kodion/items/base_item.py
index 0385052ab..d3b8572e2 100644
--- a/resources/lib/youtube_plugin/kodion/items/base_item.py
+++ b/resources/lib/youtube_plugin/kodion/items/base_item.py
@@ -19,7 +19,7 @@ class BaseItem(object):
VERSION = 3
INFO_DATE = 'date' # (string) iso 8601
- def __init__(self, name, uri, image=u'', fanart=u''):
+ def __init__(self, name, uri, image='', fanart=''):
self._version = BaseItem.VERSION
try:
@@ -29,7 +29,7 @@ def __init__(self, name, uri, image=u'', fanart=u''):
self._uri = uri
- self._image = u''
+ self._image = ''
self.set_image(image)
self._fanart = fanart
@@ -64,10 +64,7 @@ def get_name(self):
return self._name
def set_uri(self, uri):
- if isinstance(uri, str):
- self._uri = uri
- else:
- self._uri = ''
+ self._uri = uri if uri and isinstance(uri, str) else ''
def get_uri(self):
"""
diff --git a/resources/lib/youtube_plugin/kodion/items/directory_item.py b/resources/lib/youtube_plugin/kodion/items/directory_item.py
index 9c3749ebd..96072485b 100644
--- a/resources/lib/youtube_plugin/kodion/items/directory_item.py
+++ b/resources/lib/youtube_plugin/kodion/items/directory_item.py
@@ -12,7 +12,7 @@
class DirectoryItem(BaseItem):
- def __init__(self, name, uri, image=u'', fanart=u''):
+ def __init__(self, name, uri, image='', fanart=''):
BaseItem.__init__(self, name, uri, image, fanart)
self._plot = self.get_name()
self._is_action = False
diff --git a/resources/lib/youtube_plugin/kodion/items/image_item.py b/resources/lib/youtube_plugin/kodion/items/image_item.py
index b0f52b252..3909a48d6 100644
--- a/resources/lib/youtube_plugin/kodion/items/image_item.py
+++ b/resources/lib/youtube_plugin/kodion/items/image_item.py
@@ -12,7 +12,7 @@
class ImageItem(BaseItem):
- def __init__(self, name, uri, image=u'', fanart=u''):
+ def __init__(self, name, uri, image='', fanart=''):
BaseItem.__init__(self, name, uri, image, fanart)
self._title = None
diff --git a/resources/lib/youtube_plugin/kodion/items/uri_item.py b/resources/lib/youtube_plugin/kodion/items/uri_item.py
index d12a3f662..9ba74cc9b 100644
--- a/resources/lib/youtube_plugin/kodion/items/uri_item.py
+++ b/resources/lib/youtube_plugin/kodion/items/uri_item.py
@@ -13,4 +13,4 @@
class UriItem(BaseItem):
def __init__(self, uri):
- BaseItem.__init__(self, name=u'', uri=uri)
+ BaseItem.__init__(self, name='', uri=uri)
diff --git a/resources/lib/youtube_plugin/kodion/items/utils.py b/resources/lib/youtube_plugin/kodion/items/utils.py
index db7f235a4..9cdd6b6a4 100644
--- a/resources/lib/youtube_plugin/kodion/items/utils.py
+++ b/resources/lib/youtube_plugin/kodion/items/utils.py
@@ -24,10 +24,10 @@ def from_json(json_data):
"""
def _from_json(_json_data):
- mapping = {'VideoItem': lambda: VideoItem(u'', u''),
- 'DirectoryItem': lambda: DirectoryItem(u'', u''),
- 'AudioItem': lambda: AudioItem(u'', u''),
- 'ImageItem': lambda: ImageItem(u'', u'')}
+ mapping = {'VideoItem': lambda: VideoItem('', ''),
+ 'DirectoryItem': lambda: DirectoryItem('', ''),
+ 'AudioItem': lambda: AudioItem('', ''),
+ 'ImageItem': lambda: ImageItem('', '')}
item = None
item_type = _json_data.get('type', None)
diff --git a/resources/lib/youtube_plugin/kodion/items/video_item.py b/resources/lib/youtube_plugin/kodion/items/video_item.py
index 7d19fd4d9..acb0620f9 100644
--- a/resources/lib/youtube_plugin/kodion/items/video_item.py
+++ b/resources/lib/youtube_plugin/kodion/items/video_item.py
@@ -19,7 +19,7 @@
class VideoItem(BaseItem):
- def __init__(self, name, uri, image=u'', fanart=u''):
+ def __init__(self, name, uri, image='', fanart=''):
BaseItem.__init__(self, name, uri, image, fanart)
self._genre = None
self._aired = None
@@ -107,7 +107,9 @@ def set_premiered(self, year, month, day):
self._premiered = date.isoformat()
def set_premiered_from_datetime(self, date_time):
- self.set_premiered(year=date_time.year, month=date_time.month, day=date_time.day)
+ self.set_premiered(year=date_time.year,
+ month=date_time.month,
+ day=date_time.day)
def get_premiered(self):
return self._premiered
@@ -191,7 +193,9 @@ def get_aired_utc(self):
return self._aired_utc
def set_aired_from_datetime(self, date_time):
- self.set_aired(year=date_time.year, month=date_time.month, day=date_time.day)
+ self.set_aired(year=date_time.year,
+ month=date_time.month,
+ day=date_time.day)
def set_scheduled_start_utc(self, dt):
self._scheduled_start_utc = dt
@@ -221,8 +225,12 @@ def set_date(self, year, month, day, hour=0, minute=0, second=0):
self._date = date.isoformat(sep=' ')
def set_date_from_datetime(self, date_time):
- self.set_date(year=date_time.year, month=date_time.month, day=date_time.day, hour=date_time.hour,
- minute=date_time.minute, second=date_time.second)
+ self.set_date(year=date_time.year,
+ month=date_time.month,
+ day=date_time.day,
+ hour=date_time.hour,
+ minute=date_time.minute,
+ second=date_time.second)
def get_date(self):
return self._date
diff --git a/resources/lib/youtube_plugin/kodion/json_store/json_store.py b/resources/lib/youtube_plugin/kodion/json_store/json_store.py
index 3d071a7b9..52b1e06f3 100644
--- a/resources/lib/youtube_plugin/kodion/json_store/json_store.py
+++ b/resources/lib/youtube_plugin/kodion/json_store/json_store.py
@@ -17,6 +17,7 @@
from .. import logger
+
try:
xbmc.translatePath = xbmcvfs.translatePath
except AttributeError:
diff --git a/resources/lib/youtube_plugin/kodion/utils/access_manager.py b/resources/lib/youtube_plugin/kodion/utils/access_manager.py
index 2e6405bb3..f3d3a0464 100644
--- a/resources/lib/youtube_plugin/kodion/utils/access_manager.py
+++ b/resources/lib/youtube_plugin/kodion/utils/access_manager.py
@@ -344,8 +344,7 @@ def dev_keys_changed(self, addon_id, api_key, client_id, client_secret):
if last_hash != current_hash:
self.set_dev_last_key_hash(addon_id, current_hash)
return True
- else:
- return False
+ return False
@staticmethod
def __calc_key_hash(api_key, client_id, client_secret):
diff --git a/resources/lib/youtube_plugin/kodion/utils/http_server.py b/resources/lib/youtube_plugin/kodion/utils/http_server.py
index 522e5a830..8e3202719 100644
--- a/resources/lib/youtube_plugin/kodion/utils/http_server.py
+++ b/resources/lib/youtube_plugin/kodion/utils/http_server.py
@@ -55,9 +55,8 @@ def connection_allowed(self):
if not conn_allowed:
logger.log_debug('HTTPServer: Connection from |%s| not allowed' % client_ip)
- else:
- if self.path != '/ping':
- logger.log_debug(' '.join(log_lines))
+ elif self.path != '/ping':
+ logger.log_debug(' '.join(log_lines))
return conn_allowed
# noinspection PyPep8Naming
@@ -82,86 +81,82 @@ def do_GET(self):
if not self.connection_allowed():
self.send_error(403)
- else:
- if mpd_proxy_enabled and self.path.endswith('.mpd'):
- file_path = os.path.join(self.base_path, self.path.strip('/').strip('\\'))
- file_chunk = True
- logger.log_debug('HTTPServer: Request file path |{file_path}|'.format(file_path=file_path.encode('utf-8')))
- try:
- with open(file_path, 'rb') as f:
- self.send_response(200)
- self.send_header('Content-Type', 'application/xml+dash')
- self.send_header('Content-Length', os.path.getsize(file_path))
- self.end_headers()
- while file_chunk:
- file_chunk = f.read(self.chunk_size)
- if file_chunk:
- self.wfile.write(file_chunk)
- except IOError:
- response = 'File Not Found: |{proxy_path}| -> |{file_path}|'.format(proxy_path=self.path, file_path=file_path.encode('utf-8'))
- self.send_error(404, response)
- elif api_config_enabled and stripped_path.lower() == '/api':
- html = self.api_config_page()
- html = html.encode('utf-8')
- self.send_response(200)
- self.send_header('Content-Type', 'text/html; charset=utf-8')
- self.send_header('Content-Length', len(html))
- self.end_headers()
- for chunk in self.get_chunks(html):
- self.wfile.write(chunk)
- elif api_config_enabled and stripped_path.startswith('/api_submit'):
- addon = xbmcaddon.Addon('plugin.video.youtube')
- i18n = addon.getLocalizedString
- xbmc.executebuiltin('Dialog.Close(addonsettings,true)')
- old_api_key = addon.getSetting('youtube.api.key')
- old_api_id = addon.getSetting('youtube.api.id')
- old_api_secret = addon.getSetting('youtube.api.secret')
- query = urlparse(self.path).query
- params = parse_qs(query)
- api_key = params.get('api_key', [None])[0]
- api_id = params.get('api_id', [None])[0]
- api_secret = params.get('api_secret', [None])[0]
- if api_key and api_id and api_secret:
- footer = i18n(30638)
- else:
- footer = u''
- if re.search(r'api_key=(?:&|$)', query):
- api_key = ''
- if re.search(r'api_id=(?:&|$)', query):
- api_id = ''
- if re.search(r'api_secret=(?:&|$)', query):
- api_secret = ''
- updated = []
- if api_key is not None and api_key != old_api_key:
- addon.setSetting('youtube.api.key', api_key)
- updated.append(i18n(30201))
- if api_id is not None and api_id != old_api_id:
- addon.setSetting('youtube.api.id', api_id)
- updated.append(i18n(30202))
- if api_secret is not None and api_secret != old_api_secret:
- updated.append(i18n(30203))
- addon.setSetting('youtube.api.secret', api_secret)
- if addon.getSetting('youtube.api.key') and addon.getSetting('youtube.api.id') and \
- addon.getSetting('youtube.api.secret'):
- enabled = i18n(30636)
- else:
- enabled = i18n(30637)
- if not updated:
- updated = i18n(30635)
- else:
- updated = i18n(30631) % u', '.join(updated)
- html = self.api_submit_page(updated, enabled, footer)
- html = html.encode('utf-8')
- self.send_response(200)
- self.send_header('Content-Type', 'text/html; charset=utf-8')
- self.send_header('Content-Length', len(html))
- self.end_headers()
- for chunk in self.get_chunks(html):
- self.wfile.write(chunk)
- elif stripped_path == '/ping':
- self.send_error(204)
+ elif mpd_proxy_enabled and self.path.endswith('.mpd'):
+ file_path = os.path.join(self.base_path, self.path.strip('/').strip('\\'))
+ file_chunk = True
+ logger.log_debug('HTTPServer: Request file path |{file_path}|'.format(file_path=file_path.encode('utf-8')))
+ try:
+ with open(file_path, 'rb') as f:
+ self.send_response(200)
+ self.send_header('Content-Type', 'application/xml+dash')
+ self.send_header('Content-Length', os.path.getsize(file_path))
+ self.end_headers()
+ while file_chunk:
+ file_chunk = f.read(self.chunk_size)
+ if file_chunk:
+ self.wfile.write(file_chunk)
+ except IOError:
+ response = 'File Not Found: |{proxy_path}| -> |{file_path}|'.format(proxy_path=self.path, file_path=file_path.encode('utf-8'))
+ self.send_error(404, response)
+ elif api_config_enabled and stripped_path.lower() == '/api':
+ html = self.api_config_page()
+ html = html.encode('utf-8')
+ self.send_response(200)
+ self.send_header('Content-Type', 'text/html; charset=utf-8')
+ self.send_header('Content-Length', len(html))
+ self.end_headers()
+ for chunk in self.get_chunks(html):
+ self.wfile.write(chunk)
+ elif api_config_enabled and stripped_path.startswith('/api_submit'):
+ addon = xbmcaddon.Addon('plugin.video.youtube')
+ i18n = addon.getLocalizedString
+ xbmc.executebuiltin('Dialog.Close(addonsettings,true)')
+ old_api_key = addon.getSetting('youtube.api.key')
+ old_api_id = addon.getSetting('youtube.api.id')
+ old_api_secret = addon.getSetting('youtube.api.secret')
+ query = urlparse(self.path).query
+ params = parse_qs(query)
+ api_key = params.get('api_key', [None])[0]
+ api_id = params.get('api_id', [None])[0]
+ api_secret = params.get('api_secret', [None])[0]
+ footer = i18n(30638) if api_key and api_id and api_secret else ''
+ if re.search(r'api_key=(?:&|$)', query):
+ api_key = ''
+ if re.search(r'api_id=(?:&|$)', query):
+ api_id = ''
+ if re.search(r'api_secret=(?:&|$)', query):
+ api_secret = ''
+ updated = []
+ if api_key is not None and api_key != old_api_key:
+ addon.setSetting('youtube.api.key', api_key)
+ updated.append(i18n(30201))
+ if api_id is not None and api_id != old_api_id:
+ addon.setSetting('youtube.api.id', api_id)
+ updated.append(i18n(30202))
+ if api_secret is not None and api_secret != old_api_secret:
+ updated.append(i18n(30203))
+ addon.setSetting('youtube.api.secret', api_secret)
+ if addon.getSetting('youtube.api.key') and addon.getSetting('youtube.api.id') and \
+ addon.getSetting('youtube.api.secret'):
+ enabled = i18n(30636)
else:
- self.send_error(501)
+ enabled = i18n(30637)
+ if not updated:
+ updated = i18n(30635)
+ else:
+ updated = i18n(30631) % ', '.join(updated)
+ html = self.api_submit_page(updated, enabled, footer)
+ html = html.encode('utf-8')
+ self.send_response(200)
+ self.send_header('Content-Type', 'text/html; charset=utf-8')
+ self.send_header('Content-Length', len(html))
+ self.end_headers()
+ for chunk in self.get_chunks(html):
+ self.wfile.write(chunk)
+ elif stripped_path == '/ping':
+ self.send_error(204)
+ else:
+ self.send_error(501)
# noinspection PyPep8Naming
def do_HEAD(self):
@@ -289,172 +284,172 @@ def api_submit_page(updated_keys, enabled, footer):
class Pages(object):
api_configuration = {
'html':
- u'\n\n'
- u'
\n\t\n'
- u'\t{title}\n'
- u'\t\n'
- u'\n\n'
- u'\t\n'
- u'\t
{header}
\n'
- u'\t\n'
- u'\t\n'
- u'\n',
+ '\n\n'
+ '\n\t\n'
+ '\t{title}\n'
+ '\t\n'
+ '\n\n'
+ '\t\n'
+ '\t
{header}
\n'
+ '\t\n'
+ '\t\n'
+ '\n',
'css':
- u'body {\n'
- u' background: #141718;\n'
- u'}\n'
- u'.center {\n'
- u' margin: auto;\n'
- u' width: 600px;\n'
- u' padding: 10px;\n'
- u'}\n'
- u'.config_form {\n'
- u' width: 575px;\n'
- u' height: 145px;\n'
- u' font-size: 16px;\n'
- u' background: #1a2123;\n'
- u' padding: 30px 30px 15px 30px;\n'
- u' border: 5px solid #1a2123;\n'
- u'}\n'
- u'h5 {\n'
- u' font-family: Arial, Helvetica, sans-serif;\n'
- u' font-size: 16px;\n'
- u' color: #fff;\n'
- u' font-weight: 600;\n'
- u' width: 575px;\n'
- u' height: 20px;\n'
- u' background: #0f84a5;\n'
- u' padding: 5px 30px 5px 30px;\n'
- u' border: 5px solid #0f84a5;\n'
- u' margin: 0px;\n'
- u'}\n'
- u'.config_form input[type=submit],\n'
- u'.config_form input[type=button],\n'
- u'.config_form input[type=text],\n'
- u'.config_form textarea,\n'
- u'.config_form label {\n'
- u' font-family: Arial, Helvetica, sans-serif;\n'
- u' font-size: 16px;\n'
- u' color: #fff;\n'
- u'}\n'
- u'.config_form label {\n'
- u' display:block;\n'
- u' margin-bottom: 10px;\n'
- u'}\n'
- u'.config_form label > span {\n'
- u' display: inline-block;\n'
- u' float: left;\n'
- u' width: 150px;\n'
- u'}\n'
- u'.config_form input[type=text] {\n'
- u' background: transparent;\n'
- u' border: none;\n'
- u' border-bottom: 1px solid #147a96;\n'
- u' width: 400px;\n'
- u' outline: none;\n'
- u' padding: 0px 0px 0px 0px;\n'
- u'}\n'
- u'.config_form input[type=text]:focus {\n'
- u' border-bottom: 1px dashed #0f84a5;\n'
- u'}\n'
- u'.config_form input[type=submit],\n'
- u'.config_form input[type=button] {\n'
- u' width: 150px;\n'
- u' background: #141718;\n'
- u' border: none;\n'
- u' padding: 8px 0px 8px 10px;\n'
- u' border-radius: 5px;\n'
- u' color: #fff;\n'
- u' margin-top: 10px\n'
- u'}\n'
- u'.config_form input[type=submit]:hover,\n'
- u'.config_form input[type=button]:hover {\n'
- u' background: #0f84a5;\n'
- u'}\n'
+ 'body {\n'
+ ' background: #141718;\n'
+ '}\n'
+ '.center {\n'
+ ' margin: auto;\n'
+ ' width: 600px;\n'
+ ' padding: 10px;\n'
+ '}\n'
+ '.config_form {\n'
+ ' width: 575px;\n'
+ ' height: 145px;\n'
+ ' font-size: 16px;\n'
+ ' background: #1a2123;\n'
+ ' padding: 30px 30px 15px 30px;\n'
+ ' border: 5px solid #1a2123;\n'
+ '}\n'
+ 'h5 {\n'
+ ' font-family: Arial, Helvetica, sans-serif;\n'
+ ' font-size: 16px;\n'
+ ' color: #fff;\n'
+ ' font-weight: 600;\n'
+ ' width: 575px;\n'
+ ' height: 20px;\n'
+ ' background: #0f84a5;\n'
+ ' padding: 5px 30px 5px 30px;\n'
+ ' border: 5px solid #0f84a5;\n'
+ ' margin: 0px;\n'
+ '}\n'
+ '.config_form input[type=submit],\n'
+ '.config_form input[type=button],\n'
+ '.config_form input[type=text],\n'
+ '.config_form textarea,\n'
+ '.config_form label {\n'
+ ' font-family: Arial, Helvetica, sans-serif;\n'
+ ' font-size: 16px;\n'
+ ' color: #fff;\n'
+ '}\n'
+ '.config_form label {\n'
+ ' display:block;\n'
+ ' margin-bottom: 10px;\n'
+ '}\n'
+ '.config_form label > span {\n'
+ ' display: inline-block;\n'
+ ' float: left;\n'
+ ' width: 150px;\n'
+ '}\n'
+ '.config_form input[type=text] {\n'
+ ' background: transparent;\n'
+ ' border: none;\n'
+ ' border-bottom: 1px solid #147a96;\n'
+ ' width: 400px;\n'
+ ' outline: none;\n'
+ ' padding: 0px 0px 0px 0px;\n'
+ '}\n'
+ '.config_form input[type=text]:focus {\n'
+ ' border-bottom: 1px dashed #0f84a5;\n'
+ '}\n'
+ '.config_form input[type=submit],\n'
+ '.config_form input[type=button] {\n'
+ ' width: 150px;\n'
+ ' background: #141718;\n'
+ ' border: none;\n'
+ ' padding: 8px 0px 8px 10px;\n'
+ ' border-radius: 5px;\n'
+ ' color: #fff;\n'
+ ' margin-top: 10px\n'
+ '}\n'
+ '.config_form input[type=submit]:hover,\n'
+ '.config_form input[type=button]:hover {\n'
+ ' background: #0f84a5;\n'
+ '}\n'
}
api_submit = {
'html':
- u'\n\n'
- u'\n\t\n'
- u'\t{title}\n'
- u'\t\n'
- u'\n\n'
- u'\t\n'
- u'\t
{header}
\n'
- u'\t
\n'
- u'\t\t
{updated}\n'
- u'\t\t
{enabled}\n'
- u'\t\t
\n'
- u'\t\t
\n'
- u'\t\t
\n'
- u'\t\t
\n'
- u'\t\t
\n'
- u'\t\t\t{footer}\n'
- u'\t\t
\n'
- u'\t
\n'
- u'\t
\n'
- u'\n',
+ '\n\n'
+ '\n\t\n'
+ '\t{title}\n'
+ '\t\n'
+ '\n\n'
+ '\t\n'
+ '\t
{header}
\n'
+ '\t
\n'
+ '\t\t
{updated}\n'
+ '\t\t
{enabled}\n'
+ '\t\t
\n'
+ '\t\t
\n'
+ '\t\t
\n'
+ '\t\t
\n'
+ '\t\t
\n'
+ '\t\t\t{footer}\n'
+ '\t\t
\n'
+ '\t
\n'
+ '\t
\n'
+ '\n',
'css':
- u'body {\n'
- u' background: #141718;\n'
- u'}\n'
- u'.center {\n'
- u' margin: auto;\n'
- u' width: 600px;\n'
- u' padding: 10px;\n'
- u'}\n'
- u'.textcenter {\n'
- u' margin: auto;\n'
- u' width: 600px;\n'
- u' padding: 10px;\n'
- u' text-align: center;\n'
- u'}\n'
- u'.content {\n'
- u' width: 575px;\n'
- u' height: 145px;\n'
- u' background: #1a2123;\n'
- u' padding: 30px 30px 15px 30px;\n'
- u' border: 5px solid #1a2123;\n'
- u'}\n'
- u'h5 {\n'
- u' font-family: Arial, Helvetica, sans-serif;\n'
- u' font-size: 16px;\n'
- u' color: #fff;\n'
- u' font-weight: 600;\n'
- u' width: 575px;\n'
- u' height: 20px;\n'
- u' background: #0f84a5;\n'
- u' padding: 5px 30px 5px 30px;\n'
- u' border: 5px solid #0f84a5;\n'
- u' margin: 0px;\n'
- u'}\n'
- u'span {\n'
- u' font-family: Arial, Helvetica, sans-serif;\n'
- u' font-size: 16px;\n'
- u' color: #fff;\n'
- u' display: block;\n'
- u' float: left;\n'
- u' width: 575px;\n'
- u'}\n'
- u'small {\n'
- u' font-family: Arial, Helvetica, sans-serif;\n'
- u' font-size: 12px;\n'
- u' color: #fff;\n'
- u'}\n'
+ 'body {\n'
+ ' background: #141718;\n'
+ '}\n'
+ '.center {\n'
+ ' margin: auto;\n'
+ ' width: 600px;\n'
+ ' padding: 10px;\n'
+ '}\n'
+ '.textcenter {\n'
+ ' margin: auto;\n'
+ ' width: 600px;\n'
+ ' padding: 10px;\n'
+ ' text-align: center;\n'
+ '}\n'
+ '.content {\n'
+ ' width: 575px;\n'
+ ' height: 145px;\n'
+ ' background: #1a2123;\n'
+ ' padding: 30px 30px 15px 30px;\n'
+ ' border: 5px solid #1a2123;\n'
+ '}\n'
+ 'h5 {\n'
+ ' font-family: Arial, Helvetica, sans-serif;\n'
+ ' font-size: 16px;\n'
+ ' color: #fff;\n'
+ ' font-weight: 600;\n'
+ ' width: 575px;\n'
+ ' height: 20px;\n'
+ ' background: #0f84a5;\n'
+ ' padding: 5px 30px 5px 30px;\n'
+ ' border: 5px solid #0f84a5;\n'
+ ' margin: 0px;\n'
+ '}\n'
+ 'span {\n'
+ ' font-family: Arial, Helvetica, sans-serif;\n'
+ ' font-size: 16px;\n'
+ ' color: #fff;\n'
+ ' display: block;\n'
+ ' float: left;\n'
+ ' width: 575px;\n'
+ '}\n'
+ 'small {\n'
+ ' font-family: Arial, Helvetica, sans-serif;\n'
+ ' font-size: 12px;\n'
+ ' color: #fff;\n'
+ '}\n'
}
diff --git a/resources/lib/youtube_plugin/kodion/utils/ip_api.py b/resources/lib/youtube_plugin/kodion/utils/ip_api.py
index dc371483c..f6873e253 100644
--- a/resources/lib/youtube_plugin/kodion/utils/ip_api.py
+++ b/resources/lib/youtube_plugin/kodion/utils/ip_api.py
@@ -42,6 +42,5 @@ def coordinates(self):
if lat is None or lon is None:
self._context.log_error('No coordinates returned')
return None
- else:
- self._context.log_debug('Coordinates found')
- return lat, lon
+ self._context.log_debug('Coordinates found')
+ return lat, lon
diff --git a/resources/lib/youtube_plugin/kodion/utils/methods.py b/resources/lib/youtube_plugin/kodion/utils/methods.py
index 5a0738c63..490759552 100644
--- a/resources/lib/youtube_plugin/kodion/utils/methods.py
+++ b/resources/lib/youtube_plugin/kodion/utils/methods.py
@@ -22,6 +22,7 @@
__all__ = ['create_path', 'create_uri_path', 'strip_html_from_text', 'print_items', 'find_best_fit', 'to_utf8',
'to_str', 'to_unicode', 'select_stream', 'make_dirs', 'loose_version', 'find_video_id']
+
try:
xbmc.translatePath = xbmcvfs.translatePath
except AttributeError:
@@ -138,8 +139,7 @@ def _sort_stream_data(_stream_data):
def _find_best_fit_video(_stream_data):
if audio_only:
return video_quality - _stream_data.get('sort', (0, 0))[0]
- else:
- return video_quality - _stream_data.get('video', {}).get('height', 0)
+ return video_quality - _stream_data.get('video', {}).get('height', 0)
sorted_stream_data_list = sorted(stream_data_list, key=_sort_stream_data)
@@ -187,7 +187,7 @@ def create_path(*args):
uri_path = '/'.join(comps)
if uri_path:
- return u'/%s/' % uri_path
+ return '/%s/' % uri_path
return '/'
diff --git a/resources/lib/youtube_plugin/kodion/utils/monitor.py b/resources/lib/youtube_plugin/kodion/utils/monitor.py
index 9ca023e79..4105b4fd0 100644
--- a/resources/lib/youtube_plugin/kodion/utils/monitor.py
+++ b/resources/lib/youtube_plugin/kodion/utils/monitor.py
@@ -106,18 +106,26 @@ def httpd_port_sync(self):
self._old_httpd_port = self._httpd_port
def start_httpd(self):
+ if self.httpd:
+ return
+
+ logger.log_debug('HTTPServer: Starting |{ip}:{port}|'.format(
+ ip=self.httpd_address(),
+ port=str(self.httpd_port())
+ ))
+ self.httpd_port_sync()
+ self.httpd = get_http_server(address=self.httpd_address(), port=self.httpd_port())
if not self.httpd:
- logger.log_debug('HTTPServer: Starting |{ip}:{port}|'.format(ip=self.httpd_address(),
- port=str(self.httpd_port())))
- self.httpd_port_sync()
- self.httpd = get_http_server(address=self.httpd_address(), port=self.httpd_port())
- if self.httpd:
- self.httpd_thread = threading.Thread(target=self.httpd.serve_forever)
- self.httpd_thread.daemon = True
- self.httpd_thread.start()
- sock_name = self.httpd.socket.getsockname()
- logger.log_debug('HTTPServer: Serving on |{ip}:{port}|'.format(ip=str(sock_name[0]),
- port=str(sock_name[1])))
+ return
+
+ self.httpd_thread = threading.Thread(target=self.httpd.serve_forever)
+ self.httpd_thread.daemon = True
+ self.httpd_thread.start()
+ sock_name = self.httpd.socket.getsockname()
+ logger.log_debug('HTTPServer: Serving on |{ip}:{port}|'.format(
+ ip=str(sock_name[0]),
+ port=str(sock_name[1])
+ ))
def shutdown_httpd(self):
if self.httpd:
@@ -160,5 +168,4 @@ def remove_temp_dir(self):
if os.path.isdir(path):
logger.log_debug('Failed to remove directory: {dir}'.format(dir=path.encode('utf-8')))
return False
- else:
- return True
+ return True
diff --git a/resources/lib/youtube_plugin/kodion/utils/player.py b/resources/lib/youtube_plugin/kodion/utils/player.py
index c8ce05226..0bdccf1c1 100644
--- a/resources/lib/youtube_plugin/kodion/utils/player.py
+++ b/resources/lib/youtube_plugin/kodion/utils/player.py
@@ -146,7 +146,7 @@ def run(self):
except RuntimeError:
pass
- if self.current_time < 0.0:
+ if self.current_time < 0:
self.current_time = 0.0
if self.abort_now():
@@ -162,7 +162,7 @@ def run(self):
self.update_times(last_total_time, last_current_time, last_segment_start, last_percent_complete)
break
- if seek_time and seek_time != 0.0:
+ if seek_time:
player.seekTime(seek_time)
try:
self.current_time = float(player.getTime())
@@ -194,43 +194,43 @@ def run(self):
self.update_times(last_total_time, last_current_time, last_segment_start, last_percent_complete)
break
- if is_logged_in and report_url and use_remote_history:
- if first_report or (p_waited >= report_interval):
- if first_report:
- first_report = False
- self.segment_start = 0.0
- self.current_time = 0.0
- self.percent_complete = 0
-
- p_waited = 0.0
-
- if self.segment_start < 0.0:
- self.segment_start = 0.0
-
- if state == 'playing':
- segment_end = self.current_time
- else:
- segment_end = self.segment_start
-
- if segment_end > float(self.total_time):
- segment_end = float(self.total_time)
-
- if self.segment_start > segment_end:
- segment_end = self.segment_start + 10.0
-
- if state == 'playing' or last_state == 'playing': # only report state='paused' once
- client.update_watch_history(self.video_id, report_url
- .format(st=format(self.segment_start, '.3f'),
- et=format(segment_end, '.3f'),
- state=state))
- self.context.log_debug(
- 'Playback reported [%s]: %s segment start, %s segment end @ %s%% state=%s' %
- (self.video_id,
- format(self.segment_start, '.3f'),
- format(segment_end, '.3f'),
- self.percent_complete, state))
-
- self.segment_start = segment_end
+ if (is_logged_in and report_url and use_remote_history and (
+ first_report or p_waited >= report_interval)):
+ if first_report:
+ first_report = False
+ self.segment_start = 0.0
+ self.current_time = 0.0
+ self.percent_complete = 0
+
+ p_waited = 0.0
+
+ if self.segment_start < 0:
+ self.segment_start = 0.0
+
+ if state == 'playing':
+ segment_end = self.current_time
+ else:
+ segment_end = self.segment_start
+
+ if segment_end > float(self.total_time):
+ segment_end = float(self.total_time)
+
+ if self.segment_start > segment_end:
+ segment_end = self.segment_start + 10.0
+
+ if state == 'playing' or last_state == 'playing': # only report state='paused' once
+ client.update_watch_history(self.video_id, report_url
+ .format(st=format(self.segment_start, '.3f'),
+ et=format(segment_end, '.3f'),
+ state=state))
+ self.context.log_debug(
+ 'Playback reported [%s]: %s segment start, %s segment end @ %s%% state=%s' %
+ (self.video_id,
+ format(self.segment_start, '.3f'),
+ format(segment_end, '.3f'),
+ self.percent_complete, state))
+
+ self.segment_start = segment_end
if self.abort_now():
break
@@ -292,42 +292,40 @@ def run(self):
self.context.get_playback_history().update(self.video_id, play_count, self.total_time,
self.current_time, self.percent_complete)
- if not refresh_only:
- if is_logged_in:
-
- if settings.get_bool('youtube.playlist.watchlater.autoremove', True):
- watch_later_id = access_manager.get_watch_later_id()
-
- if watch_later_id:
- playlist_item_id = \
- client.get_playlist_item_id_of_video_id(playlist_id=watch_later_id, video_id=self.video_id)
- if playlist_item_id:
- json_data = client.remove_video_from_playlist(watch_later_id, playlist_item_id)
- _ = self.provider.v3_handle_error(self.provider, self.context, json_data)
-
- history_playlist_id = access_manager.get_watch_history_id()
- if history_playlist_id and history_playlist_id != 'HL':
- json_data = client.add_video_to_playlist(history_playlist_id, self.video_id)
- _ = self.provider.v3_handle_error(self.provider, self.context, json_data)
-
- # rate video
- if settings.get_bool('youtube.post.play.rate', False):
- do_rating = True
- if not settings.get_bool('youtube.post.play.rate.playlists', False):
- playlist = xbmc.PlayList(xbmc.PLAYLIST_VIDEO)
- do_rating = int(playlist.size()) < 2
-
- if do_rating:
- json_data = client.get_video_rating(self.video_id)
- success = self.provider.v3_handle_error(self.provider, self.context, json_data)
- if success:
- items = json_data.get('items', [{'rating': 'none'}])
- rating = items[0].get('rating', 'none')
- if rating == 'none':
- rating_match = \
- re.search('/(?P[^/]+)/(?P[^/]+)', '/%s/%s/' %
- (self.video_id, rating))
- self.provider.yt_video.process('rate', self.provider, self.context, rating_match)
+ if not refresh_only and is_logged_in:
+ if settings.get_bool('youtube.playlist.watchlater.autoremove', True):
+ watch_later_id = access_manager.get_watch_later_id()
+
+ if watch_later_id:
+ playlist_item_id = \
+ client.get_playlist_item_id_of_video_id(playlist_id=watch_later_id, video_id=self.video_id)
+ if playlist_item_id:
+ json_data = client.remove_video_from_playlist(watch_later_id, playlist_item_id)
+ _ = self.provider.v3_handle_error(self.provider, self.context, json_data)
+
+ history_playlist_id = access_manager.get_watch_history_id()
+ if history_playlist_id and history_playlist_id != 'HL':
+ json_data = client.add_video_to_playlist(history_playlist_id, self.video_id)
+ _ = self.provider.v3_handle_error(self.provider, self.context, json_data)
+
+ # rate video
+ if settings.get_bool('youtube.post.play.rate', False):
+ do_rating = True
+ if not settings.get_bool('youtube.post.play.rate.playlists', False):
+ playlist = xbmc.PlayList(xbmc.PLAYLIST_VIDEO)
+ do_rating = int(playlist.size()) < 2
+
+ if do_rating:
+ json_data = client.get_video_rating(self.video_id)
+ success = self.provider.v3_handle_error(self.provider, self.context, json_data)
+ if success:
+ items = json_data.get('items', [{'rating': 'none'}])
+ rating = items[0].get('rating', 'none')
+ if rating == 'none':
+ rating_match = \
+ re.search('/(?P[^/]+)/(?P[^/]+)', '/%s/%s/' %
+ (self.video_id, rating))
+ self.provider.yt_video.process('rate', self.provider, self.context, rating_match)
playlist = xbmc.PlayList(xbmc.PLAYLIST_VIDEO)
do_refresh = (int(playlist.size()) < 2) or (playlist.getposition() == -1)
diff --git a/resources/lib/youtube_plugin/kodion/utils/storage.py b/resources/lib/youtube_plugin/kodion/utils/storage.py
index 17ba64062..e764274bd 100644
--- a/resources/lib/youtube_plugin/kodion/utils/storage.py
+++ b/resources/lib/youtube_plugin/kodion/utils/storage.py
@@ -70,15 +70,14 @@ def _execute(self, needs_commit, query, values=None):
Tests revealed that sqlite has problems to release the database in time. This happens no so often, but just to
be sure, we try at least 3 times to execute out statement.
"""
- for tries in range(3):
+ for _ in range(3):
try:
return self._cursor.execute(query, values)
except TypeError:
return None
except:
time.sleep(0.1)
- else:
- return None
+ return None
def _close(self):
if self._file is not None:
@@ -117,9 +116,10 @@ def _create_table(self):
self._table_created = True
def sync(self):
- if self._cursor is not None and self._needs_commit:
- self._needs_commit = False
- return self._execute(False, 'COMMIT')
+ if not self._cursor or not self._needs_commit:
+ return None
+ self._needs_commit = False
+ return self._execute(False, 'COMMIT')
def _set(self, item_id, item):
def _encode(obj):
diff --git a/resources/lib/youtube_plugin/kodion/utils/system_version.py b/resources/lib/youtube_plugin/kodion/utils/system_version.py
index 50e24b86a..09f0553fe 100644
--- a/resources/lib/youtube_plugin/kodion/utils/system_version.py
+++ b/resources/lib/youtube_plugin/kodion/utils/system_version.py
@@ -15,20 +15,20 @@
class SystemVersion(object):
def __init__(self, version, releasename, appname):
- if not isinstance(version, tuple):
- self._version = (0, 0, 0, 0)
- else:
- self._version = version
+ self._version = (
+ version if version and isinstance(version, tuple)
+ else (0, 0, 0, 0)
+ )
- if not releasename or not isinstance(releasename, str):
- self._releasename = 'UNKNOWN'
- else:
- self._releasename = releasename
+ self._releasename = (
+ releasename if releasename and isinstance(releasename, str)
+ else 'UNKNOWN'
+ )
- if not appname or not isinstance(appname, str):
- self._appname = 'UNKNOWN'
- else:
- self._appname = appname
+ self._appname = (
+ appname if appname and isinstance(appname, str)
+ else 'UNKNOWN'
+ )
try:
json_query = xbmc.executeJSONRPC('{ "jsonrpc": "2.0", "method": "Application.GetProperties", '
diff --git a/resources/lib/youtube_plugin/youtube/client/__config__.py b/resources/lib/youtube_plugin/youtube/client/__config__.py
index 7df58a97b..ac952299c 100644
--- a/resources/lib/youtube_plugin/youtube/client/__config__.py
+++ b/resources/lib/youtube_plugin/youtube/client/__config__.py
@@ -44,10 +44,10 @@ def _on_init(self):
# users are now pasting keys into api_keys.json
# try stripping whitespace and .apps.googleusercontent.com from keys and saving the result if they differ
stripped_key, stripped_id, stripped_secret = self._strip_api_keys(j_key, j_id, j_secret)
- if stripped_key and stripped_id and stripped_secret:
- if (j_key != stripped_key) or (j_id != stripped_id) or (j_secret != stripped_secret):
- self._json_api['keys']['personal'] = {'api_key': stripped_key, 'client_id': stripped_id, 'client_secret': stripped_secret}
- self._api_jstore.save(self._json_api)
+ if (stripped_key and stripped_id and stripped_secret
+ and (j_key != stripped_key or j_id != stripped_id or j_secret != stripped_secret)):
+ self._json_api['keys']['personal'] = {'api_key': stripped_key, 'client_id': stripped_id, 'client_secret': stripped_secret}
+ self._api_jstore.save(self._json_api)
original_key = self._settings.get_string('youtube.api.key')
original_id = self._settings.get_string('youtube.api.id')
@@ -81,18 +81,18 @@ def _on_init(self):
refresh_token = self._settings.get_string('kodion.refresh_token', '')
token_expires = self._settings.get_int('kodion.access_token.expires', -1)
last_hash = self._settings.get_string('youtube.api.last.hash', '')
- if not self._json_am['access_manager']['users'].get(user, {}).get('access_token') or \
- not self._json_am['access_manager']['users'].get(user, {}).get('refresh_token'):
- if access_token and refresh_token:
- self._json_am['access_manager']['users'][user]['access_token'] = access_token
- self._json_am['access_manager']['users'][user]['refresh_token'] = refresh_token
- self._json_am['access_manager']['users'][user]['token_expires'] = token_expires
- if switch == 'own':
- own_key_hash = self._get_key_set_hash('own')
- if last_hash == self._get_key_set_hash('own', True) or \
- last_hash == own_key_hash:
- self._json_am['access_manager']['users'][user]['last_key_hash'] = own_key_hash
- self._am_jstore.save(self._json_am)
+ if ((not self._json_am['access_manager']['users'].get(user, {}).get('access_token')
+ or not self._json_am['access_manager']['users'].get(user, {}).get('refresh_token'))
+ and access_token and refresh_token):
+ self._json_am['access_manager']['users'][user]['access_token'] = access_token
+ self._json_am['access_manager']['users'][user]['refresh_token'] = refresh_token
+ self._json_am['access_manager']['users'][user]['token_expires'] = token_expires
+ if switch == 'own':
+ own_key_hash = self._get_key_set_hash('own')
+ if (last_hash == self._get_key_set_hash('own', True)
+ or last_hash == own_key_hash):
+ self._json_am['access_manager']['users'][user]['last_key_hash'] = own_key_hash
+ self._am_jstore.save(self._json_am)
if access_token or refresh_token or last_hash:
self._settings.set_string('kodion.access_token', '')
self._settings.set_string('kodion.refresh_token', '')
@@ -121,26 +121,23 @@ def has_own_api_keys(self):
own_key = self._json_api['keys']['personal']['api_key']
own_id = self._json_api['keys']['personal']['client_id']
own_secret = self._json_api['keys']['personal']['client_secret']
- return False if not own_key or \
- not own_id or \
- not own_secret else True
+ return own_key and own_id and own_secret
def get_api_keys(self, switch):
self._json_api = self._api_jstore.get_data()
+ if switch == 'developer':
+ return self._json_api['keys'][switch]
if switch == 'youtube-tv':
- api_key = b64decode(key_sets['youtube-tv']['key']).decode('utf-8'),
- client_id = u''.join([b64decode(key_sets['youtube-tv']['id']).decode('utf-8'), u'.apps.googleusercontent.com'])
- client_secret = b64decode(key_sets['youtube-tv']['secret']).decode('utf-8')
- elif switch == 'developer':
- self._json_api = self._api_jstore.get_data()
- return self._json_api['keys']['developer']
+ api_key = b64decode(key_sets[switch]['key']).decode('utf-8'),
+ client_id = ''.join([b64decode(key_sets[switch]['id']).decode('utf-8'), '.apps.googleusercontent.com'])
+ client_secret = b64decode(key_sets[switch]['secret']).decode('utf-8')
elif switch == 'own':
api_key = self._json_api['keys']['personal']['api_key']
- client_id = u''.join([self._json_api['keys']['personal']['client_id'], u'.apps.googleusercontent.com'])
+ client_id = ''.join([self._json_api['keys']['personal']['client_id'], '.apps.googleusercontent.com'])
client_secret = self._json_api['keys']['personal']['client_secret']
else:
api_key = b64decode(key_sets['provided'][switch]['key']).decode('utf-8')
- client_id = u''.join([b64decode(key_sets['provided'][switch]['id']).decode('utf-8'), u'.apps.googleusercontent.com'])
+ client_id = ''.join([b64decode(key_sets['provided'][switch]['id']).decode('utf-8'), '.apps.googleusercontent.com'])
client_secret = b64decode(key_sets['provided'][switch]['secret']).decode('utf-8')
return api_key, client_id, client_secret
@@ -152,14 +149,13 @@ def _api_keys_changed(self, switch):
if last_set_hash != current_set_hash:
self.changed = True
return current_set_hash
- else:
- self.changed = False
- return None
+ self.changed = False
+ return None
def _get_key_set_hash(self, switch, old=False):
api_key, client_id, client_secret = self.get_api_keys(switch)
if old and switch == 'own':
- client_id = client_id.replace(u'.apps.googleusercontent.com', u'')
+ client_id = client_id.replace('.apps.googleusercontent.com', '')
m = md5()
m.update(api_key.encode('utf-8'))
m.update(client_id.encode('utf-8'))
@@ -209,9 +205,9 @@ def _strip_api_keys(self, api_key, client_id, client_secret):
return return_key, return_id, return_secret
-notification_data = {'use_httpd': (__settings.use_mpd_videos() or
- __settings.use_mpd_live_streams()) or
- (__settings.api_config_page()),
+notification_data = {'use_httpd': (__settings.use_mpd_videos()
+ or __settings.use_mpd_live_streams()
+ or __settings.api_config_page()),
'httpd_port': __settings.httpd_port(),
'whitelist': __settings.httpd_whitelist(),
'httpd_address': __settings.httpd_listen()
@@ -227,9 +223,7 @@ def _strip_api_keys(self, api_key, client_id, client_secret):
api = dict()
youtube_tv = dict()
-_current_switch = _api_check.get_current_switch()
-
-api['key'], api['id'], api['secret'] = _api_check.get_api_keys(_current_switch)
+api['key'], api['id'], api['secret'] = _api_check.get_api_keys(_api_check.get_current_switch())
youtube_tv['key'], youtube_tv['id'], youtube_tv['secret'] = _api_check.get_api_keys('youtube-tv')
diff --git a/resources/lib/youtube_plugin/youtube/client/login_client.py b/resources/lib/youtube_plugin/youtube/client/login_client.py
index 2948c4992..4135ac025 100644
--- a/resources/lib/youtube_plugin/youtube/client/login_client.py
+++ b/resources/lib/youtube_plugin/youtube/client/login_client.py
@@ -323,22 +323,20 @@ def _get_config_type(self, client_id, client_secret=None):
using_conf_main = ((client_id == self.CONFIGS['main'].get('id')) and (client_secret == self.CONFIGS['main'].get('secret')))
if not using_conf_main and not using_conf_tv:
return 'None'
- elif using_conf_tv:
+ if using_conf_tv:
return 'YouTube-TV'
- elif using_conf_main:
+ if using_conf_main:
return 'YouTube-Kodi'
- else:
- return 'Unknown'
+ return 'Unknown'
@staticmethod
def _get_response_dump(response, json_data=None):
if json_data:
return json_data
- else:
+ try:
+ return response.json()
+ except ValueError:
try:
- return response.json()
- except ValueError:
- try:
- return response.text
- except:
- return 'None'
+ return response.text
+ except:
+ return 'None'
diff --git a/resources/lib/youtube_plugin/youtube/helper/resource_manager.py b/resources/lib/youtube_plugin/youtube/helper/resource_manager.py
index 81afc78ab..d8b1eeb64 100644
--- a/resources/lib/youtube_plugin/youtube/helper/resource_manager.py
+++ b/resources/lib/youtube_plugin/youtube/helper/resource_manager.py
@@ -138,6 +138,7 @@ def _update_videos(self, video_ids, live_details=False, suppress_errors=False):
if self.handle_error(json_data, suppress_errors) or suppress_errors:
return result
+ return {}
@staticmethod
def _make_list_of_50(list_of_ids):
@@ -188,6 +189,7 @@ def _update_playlists(self, playlists_ids):
if self.handle_error(json_data):
return result
+ return {}
def get_playlists(self, playlists_ids):
list_of_50s = self._make_list_of_50(playlists_ids)
diff --git a/resources/lib/youtube_plugin/youtube/helper/signature/cipher.py b/resources/lib/youtube_plugin/youtube/helper/signature/cipher.py
index 8db38e1dd..ae89edf24 100644
--- a/resources/lib/youtube_plugin/youtube/helper/signature/cipher.py
+++ b/resources/lib/youtube_plugin/youtube/helper/signature/cipher.py
@@ -32,7 +32,7 @@ def get_signature(self, signature):
json_script_engine = JsonScriptEngine(json_script)
return json_script_engine.execute(signature)
- return u''
+ return ''
def _load_javascript(self, javascript):
function_name = self._find_signature_function_name(javascript)
diff --git a/resources/lib/youtube_plugin/youtube/helper/subtitles.py b/resources/lib/youtube_plugin/youtube/helper/subtitles.py
index 2286d2c3d..ef2f4ab2b 100644
--- a/resources/lib/youtube_plugin/youtube/helper/subtitles.py
+++ b/resources/lib/youtube_plugin/youtube/helper/subtitles.py
@@ -221,7 +221,8 @@ def _get(self, lang_code='en', language=None, no_asr=False):
base_url = None
if base_url:
- subtitle_url = self._set_query_param(base_url,
+ subtitle_url = self._set_query_param(
+ base_url,
('type', 'track'),
('fmt', 'vtt'),
('tlang', lang_code) if has_translation else (None, None),
diff --git a/resources/lib/youtube_plugin/youtube/helper/utils.py b/resources/lib/youtube_plugin/youtube/helper/utils.py
index 386b4b7a0..e10804369 100644
--- a/resources/lib/youtube_plugin/youtube/helper/utils.py
+++ b/resources/lib/youtube_plugin/youtube/helper/utils.py
@@ -139,8 +139,7 @@ def update_channel_infos(provider, context, channel_id_dict, subscription_id_dic
yt_context_menu.append_subscribe_to_channel(context_menu, provider, context, channel_id)
if context.get_path() == '/subscriptions/list/':
- channel = title.lower()
- channel = channel.replace(',', '')
+ channel = title.lower().replace(',', '')
if channel in filter_list:
yt_context_menu.append_remove_my_subscriptions_filter(context_menu, provider, context, title)
else:
@@ -295,16 +294,15 @@ def update_video_infos(provider, context, video_id_dict, playlist_item_id_dict=N
video_item.set_scheduled_start_utc(datetime)
start_date, start_time = utils.datetime_parser.get_scheduled_start(datetime)
if start_date:
- title = u'({live} {date}@{time}) {title}' \
+ title = '({live} {date}@{time}) {title}' \
.format(live=context.localize(provider.LOCAL_MAP['youtube.live']), date=start_date, time=start_time, title=snippet['title'])
else:
- title = u'({live} @ {time}) {title}' \
- .format(live=context.localize(provider.LOCAL_MAP['youtube.live']), date=start_date, time=start_time, title=snippet['title'])
+ title = '({live} @ {time}) {title}' \
+ .format(live=context.localize(provider.LOCAL_MAP['youtube.live']), time=start_time, title=snippet['title'])
video_item.set_title(title)
- else:
- # set the title
- if not video_item.get_title():
- video_item.set_title(snippet['title'])
+ # set the title
+ elif not video_item.get_title():
+ video_item.set_title(snippet['title'])
"""
This is experimental. We try to get the most information out of the title of a video.
@@ -401,27 +399,25 @@ def update_video_infos(provider, context, video_id_dict, playlist_item_id_dict=N
if playlist_match:
playlist_id = playlist_match.group('playlist_id')
# we support all playlist except 'Watch History'
- if playlist_id:
- if playlist_id != 'HL' and playlist_id.strip().lower() != 'wl':
- playlist_item_id = playlist_item_id_dict[video_id]
- video_item.set_playlist_id(playlist_id)
- video_item.set_playlist_item_id(playlist_item_id)
- context_menu.append((context.localize(provider.LOCAL_MAP['youtube.remove']),
- 'RunPlugin(%s)' % context.create_uri(
- ['playlist', 'remove', 'video'],
- {'playlist_id': playlist_id, 'video_id': playlist_item_id,
- 'video_name': video_item.get_name()})))
+ if playlist_id and playlist_id != 'HL' and playlist_id.strip().lower() != 'wl':
+ playlist_item_id = playlist_item_id_dict[video_id]
+ video_item.set_playlist_id(playlist_id)
+ video_item.set_playlist_item_id(playlist_item_id)
+ context_menu.append((context.localize(provider.LOCAL_MAP['youtube.remove']),
+ 'RunPlugin(%s)' % context.create_uri(
+ ['playlist', 'remove', 'video'],
+ {'playlist_id': playlist_id, 'video_id': playlist_item_id,
+ 'video_name': video_item.get_name()})))
is_history = re.match('^/special/watch_history_tv/$', context.get_path())
if is_history:
yt_context_menu.append_clear_watch_history(context_menu, provider, context)
- # got to [CHANNEL]
- if channel_id and channel_name:
- # only if we are not directly in the channel provide a jump to the channel
- if kodion.utils.create_path('channel', channel_id) != context.get_path():
- video_item.set_channel_id(channel_id)
- yt_context_menu.append_go_to_channel(context_menu, provider, context, channel_id, channel_name)
+ # got to [CHANNEL], only if we are not directly in the channel provide a jump to the channel
+ if (channel_id and channel_name and
+ kodion.utils.create_path('channel', channel_id) != context.get_path()):
+ video_item.set_channel_id(channel_id)
+ yt_context_menu.append_go_to_channel(context_menu, provider, context, channel_id, channel_name)
if provider.is_logged_in():
# subscribe to the channel of the video
@@ -630,10 +626,9 @@ def get_shelf_index_by_title(context, json_data, shelf_title):
context.log_debug('Found shelf index |{index}| for |{title}|'.format(index=str(shelf_index), title=shelf_title))
break
- if shelf_index is not None:
- if 0 > shelf_index >= len(contents):
- context.log_debug('Shelf index |{index}| out of range |0-{content_length}|'.format(index=str(shelf_index), content_length=str(len(contents))))
- shelf_index = None
+ if shelf_index is not None and 0 > shelf_index >= len(contents):
+ context.log_debug('Shelf index |{index}| out of range |0-{content_length}|'.format(index=str(shelf_index), content_length=str(len(contents))))
+ shelf_index = None
return shelf_index
diff --git a/resources/lib/youtube_plugin/youtube/helper/video_info.py b/resources/lib/youtube_plugin/youtube/helper/video_info.py
index e3ac3d70c..4f7621e9a 100644
--- a/resources/lib/youtube_plugin/youtube/helper/video_info.py
+++ b/resources/lib/youtube_plugin/youtube/helper/video_info.py
@@ -2,7 +2,7 @@
"""
Copyright (C) 2014-2016 bromix (plugin.video.youtube)
- Copyright (C) 2016-2018 plugin.video.youtube
+ Copyright (C) 2016-present plugin.video.youtube
SPDX-License-Identifier: GPL-2.0-only
See LICENSES/GPL-2.0-only for more information.
@@ -1068,7 +1068,8 @@ def _normalize_url(url):
url = urljoin('https://www.youtube.com', url)
return url
- def _load_hls_manifest(self, url, live_type=None, meta_info=None, headers=None, playback_stats=None):
+ def _load_hls_manifest(self, url, live_type=None, meta_info=None,
+ headers=None, playback_stats=None):
if not url:
return []
diff --git a/resources/lib/youtube_plugin/youtube/helper/yt_context_menu.py b/resources/lib/youtube_plugin/youtube/helper/yt_context_menu.py
index e1b56666f..941e86e34 100644
--- a/resources/lib/youtube_plugin/youtube/helper/yt_context_menu.py
+++ b/resources/lib/youtube_plugin/youtube/helper/yt_context_menu.py
@@ -120,10 +120,7 @@ def append_add_my_subscriptions_filter(context_menu, provider, context, channel_
def append_rate_video(context_menu, provider, context, video_id, refresh_container=False):
- if refresh_container:
- refresh_container = '1'
- else:
- refresh_container = '0'
+ refresh_container = '1' if refresh_container else '0'
context_menu.append((context.localize(provider.LOCAL_MAP['youtube.video.rate']),
'RunPlugin(%s)' % context.create_uri(['video', 'rate'],
{'video_id': video_id,
@@ -158,7 +155,7 @@ def append_refresh(context_menu, provider, context):
context_menu.append((context.localize(provider.LOCAL_MAP['youtube.refresh']), 'Container.Refresh'))
-def append_subscribe_to_channel(context_menu, provider, context, channel_id, channel_name=u''):
+def append_subscribe_to_channel(context_menu, provider, context, channel_id, channel_name=''):
if channel_name:
text = context.localize(provider.LOCAL_MAP['youtube.subscribe_to']) % context.get_ui().bold(channel_name)
context_menu.append(
diff --git a/resources/lib/youtube_plugin/youtube/helper/yt_login.py b/resources/lib/youtube_plugin/youtube/helper/yt_login.py
index faa621b68..f06b86509 100644
--- a/resources/lib/youtube_plugin/youtube/helper/yt_login.py
+++ b/resources/lib/youtube_plugin/youtube/helper/yt_login.py
@@ -28,12 +28,11 @@ def _do_logout():
refresh_tokens = list(set(refresh_tokens))
for _refresh_token in refresh_tokens:
provider.get_client(context).revoke(_refresh_token)
- else:
- if signout_access_manager.has_refresh_token():
- refresh_tokens = signout_access_manager.get_refresh_token().split('|')
- refresh_tokens = list(set(refresh_tokens))
- for _refresh_token in refresh_tokens:
- provider.get_client(context).revoke(_refresh_token)
+ elif signout_access_manager.has_refresh_token():
+ refresh_tokens = signout_access_manager.get_refresh_token().split('|')
+ refresh_tokens = list(set(refresh_tokens))
+ for _refresh_token in refresh_tokens:
+ provider.get_client(context).revoke(_refresh_token)
provider.reset_client()
@@ -70,7 +69,7 @@ def _do_login(_for_tv=False):
steps = ((10 * 60 * 1000) // interval) # 10 Minutes
dialog.set_total(steps)
- for i in range(steps):
+ for _ in range(steps):
dialog.update()
try:
if _for_tv:
@@ -97,18 +96,18 @@ def _do_login(_for_tv=False):
_expires_in = 0
return _access_token, _expires_in, _refresh_token
- elif json_data['error'] != u'authorization_pending':
+ if json_data['error'] != 'authorization_pending':
message = json_data['error']
title = '%s: %s' % (context.get_name(), message)
context.get_ui().show_notification(message, title)
context.log_error('Error requesting access token: |%s|' % message)
if dialog.is_aborted():
- dialog.close()
- return '', 0, ''
+ break
context.sleep(interval)
dialog.close()
+ return '', 0, ''
if mode == 'out':
_do_logout()
diff --git a/resources/lib/youtube_plugin/youtube/helper/yt_play.py b/resources/lib/youtube_plugin/youtube/helper/yt_play.py
index 9de573632..e62163555 100644
--- a/resources/lib/youtube_plugin/youtube/helper/yt_play.py
+++ b/resources/lib/youtube_plugin/youtube/helper/yt_play.py
@@ -63,8 +63,8 @@ def play_video(provider, context):
if video_stream is None:
return False
- is_video = True if video_stream.get('video') else False
- is_live = video_stream.get('Live') is True
+ is_video = video_stream.get('video')
+ is_live = video_stream.get('Live')
if is_video and video_stream['video'].get('rtmpe', False):
message = context.localize(provider.LOCAL_MAP['youtube.error.rtmpe_not_supported'])
@@ -225,8 +225,8 @@ def _load_videos(_page_token='', _progress_dialog=None):
if (context.get_param('play', '') == '1') and (context.get_handle() == -1):
player.play(playlist_index=playlist_position)
- return
- elif context.get_param('play', '') == '1':
+ return False
+ if context.get_param('play', '') == '1':
return videos[playlist_position]
return True
@@ -257,5 +257,5 @@ def play_channel_live(provider, context):
if context.get_handle() == -1:
player.play(playlist_index=0)
- else:
- return video_item
+ return False
+ return video_item
diff --git a/resources/lib/youtube_plugin/youtube/helper/yt_playlist.py b/resources/lib/youtube_plugin/youtube/helper/yt_playlist.py
index cdf9bbc0d..6d1a32d7d 100644
--- a/resources/lib/youtube_plugin/youtube/helper/yt_playlist.py
+++ b/resources/lib/youtube_plugin/youtube/helper/yt_playlist.py
@@ -55,9 +55,8 @@ def _process_add_video(provider, context, keymap_action=False):
context.get_ui().set_focus_next_item()
return True
- else:
- context.log_debug('Cannot add to playlist id |%s|' % playlist_id)
+ context.log_debug('Cannot add to playlist id |%s|' % playlist_id)
return False
@@ -71,12 +70,14 @@ def _process_remove_video(provider, context):
video_id = context.get_param('video_id', '')
video_name = context.get_param('video_name', '')
- if not playlist_id and not video_id: # keymap support
- if listitem_playlist_id and listitem_playlist_id.startswith('PL') \
- and listitem_playlist_item_id and listitem_playlist_item_id.startswith('UE'):
- playlist_id = listitem_playlist_id
- video_id = listitem_playlist_item_id
- keymap_action = True
+ # keymap support
+ if (not playlist_id and not video_id and listitem_playlist_id
+ and listitem_playlist_id.startswith('PL')
+ and listitem_playlist_item_id
+ and listitem_playlist_item_id.startswith('UE')):
+ playlist_id = listitem_playlist_id
+ video_id = listitem_playlist_item_id
+ keymap_action = True
if not playlist_id:
raise kodion.KodionException('Playlist/Remove: missing playlist_id')
@@ -209,17 +210,16 @@ def _process_select_playlist(provider, context):
new_context = context.clone(new_params=new_params)
_process_add_video(provider, new_context, keymap_action)
break
- elif result == 'playlist.next':
+ if result == 'playlist.next':
continue
- elif result != -1:
+ if result != -1:
new_params = {}
new_params.update(context.get_params())
new_params['playlist_id'] = result
new_context = context.clone(new_params=new_params)
_process_add_video(provider, new_context, keymap_action)
break
- else:
- break
+ break
def _process_rename_playlist(provider, context):
@@ -287,17 +287,16 @@ def _history_playlist_id_change(context, method):
def process(method, category, provider, context):
if method == 'add' and category == 'video':
return _process_add_video(provider, context)
- elif method == 'remove' and category == 'video':
+ if method == 'remove' and category == 'video':
return _process_remove_video(provider, context)
- elif method == 'remove' and category == 'playlist':
+ if method == 'remove' and category == 'playlist':
return _process_remove_playlist(provider, context)
- elif method == 'select' and category == 'playlist':
+ if method == 'select' and category == 'playlist':
return _process_select_playlist(provider, context)
- elif method == 'rename' and category == 'playlist':
+ if method == 'rename' and category == 'playlist':
return _process_rename_playlist(provider, context)
- elif (method == 'set' or method == 'remove') and category == 'watchlater':
+ if method in {'set', 'remove'} and category == 'watchlater':
return _watchlater_playlist_id_change(context, method)
- elif (method == 'set' or method == 'remove') and category == 'history':
+ if method in {'set', 'remove'} and category == 'history':
return _history_playlist_id_change(context, method)
- else:
- raise kodion.KodionException("Unknown category '%s' or method '%s'" % (category, method))
+ raise kodion.KodionException("Unknown category '%s' or method '%s'" % (category, method))
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 9b5bb4b7e..559cba4d2 100644
--- a/resources/lib/youtube_plugin/youtube/helper/yt_setup_wizard.py
+++ b/resources/lib/youtube_plugin/youtube/helper/yt_setup_wizard.py
@@ -11,49 +11,49 @@
from ...kodion.utils import ip_api
-DEFAULT_LANGUAGES = {u'items': [{u'snippet': {u'name': u'Afrikaans', u'hl': u'af'}, u'id': u'af'}, {u'snippet': {u'name': u'Azerbaijani', u'hl': u'az'}, u'id': u'az'}, {u'snippet': {u'name': u'Indonesian', u'hl': u'id'}, u'id': u'id'}, {u'snippet': {u'name': u'Malay', u'hl': u'ms'}, u'id': u'ms'},
- {u'snippet': {u'name': u'Catalan', u'hl': u'ca'}, u'id': u'ca'}, {u'snippet': {u'name': u'Czech', u'hl': u'cs'}, u'id': u'cs'}, {u'snippet': {u'name': u'Danish', u'hl': u'da'}, u'id': u'da'}, {u'snippet': {u'name': u'German', u'hl': u'de'}, u'id': u'de'},
- {u'snippet': {u'name': u'Estonian', u'hl': u'et'}, u'id': u'et'}, {u'snippet': {u'name': u'English (United Kingdom)', u'hl': u'en-GB'}, u'id': u'en-GB'}, {u'snippet': {u'name': u'English', u'hl': u'en'}, u'id': u'en'},
- {u'snippet': {u'name': u'Spanish (Spain)', u'hl': u'es'}, u'id': u'es'}, {u'snippet': {u'name': u'Spanish (Latin America)', u'hl': u'es-419'}, u'id': u'es-419'}, {u'snippet': {u'name': u'Basque', u'hl': u'eu'}, u'id': u'eu'},
- {u'snippet': {u'name': u'Filipino', u'hl': u'fil'}, u'id': u'fil'}, {u'snippet': {u'name': u'French', u'hl': u'fr'}, u'id': u'fr'}, {u'snippet': {u'name': u'French (Canada)', u'hl': u'fr-CA'}, u'id': u'fr-CA'}, {u'snippet': {u'name': u'Galician', u'hl': u'gl'}, u'id': u'gl'},
- {u'snippet': {u'name': u'Croatian', u'hl': u'hr'}, u'id': u'hr'}, {u'snippet': {u'name': u'Zulu', u'hl': u'zu'}, u'id': u'zu'}, {u'snippet': {u'name': u'Icelandic', u'hl': u'is'}, u'id': u'is'}, {u'snippet': {u'name': u'Italian', u'hl': u'it'}, u'id': u'it'},
- {u'snippet': {u'name': u'Swahili', u'hl': u'sw'}, u'id': u'sw'}, {u'snippet': {u'name': u'Latvian', u'hl': u'lv'}, u'id': u'lv'}, {u'snippet': {u'name': u'Lithuanian', u'hl': u'lt'}, u'id': u'lt'}, {u'snippet': {u'name': u'Hungarian', u'hl': u'hu'}, u'id': u'hu'},
- {u'snippet': {u'name': u'Dutch', u'hl': u'nl'}, u'id': u'nl'}, {u'snippet': {u'name': u'Norwegian', u'hl': u'no'}, u'id': u'no'}, {u'snippet': {u'name': u'Uzbek', u'hl': u'uz'}, u'id': u'uz'}, {u'snippet': {u'name': u'Polish', u'hl': u'pl'}, u'id': u'pl'},
- {u'snippet': {u'name': u'Portuguese (Portugal)', u'hl': u'pt-PT'}, u'id': u'pt-PT'}, {u'snippet': {u'name': u'Portuguese (Brazil)', u'hl': u'pt'}, u'id': u'pt'}, {u'snippet': {u'name': u'Romanian', u'hl': u'ro'}, u'id': u'ro'},
- {u'snippet': {u'name': u'Albanian', u'hl': u'sq'}, u'id': u'sq'}, {u'snippet': {u'name': u'Slovak', u'hl': u'sk'}, u'id': u'sk'}, {u'snippet': {u'name': u'Slovenian', u'hl': u'sl'}, u'id': u'sl'}, {u'snippet': {u'name': u'Finnish', u'hl': u'fi'}, u'id': u'fi'},
- {u'snippet': {u'name': u'Swedish', u'hl': u'sv'}, u'id': u'sv'}, {u'snippet': {u'name': u'Vietnamese', u'hl': u'vi'}, u'id': u'vi'}, {u'snippet': {u'name': u'Turkish', u'hl': u'tr'}, u'id': u'tr'}, {u'snippet': {u'name': u'Bulgarian', u'hl': u'bg'}, u'id': u'bg'},
- {u'snippet': {u'name': u'Kyrgyz', u'hl': u'ky'}, u'id': u'ky'}, {u'snippet': {u'name': u'Kazakh', u'hl': u'kk'}, u'id': u'kk'}, {u'snippet': {u'name': u'Macedonian', u'hl': u'mk'}, u'id': u'mk'}, {u'snippet': {u'name': u'Mongolian', u'hl': u'mn'}, u'id': u'mn'},
- {u'snippet': {u'name': u'Russian', u'hl': u'ru'}, u'id': u'ru'}, {u'snippet': {u'name': u'Serbian', u'hl': u'sr'}, u'id': u'sr'}, {u'snippet': {u'name': u'Ukrainian', u'hl': u'uk'}, u'id': u'uk'}, {u'snippet': {u'name': u'Greek', u'hl': u'el'}, u'id': u'el'},
- {u'snippet': {u'name': u'Armenian', u'hl': u'hy'}, u'id': u'hy'}, {u'snippet': {u'name': u'Hebrew', u'hl': u'iw'}, u'id': u'iw'}, {u'snippet': {u'name': u'Urdu', u'hl': u'ur'}, u'id': u'ur'}, {u'snippet': {u'name': u'Arabic', u'hl': u'ar'}, u'id': u'ar'},
- {u'snippet': {u'name': u'Persian', u'hl': u'fa'}, u'id': u'fa'}, {u'snippet': {u'name': u'Nepali', u'hl': u'ne'}, u'id': u'ne'}, {u'snippet': {u'name': u'Marathi', u'hl': u'mr'}, u'id': u'mr'}, {u'snippet': {u'name': u'Hindi', u'hl': u'hi'}, u'id': u'hi'},
- {u'snippet': {u'name': u'Bengali', u'hl': u'bn'}, u'id': u'bn'}, {u'snippet': {u'name': u'Punjabi', u'hl': u'pa'}, u'id': u'pa'}, {u'snippet': {u'name': u'Gujarati', u'hl': u'gu'}, u'id': u'gu'}, {u'snippet': {u'name': u'Tamil', u'hl': u'ta'}, u'id': u'ta'},
- {u'snippet': {u'name': u'Telugu', u'hl': u'te'}, u'id': u'te'}, {u'snippet': {u'name': u'Kannada', u'hl': u'kn'}, u'id': u'kn'}, {u'snippet': {u'name': u'Malayalam', u'hl': u'ml'}, u'id': u'ml'}, {u'snippet': {u'name': u'Sinhala', u'hl': u'si'}, u'id': u'si'},
- {u'snippet': {u'name': u'Thai', u'hl': u'th'}, u'id': u'th'}, {u'snippet': {u'name': u'Lao', u'hl': u'lo'}, u'id': u'lo'}, {u'snippet': {u'name': u'Myanmar (Burmese)', u'hl': u'my'}, u'id': u'my'}, {u'snippet': {u'name': u'Georgian', u'hl': u'ka'}, u'id': u'ka'},
- {u'snippet': {u'name': u'Amharic', u'hl': u'am'}, u'id': u'am'}, {u'snippet': {u'name': u'Khmer', u'hl': u'km'}, u'id': u'km'}, {u'snippet': {u'name': u'Chinese', u'hl': u'zh-CN'}, u'id': u'zh-CN'}, {u'snippet': {u'name': u'Chinese (Taiwan)', u'hl': u'zh-TW'}, u'id': u'zh-TW'},
- {u'snippet': {u'name': u'Chinese (Hong Kong)', u'hl': u'zh-HK'}, u'id': u'zh-HK'}, {u'snippet': {u'name': u'Japanese', u'hl': u'ja'}, u'id': u'ja'}, {u'snippet': {u'name': u'Korean', u'hl': u'ko'}, u'id': u'ko'}]}
-DEFAULT_REGIONS = {u'items': [{u'snippet': {u'gl': u'DZ', u'name': u'Algeria'}, u'id': u'DZ'}, {u'snippet': {u'gl': u'AR', u'name': u'Argentina'}, u'id': u'AR'}, {u'snippet': {u'gl': u'AU', u'name': u'Australia'}, u'id': u'AU'}, {u'snippet': {u'gl': u'AT', u'name': u'Austria'}, u'id': u'AT'},
- {u'snippet': {u'gl': u'AZ', u'name': u'Azerbaijan'}, u'id': u'AZ'}, {u'snippet': {u'gl': u'BH', u'name': u'Bahrain'}, u'id': u'BH'}, {u'snippet': {u'gl': u'BY', u'name': u'Belarus'}, u'id': u'BY'}, {u'snippet': {u'gl': u'BE', u'name': u'Belgium'}, u'id': u'BE'},
- {u'snippet': {u'gl': u'BA', u'name': u'Bosnia and Herzegovina'}, u'id': u'BA'}, {u'snippet': {u'gl': u'BR', u'name': u'Brazil'}, u'id': u'BR'}, {u'snippet': {u'gl': u'BG', u'name': u'Bulgaria'}, u'id': u'BG'}, {u'snippet': {u'gl': u'CA', u'name': u'Canada'}, u'id': u'CA'},
- {u'snippet': {u'gl': u'CL', u'name': u'Chile'}, u'id': u'CL'}, {u'snippet': {u'gl': u'CO', u'name': u'Colombia'}, u'id': u'CO'}, {u'snippet': {u'gl': u'HR', u'name': u'Croatia'}, u'id': u'HR'}, {u'snippet': {u'gl': u'CZ', u'name': u'Czech Republic'}, u'id': u'CZ'},
- {u'snippet': {u'gl': u'DK', u'name': u'Denmark'}, u'id': u'DK'}, {u'snippet': {u'gl': u'EG', u'name': u'Egypt'}, u'id': u'EG'}, {u'snippet': {u'gl': u'EE', u'name': u'Estonia'}, u'id': u'EE'}, {u'snippet': {u'gl': u'FI', u'name': u'Finland'}, u'id': u'FI'},
- {u'snippet': {u'gl': u'FR', u'name': u'France'}, u'id': u'FR'}, {u'snippet': {u'gl': u'GE', u'name': u'Georgia'}, u'id': u'GE'}, {u'snippet': {u'gl': u'DE', u'name': u'Germany'}, u'id': u'DE'}, {u'snippet': {u'gl': u'GH', u'name': u'Ghana'}, u'id': u'GH'},
- {u'snippet': {u'gl': u'GR', u'name': u'Greece'}, u'id': u'GR'}, {u'snippet': {u'gl': u'HK', u'name': u'Hong Kong'}, u'id': u'HK'}, {u'snippet': {u'gl': u'HU', u'name': u'Hungary'}, u'id': u'HU'}, {u'snippet': {u'gl': u'IS', u'name': u'Iceland'}, u'id': u'IS'},
- {u'snippet': {u'gl': u'IN', u'name': u'India'}, u'id': u'IN'}, {u'snippet': {u'gl': u'ID', u'name': u'Indonesia'}, u'id': u'ID'}, {u'snippet': {u'gl': u'IQ', u'name': u'Iraq'}, u'id': u'IQ'}, {u'snippet': {u'gl': u'IE', u'name': u'Ireland'}, u'id': u'IE'},
- {u'snippet': {u'gl': u'IL', u'name': u'Israel'}, u'id': u'IL'}, {u'snippet': {u'gl': u'IT', u'name': u'Italy'}, u'id': u'IT'}, {u'snippet': {u'gl': u'JM', u'name': u'Jamaica'}, u'id': u'JM'}, {u'snippet': {u'gl': u'JP', u'name': u'Japan'}, u'id': u'JP'},
- {u'snippet': {u'gl': u'JO', u'name': u'Jordan'}, u'id': u'JO'}, {u'snippet': {u'gl': u'KZ', u'name': u'Kazakhstan'}, u'id': u'KZ'}, {u'snippet': {u'gl': u'KE', u'name': u'Kenya'}, u'id': u'KE'}, {u'snippet': {u'gl': u'KW', u'name': u'Kuwait'}, u'id': u'KW'},
- {u'snippet': {u'gl': u'LV', u'name': u'Latvia'}, u'id': u'LV'}, {u'snippet': {u'gl': u'LB', u'name': u'Lebanon'}, u'id': u'LB'}, {u'snippet': {u'gl': u'LY', u'name': u'Libya'}, u'id': u'LY'}, {u'snippet': {u'gl': u'LT', u'name': u'Lithuania'}, u'id': u'LT'},
- {u'snippet': {u'gl': u'LU', u'name': u'Luxembourg'}, u'id': u'LU'}, {u'snippet': {u'gl': u'MK', u'name': u'Macedonia'}, u'id': u'MK'}, {u'snippet': {u'gl': u'MY', u'name': u'Malaysia'}, u'id': u'MY'}, {u'snippet': {u'gl': u'MX', u'name': u'Mexico'}, u'id': u'MX'},
- {u'snippet': {u'gl': u'ME', u'name': u'Montenegro'}, u'id': u'ME'}, {u'snippet': {u'gl': u'MA', u'name': u'Morocco'}, u'id': u'MA'}, {u'snippet': {u'gl': u'NP', u'name': u'Nepal'}, u'id': u'NP'}, {u'snippet': {u'gl': u'NL', u'name': u'Netherlands'}, u'id': u'NL'},
- {u'snippet': {u'gl': u'NZ', u'name': u'New Zealand'}, u'id': u'NZ'}, {u'snippet': {u'gl': u'NG', u'name': u'Nigeria'}, u'id': u'NG'}, {u'snippet': {u'gl': u'NO', u'name': u'Norway'}, u'id': u'NO'}, {u'snippet': {u'gl': u'OM', u'name': u'Oman'}, u'id': u'OM'},
- {u'snippet': {u'gl': u'PK', u'name': u'Pakistan'}, u'id': u'PK'}, {u'snippet': {u'gl': u'PE', u'name': u'Peru'}, u'id': u'PE'}, {u'snippet': {u'gl': u'PH', u'name': u'Philippines'}, u'id': u'PH'}, {u'snippet': {u'gl': u'PL', u'name': u'Poland'}, u'id': u'PL'},
- {u'snippet': {u'gl': u'PT', u'name': u'Portugal'}, u'id': u'PT'}, {u'snippet': {u'gl': u'PR', u'name': u'Puerto Rico'}, u'id': u'PR'}, {u'snippet': {u'gl': u'QA', u'name': u'Qatar'}, u'id': u'QA'}, {u'snippet': {u'gl': u'RO', u'name': u'Romania'}, u'id': u'RO'},
- {u'snippet': {u'gl': u'RU', u'name': u'Russia'}, u'id': u'RU'}, {u'snippet': {u'gl': u'SA', u'name': u'Saudi Arabia'}, u'id': u'SA'}, {u'snippet': {u'gl': u'SN', u'name': u'Senegal'}, u'id': u'SN'}, {u'snippet': {u'gl': u'RS', u'name': u'Serbia'}, u'id': u'RS'},
- {u'snippet': {u'gl': u'SG', u'name': u'Singapore'}, u'id': u'SG'}, {u'snippet': {u'gl': u'SK', u'name': u'Slovakia'}, u'id': u'SK'}, {u'snippet': {u'gl': u'SI', u'name': u'Slovenia'}, u'id': u'SI'}, {u'snippet': {u'gl': u'ZA', u'name': u'South Africa'}, u'id': u'ZA'},
- {u'snippet': {u'gl': u'KR', u'name': u'South Korea'}, u'id': u'KR'}, {u'snippet': {u'gl': u'ES', u'name': u'Spain'}, u'id': u'ES'}, {u'snippet': {u'gl': u'LK', u'name': u'Sri Lanka'}, u'id': u'LK'}, {u'snippet': {u'gl': u'SE', u'name': u'Sweden'}, u'id': u'SE'},
- {u'snippet': {u'gl': u'CH', u'name': u'Switzerland'}, u'id': u'CH'}, {u'snippet': {u'gl': u'TW', u'name': u'Taiwan'}, u'id': u'TW'}, {u'snippet': {u'gl': u'TZ', u'name': u'Tanzania'}, u'id': u'TZ'}, {u'snippet': {u'gl': u'TH', u'name': u'Thailand'}, u'id': u'TH'},
- {u'snippet': {u'gl': u'TN', u'name': u'Tunisia'}, u'id': u'TN'}, {u'snippet': {u'gl': u'TR', u'name': u'Turkey'}, u'id': u'TR'}, {u'snippet': {u'gl': u'UG', u'name': u'Uganda'}, u'id': u'UG'}, {u'snippet': {u'gl': u'UA', u'name': u'Ukraine'}, u'id': u'UA'},
- {u'snippet': {u'gl': u'AE', u'name': u'United Arab Emirates'}, u'id': u'AE'}, {u'snippet': {u'gl': u'GB', u'name': u'United Kingdom'}, u'id': u'GB'}, {u'snippet': {u'gl': u'US', u'name': u'United States'}, u'id': u'US'}, {u'snippet': {u'gl': u'VN', u'name': u'Vietnam'}, u'id': u'VN'},
- {u'snippet': {u'gl': u'YE', u'name': u'Yemen'}, u'id': u'YE'}, {u'snippet': {u'gl': u'ZW', u'name': u'Zimbabwe'}, u'id': u'ZW'}]}
+DEFAULT_LANGUAGES = {'items': [{'snippet': {'name': 'Afrikaans', 'hl': 'af'}, 'id': 'af'}, {'snippet': {'name': 'Azerbaijani', 'hl': 'az'}, 'id': 'az'}, {'snippet': {'name': 'Indonesian', 'hl': 'id'}, 'id': 'id'}, {'snippet': {'name': 'Malay', 'hl': 'ms'}, 'id': 'ms'},
+ {'snippet': {'name': 'Catalan', 'hl': 'ca'}, 'id': 'ca'}, {'snippet': {'name': 'Czech', 'hl': 'cs'}, 'id': 'cs'}, {'snippet': {'name': 'Danish', 'hl': 'da'}, 'id': 'da'}, {'snippet': {'name': 'German', 'hl': 'de'}, 'id': 'de'},
+ {'snippet': {'name': 'Estonian', 'hl': 'et'}, 'id': 'et'}, {'snippet': {'name': 'English (United Kingdom)', 'hl': 'en-GB'}, 'id': 'en-GB'}, {'snippet': {'name': 'English', 'hl': 'en'}, 'id': 'en'},
+ {'snippet': {'name': 'Spanish (Spain)', 'hl': 'es'}, 'id': 'es'}, {'snippet': {'name': 'Spanish (Latin America)', 'hl': 'es-419'}, 'id': 'es-419'}, {'snippet': {'name': 'Basque', 'hl': 'eu'}, 'id': 'eu'},
+ {'snippet': {'name': 'Filipino', 'hl': 'fil'}, 'id': 'fil'}, {'snippet': {'name': 'French', 'hl': 'fr'}, 'id': 'fr'}, {'snippet': {'name': 'French (Canada)', 'hl': 'fr-CA'}, 'id': 'fr-CA'}, {'snippet': {'name': 'Galician', 'hl': 'gl'}, 'id': 'gl'},
+ {'snippet': {'name': 'Croatian', 'hl': 'hr'}, 'id': 'hr'}, {'snippet': {'name': 'Zulu', 'hl': 'zu'}, 'id': 'zu'}, {'snippet': {'name': 'Icelandic', 'hl': 'is'}, 'id': 'is'}, {'snippet': {'name': 'Italian', 'hl': 'it'}, 'id': 'it'},
+ {'snippet': {'name': 'Swahili', 'hl': 'sw'}, 'id': 'sw'}, {'snippet': {'name': 'Latvian', 'hl': 'lv'}, 'id': 'lv'}, {'snippet': {'name': 'Lithuanian', 'hl': 'lt'}, 'id': 'lt'}, {'snippet': {'name': 'Hungarian', 'hl': 'hu'}, 'id': 'hu'},
+ {'snippet': {'name': 'Dutch', 'hl': 'nl'}, 'id': 'nl'}, {'snippet': {'name': 'Norwegian', 'hl': 'no'}, 'id': 'no'}, {'snippet': {'name': 'Uzbek', 'hl': 'uz'}, 'id': 'uz'}, {'snippet': {'name': 'Polish', 'hl': 'pl'}, 'id': 'pl'},
+ {'snippet': {'name': 'Portuguese (Portugal)', 'hl': 'pt-PT'}, 'id': 'pt-PT'}, {'snippet': {'name': 'Portuguese (Brazil)', 'hl': 'pt'}, 'id': 'pt'}, {'snippet': {'name': 'Romanian', 'hl': 'ro'}, 'id': 'ro'},
+ {'snippet': {'name': 'Albanian', 'hl': 'sq'}, 'id': 'sq'}, {'snippet': {'name': 'Slovak', 'hl': 'sk'}, 'id': 'sk'}, {'snippet': {'name': 'Slovenian', 'hl': 'sl'}, 'id': 'sl'}, {'snippet': {'name': 'Finnish', 'hl': 'fi'}, 'id': 'fi'},
+ {'snippet': {'name': 'Swedish', 'hl': 'sv'}, 'id': 'sv'}, {'snippet': {'name': 'Vietnamese', 'hl': 'vi'}, 'id': 'vi'}, {'snippet': {'name': 'Turkish', 'hl': 'tr'}, 'id': 'tr'}, {'snippet': {'name': 'Bulgarian', 'hl': 'bg'}, 'id': 'bg'},
+ {'snippet': {'name': 'Kyrgyz', 'hl': 'ky'}, 'id': 'ky'}, {'snippet': {'name': 'Kazakh', 'hl': 'kk'}, 'id': 'kk'}, {'snippet': {'name': 'Macedonian', 'hl': 'mk'}, 'id': 'mk'}, {'snippet': {'name': 'Mongolian', 'hl': 'mn'}, 'id': 'mn'},
+ {'snippet': {'name': 'Russian', 'hl': 'ru'}, 'id': 'ru'}, {'snippet': {'name': 'Serbian', 'hl': 'sr'}, 'id': 'sr'}, {'snippet': {'name': 'Ukrainian', 'hl': 'uk'}, 'id': 'uk'}, {'snippet': {'name': 'Greek', 'hl': 'el'}, 'id': 'el'},
+ {'snippet': {'name': 'Armenian', 'hl': 'hy'}, 'id': 'hy'}, {'snippet': {'name': 'Hebrew', 'hl': 'iw'}, 'id': 'iw'}, {'snippet': {'name': 'Urdu', 'hl': 'ur'}, 'id': 'ur'}, {'snippet': {'name': 'Arabic', 'hl': 'ar'}, 'id': 'ar'},
+ {'snippet': {'name': 'Persian', 'hl': 'fa'}, 'id': 'fa'}, {'snippet': {'name': 'Nepali', 'hl': 'ne'}, 'id': 'ne'}, {'snippet': {'name': 'Marathi', 'hl': 'mr'}, 'id': 'mr'}, {'snippet': {'name': 'Hindi', 'hl': 'hi'}, 'id': 'hi'},
+ {'snippet': {'name': 'Bengali', 'hl': 'bn'}, 'id': 'bn'}, {'snippet': {'name': 'Punjabi', 'hl': 'pa'}, 'id': 'pa'}, {'snippet': {'name': 'Gujarati', 'hl': 'gu'}, 'id': 'gu'}, {'snippet': {'name': 'Tamil', 'hl': 'ta'}, 'id': 'ta'},
+ {'snippet': {'name': 'Telugu', 'hl': 'te'}, 'id': 'te'}, {'snippet': {'name': 'Kannada', 'hl': 'kn'}, 'id': 'kn'}, {'snippet': {'name': 'Malayalam', 'hl': 'ml'}, 'id': 'ml'}, {'snippet': {'name': 'Sinhala', 'hl': 'si'}, 'id': 'si'},
+ {'snippet': {'name': 'Thai', 'hl': 'th'}, 'id': 'th'}, {'snippet': {'name': 'Lao', 'hl': 'lo'}, 'id': 'lo'}, {'snippet': {'name': 'Myanmar (Burmese)', 'hl': 'my'}, 'id': 'my'}, {'snippet': {'name': 'Georgian', 'hl': 'ka'}, 'id': 'ka'},
+ {'snippet': {'name': 'Amharic', 'hl': 'am'}, 'id': 'am'}, {'snippet': {'name': 'Khmer', 'hl': 'km'}, 'id': 'km'}, {'snippet': {'name': 'Chinese', 'hl': 'zh-CN'}, 'id': 'zh-CN'}, {'snippet': {'name': 'Chinese (Taiwan)', 'hl': 'zh-TW'}, 'id': 'zh-TW'},
+ {'snippet': {'name': 'Chinese (Hong Kong)', 'hl': 'zh-HK'}, 'id': 'zh-HK'}, {'snippet': {'name': 'Japanese', 'hl': 'ja'}, 'id': 'ja'}, {'snippet': {'name': 'Korean', 'hl': 'ko'}, 'id': 'ko'}]}
+DEFAULT_REGIONS = {'items': [{'snippet': {'gl': 'DZ', 'name': 'Algeria'}, 'id': 'DZ'}, {'snippet': {'gl': 'AR', 'name': 'Argentina'}, 'id': 'AR'}, {'snippet': {'gl': 'AU', 'name': 'Australia'}, 'id': 'AU'}, {'snippet': {'gl': 'AT', 'name': 'Austria'}, 'id': 'AT'},
+ {'snippet': {'gl': 'AZ', 'name': 'Azerbaijan'}, 'id': 'AZ'}, {'snippet': {'gl': 'BH', 'name': 'Bahrain'}, 'id': 'BH'}, {'snippet': {'gl': 'BY', 'name': 'Belarus'}, 'id': 'BY'}, {'snippet': {'gl': 'BE', 'name': 'Belgium'}, 'id': 'BE'},
+ {'snippet': {'gl': 'BA', 'name': 'Bosnia and Herzegovina'}, 'id': 'BA'}, {'snippet': {'gl': 'BR', 'name': 'Brazil'}, 'id': 'BR'}, {'snippet': {'gl': 'BG', 'name': 'Bulgaria'}, 'id': 'BG'}, {'snippet': {'gl': 'CA', 'name': 'Canada'}, 'id': 'CA'},
+ {'snippet': {'gl': 'CL', 'name': 'Chile'}, 'id': 'CL'}, {'snippet': {'gl': 'CO', 'name': 'Colombia'}, 'id': 'CO'}, {'snippet': {'gl': 'HR', 'name': 'Croatia'}, 'id': 'HR'}, {'snippet': {'gl': 'CZ', 'name': 'Czech Republic'}, 'id': 'CZ'},
+ {'snippet': {'gl': 'DK', 'name': 'Denmark'}, 'id': 'DK'}, {'snippet': {'gl': 'EG', 'name': 'Egypt'}, 'id': 'EG'}, {'snippet': {'gl': 'EE', 'name': 'Estonia'}, 'id': 'EE'}, {'snippet': {'gl': 'FI', 'name': 'Finland'}, 'id': 'FI'},
+ {'snippet': {'gl': 'FR', 'name': 'France'}, 'id': 'FR'}, {'snippet': {'gl': 'GE', 'name': 'Georgia'}, 'id': 'GE'}, {'snippet': {'gl': 'DE', 'name': 'Germany'}, 'id': 'DE'}, {'snippet': {'gl': 'GH', 'name': 'Ghana'}, 'id': 'GH'},
+ {'snippet': {'gl': 'GR', 'name': 'Greece'}, 'id': 'GR'}, {'snippet': {'gl': 'HK', 'name': 'Hong Kong'}, 'id': 'HK'}, {'snippet': {'gl': 'HU', 'name': 'Hungary'}, 'id': 'HU'}, {'snippet': {'gl': 'IS', 'name': 'Iceland'}, 'id': 'IS'},
+ {'snippet': {'gl': 'IN', 'name': 'India'}, 'id': 'IN'}, {'snippet': {'gl': 'ID', 'name': 'Indonesia'}, 'id': 'ID'}, {'snippet': {'gl': 'IQ', 'name': 'Iraq'}, 'id': 'IQ'}, {'snippet': {'gl': 'IE', 'name': 'Ireland'}, 'id': 'IE'},
+ {'snippet': {'gl': 'IL', 'name': 'Israel'}, 'id': 'IL'}, {'snippet': {'gl': 'IT', 'name': 'Italy'}, 'id': 'IT'}, {'snippet': {'gl': 'JM', 'name': 'Jamaica'}, 'id': 'JM'}, {'snippet': {'gl': 'JP', 'name': 'Japan'}, 'id': 'JP'},
+ {'snippet': {'gl': 'JO', 'name': 'Jordan'}, 'id': 'JO'}, {'snippet': {'gl': 'KZ', 'name': 'Kazakhstan'}, 'id': 'KZ'}, {'snippet': {'gl': 'KE', 'name': 'Kenya'}, 'id': 'KE'}, {'snippet': {'gl': 'KW', 'name': 'Kuwait'}, 'id': 'KW'},
+ {'snippet': {'gl': 'LV', 'name': 'Latvia'}, 'id': 'LV'}, {'snippet': {'gl': 'LB', 'name': 'Lebanon'}, 'id': 'LB'}, {'snippet': {'gl': 'LY', 'name': 'Libya'}, 'id': 'LY'}, {'snippet': {'gl': 'LT', 'name': 'Lithuania'}, 'id': 'LT'},
+ {'snippet': {'gl': 'LU', 'name': 'Luxembourg'}, 'id': 'LU'}, {'snippet': {'gl': 'MK', 'name': 'Macedonia'}, 'id': 'MK'}, {'snippet': {'gl': 'MY', 'name': 'Malaysia'}, 'id': 'MY'}, {'snippet': {'gl': 'MX', 'name': 'Mexico'}, 'id': 'MX'},
+ {'snippet': {'gl': 'ME', 'name': 'Montenegro'}, 'id': 'ME'}, {'snippet': {'gl': 'MA', 'name': 'Morocco'}, 'id': 'MA'}, {'snippet': {'gl': 'NP', 'name': 'Nepal'}, 'id': 'NP'}, {'snippet': {'gl': 'NL', 'name': 'Netherlands'}, 'id': 'NL'},
+ {'snippet': {'gl': 'NZ', 'name': 'New Zealand'}, 'id': 'NZ'}, {'snippet': {'gl': 'NG', 'name': 'Nigeria'}, 'id': 'NG'}, {'snippet': {'gl': 'NO', 'name': 'Norway'}, 'id': 'NO'}, {'snippet': {'gl': 'OM', 'name': 'Oman'}, 'id': 'OM'},
+ {'snippet': {'gl': 'PK', 'name': 'Pakistan'}, 'id': 'PK'}, {'snippet': {'gl': 'PE', 'name': 'Peru'}, 'id': 'PE'}, {'snippet': {'gl': 'PH', 'name': 'Philippines'}, 'id': 'PH'}, {'snippet': {'gl': 'PL', 'name': 'Poland'}, 'id': 'PL'},
+ {'snippet': {'gl': 'PT', 'name': 'Portugal'}, 'id': 'PT'}, {'snippet': {'gl': 'PR', 'name': 'Puerto Rico'}, 'id': 'PR'}, {'snippet': {'gl': 'QA', 'name': 'Qatar'}, 'id': 'QA'}, {'snippet': {'gl': 'RO', 'name': 'Romania'}, 'id': 'RO'},
+ {'snippet': {'gl': 'RU', 'name': 'Russia'}, 'id': 'RU'}, {'snippet': {'gl': 'SA', 'name': 'Saudi Arabia'}, 'id': 'SA'}, {'snippet': {'gl': 'SN', 'name': 'Senegal'}, 'id': 'SN'}, {'snippet': {'gl': 'RS', 'name': 'Serbia'}, 'id': 'RS'},
+ {'snippet': {'gl': 'SG', 'name': 'Singapore'}, 'id': 'SG'}, {'snippet': {'gl': 'SK', 'name': 'Slovakia'}, 'id': 'SK'}, {'snippet': {'gl': 'SI', 'name': 'Slovenia'}, 'id': 'SI'}, {'snippet': {'gl': 'ZA', 'name': 'South Africa'}, 'id': 'ZA'},
+ {'snippet': {'gl': 'KR', 'name': 'South Korea'}, 'id': 'KR'}, {'snippet': {'gl': 'ES', 'name': 'Spain'}, 'id': 'ES'}, {'snippet': {'gl': 'LK', 'name': 'Sri Lanka'}, 'id': 'LK'}, {'snippet': {'gl': 'SE', 'name': 'Sweden'}, 'id': 'SE'},
+ {'snippet': {'gl': 'CH', 'name': 'Switzerland'}, 'id': 'CH'}, {'snippet': {'gl': 'TW', 'name': 'Taiwan'}, 'id': 'TW'}, {'snippet': {'gl': 'TZ', 'name': 'Tanzania'}, 'id': 'TZ'}, {'snippet': {'gl': 'TH', 'name': 'Thailand'}, 'id': 'TH'},
+ {'snippet': {'gl': 'TN', 'name': 'Tunisia'}, 'id': 'TN'}, {'snippet': {'gl': 'TR', 'name': 'Turkey'}, 'id': 'TR'}, {'snippet': {'gl': 'UG', 'name': 'Uganda'}, 'id': 'UG'}, {'snippet': {'gl': 'UA', 'name': 'Ukraine'}, 'id': 'UA'},
+ {'snippet': {'gl': 'AE', 'name': 'United Arab Emirates'}, 'id': 'AE'}, {'snippet': {'gl': 'GB', 'name': 'United Kingdom'}, 'id': 'GB'}, {'snippet': {'gl': 'US', 'name': 'United States'}, 'id': 'US'}, {'snippet': {'gl': 'VN', 'name': 'Vietnam'}, 'id': 'VN'},
+ {'snippet': {'gl': 'YE', 'name': 'Yemen'}, 'id': 'YE'}, {'snippet': {'gl': 'ZW', 'name': 'Zimbabwe'}, 'id': 'ZW'}]}
def _process_language(provider, context):
@@ -70,7 +70,7 @@ def _process_language(provider, context):
else:
items = json_data['items']
language_list = []
- invalid_ids = [u'es-419'] # causes hl not a valid language error. Issue #418
+ invalid_ids = ['es-419'] # causes hl not a valid language error. Issue #418
for item in items:
if item['id'] in invalid_ids:
continue
diff --git a/resources/lib/youtube_plugin/youtube/helper/yt_specials.py b/resources/lib/youtube_plugin/youtube/helper/yt_specials.py
index 89ad2b119..c7b5c3fe8 100644
--- a/resources/lib/youtube_plugin/youtube/helper/yt_specials.py
+++ b/resources/lib/youtube_plugin/youtube/helper/yt_specials.py
@@ -334,31 +334,30 @@ def process(category, provider, context):
if category == 'related_videos':
return _process_related_videos(provider, context)
- elif category == 'popular_right_now':
+ if category == 'popular_right_now':
return _process_popular_right_now(provider, context)
- elif category == 'recommendations':
+ if category == 'recommendations':
return _process_recommendations(provider, context)
- elif category == 'browse_channels':
+ if category == 'browse_channels':
return _process_browse_channels(provider, context)
- elif category == 'new_uploaded_videos_tv':
+ if category == 'new_uploaded_videos_tv':
return _process_new_uploaded_videos_tv(provider, context)
- elif category == 'new_uploaded_videos_tv_filtered':
+ if category == 'new_uploaded_videos_tv_filtered':
return _process_new_uploaded_videos_tv_filtered(provider, context)
- elif category == 'disliked_videos':
+ if category == 'disliked_videos':
return _process_disliked_videos(provider, context)
- elif category == 'live':
+ if category == 'live':
return _process_live_events(provider, context)
- elif category == 'upcoming_live':
+ if category == 'upcoming_live':
return _process_live_events(provider, context, event_type='upcoming')
- elif category == 'completed_live':
+ if category == 'completed_live':
return _process_live_events(provider, context, event_type='completed')
- elif category == 'description_links':
+ if category == 'description_links':
return _process_description_links(provider, context)
- elif category == 'parent_comments':
+ if category == 'parent_comments':
return _process_parent_comments(provider, context)
- elif category == 'child_comments':
+ if category == 'child_comments':
return _process_child_comments(provider, context)
- elif category == 'saved_playlists':
+ if category == 'saved_playlists':
return _process_saved_playlists_tv(provider, context)
- else:
- raise kodion.KodionException("YouTube special category '%s' not found" % category)
+ raise kodion.KodionException("YouTube special category '%s' not found" % category)
diff --git a/resources/lib/youtube_plugin/youtube/helper/yt_subscriptions.py b/resources/lib/youtube_plugin/youtube/helper/yt_subscriptions.py
index 9dd98b0a5..712308492 100644
--- a/resources/lib/youtube_plugin/youtube/helper/yt_subscriptions.py
+++ b/resources/lib/youtube_plugin/youtube/helper/yt_subscriptions.py
@@ -30,9 +30,9 @@ def _process_add(provider, context):
listitem_subscription_id = context.get_ui().get_info_label('Container.ListItem(0).Property(subscription_id)')
subscription_id = context.get_param('subscription_id', '')
- if not subscription_id:
- if listitem_subscription_id and listitem_subscription_id.lower().startswith('uc'):
- subscription_id = listitem_subscription_id
+ if (not subscription_id and listitem_subscription_id
+ and listitem_subscription_id.lower().startswith('uc')):
+ subscription_id = listitem_subscription_id
if subscription_id:
json_data = provider.get_client(context).subscribe(subscription_id)
diff --git a/resources/lib/youtube_plugin/youtube/helper/yt_video.py b/resources/lib/youtube_plugin/youtube/helper/yt_video.py
index 7d900ea4d..16cece658 100644
--- a/resources/lib/youtube_plugin/youtube/helper/yt_video.py
+++ b/resources/lib/youtube_plugin/youtube/helper/yt_video.py
@@ -52,11 +52,10 @@ def _process_rate_video(provider, context, re_match):
if rating != current_rating:
rating_items.append((context.localize(provider.LOCAL_MAP['youtube.video.rate.%s' % rating]), rating))
result = context.get_ui().on_select(context.localize(provider.LOCAL_MAP['youtube.video.rate']), rating_items)
+ elif rating_param != current_rating:
+ result = rating_param
else:
- if rating_param != current_rating:
- result = rating_param
- else:
- result = -1
+ result = -1
if result != -1:
notify_message = ''
@@ -85,6 +84,8 @@ def _process_rate_video(provider, context, re_match):
audible=False
)
+ return True
+
def _process_more_for_video(provider, context):
video_id = context.get_param('video_id', '')
@@ -124,7 +125,6 @@ def _process_more_for_video(provider, context):
def process(method, provider, context, re_match):
if method == 'rate':
return _process_rate_video(provider, context, re_match)
- elif method == 'more':
+ if method == 'more':
return _process_more_for_video(provider, context)
- else:
- raise kodion.KodionException("Unknown method '%s'" % method)
+ raise kodion.KodionException("Unknown method '%s'" % method)
diff --git a/resources/lib/youtube_plugin/youtube/provider.py b/resources/lib/youtube_plugin/youtube/provider.py
index ab284c4e3..1982a4777 100644
--- a/resources/lib/youtube_plugin/youtube/provider.py
+++ b/resources/lib/youtube_plugin/youtube/provider.py
@@ -30,6 +30,7 @@
import xbmcgui
import xbmcplugin
+
try:
xbmc.translatePath = xbmcvfs.translatePath
except AttributeError:
@@ -220,29 +221,29 @@ def get_dev_config(context, addon_id, dev_configs):
if dev_config and not context.get_settings().allow_dev_keys():
context.log_debug('Developer config ignored')
return None
- elif dev_config:
+
+ if dev_config:
if not dev_config.get('main') or not dev_config['main'].get('key') \
or not dev_config['main'].get('system') or not dev_config.get('origin') \
or not dev_config['main'].get('id') or not dev_config['main'].get('secret'):
context.log_error('Error loading developer config: |invalid structure| '
'expected: |{"origin": ADDON_ID, "main": {"system": SYSTEM_NAME, "key": API_KEY, "id": CLIENT_ID, "secret": CLIENT_SECRET}}|')
return dict()
+ dev_origin = dev_config['origin']
+ dev_main = dev_config['main']
+ dev_system = dev_main['system']
+ if dev_system == 'JSONStore':
+ dev_key = b64decode(dev_main['key'])
+ dev_id = b64decode(dev_main['id'])
+ dev_secret = b64decode(dev_main['secret'])
else:
- dev_origin = dev_config['origin']
- dev_main = dev_config['main']
- dev_system = dev_main['system']
- if dev_system == 'JSONStore':
- dev_key = b64decode(dev_main['key'])
- dev_id = b64decode(dev_main['id'])
- dev_secret = b64decode(dev_main['secret'])
- else:
- dev_key = dev_main['key']
- dev_id = dev_main['id']
- dev_secret = dev_main['secret']
- context.log_debug('Using developer config: origin: |{0}| system |{1}|'.format(dev_origin, dev_system))
- return {'origin': dev_origin, 'main': {'id': dev_id, 'secret': dev_secret, 'key': dev_key, 'system': dev_system}}
- else:
- return dict()
+ dev_key = dev_main['key']
+ dev_id = dev_main['id']
+ dev_secret = dev_main['secret']
+ context.log_debug('Using developer config: origin: |{0}| system |{1}|'.format(dev_origin, dev_system))
+ return {'origin': dev_origin, 'main': {'id': dev_id, 'secret': dev_secret, 'key': dev_key, 'system': dev_system}}
+
+ return dict()
def reset_client(self):
self._client = None
@@ -279,11 +280,10 @@ def get_client(self, context):
context.log_debug('API key origin changed, clearing cache. |%s|' % dev_origin)
access_manager.set_last_origin(dev_origin)
self.get_resource_manager(context).clear()
- else:
- if api_last_origin != 'plugin.video.youtube':
- context.log_debug('API key origin changed, clearing cache. |plugin.video.youtube|')
- access_manager.set_last_origin('plugin.video.youtube')
- self.get_resource_manager(context).clear()
+ elif api_last_origin != 'plugin.video.youtube':
+ context.log_debug('API key origin changed, clearing cache. |plugin.video.youtube|')
+ access_manager.set_last_origin('plugin.video.youtube')
+ self.get_resource_manager(context).clear()
if dev_id:
access_tokens = access_manager.get_dev_access_token(dev_id).split('|')
@@ -375,20 +375,17 @@ def get_client(self, context):
access_manager.update_dev_access_token(dev_id, access_token='', refresh_token='')
else:
access_manager.update_access_token(access_token='', refresh_token='')
+ elif dev_id:
+ access_manager.update_dev_access_token(dev_id, '')
else:
- if dev_id:
- access_manager.update_dev_access_token(dev_id, '')
- else:
- access_manager.update_access_token('')
+ access_manager.update_access_token('')
# we clear the cache, so none cached data of an old account will be displayed.
self.get_resource_manager(context).clear()
# in debug log the login status
self._is_logged_in = len(access_tokens) == 2
- if self._is_logged_in:
- context.log_debug('User is logged in')
- else:
- context.log_debug('User is not logged in')
+ context.log_debug('User is logged in' if self._is_logged_in else
+ 'User is not logged in')
if len(access_tokens) == 0:
access_tokens = ['', '']
@@ -553,9 +550,9 @@ def _on_channel(self, context, re_match):
method = re_match.group('method')
channel_id = re_match.group('channel_id')
- if method == 'channel' and channel_id and channel_id.lower() == 'property':
- if listitem_channel_id and listitem_channel_id.lower().startswith(('mine', 'uc')):
- context.execute('Container.Update(%s)' % context.create_uri(['channel', listitem_channel_id])) # redirect if keymap, without redirect results in 'invalid handle -1'
+ if (method == 'channel' and channel_id and channel_id.lower() == 'property'
+ and listitem_channel_id and listitem_channel_id.lower().startswith(('mine', 'uc'))):
+ context.execute('Container.Update(%s)' % context.create_uri(['channel', listitem_channel_id])) # redirect if keymap, without redirect results in 'invalid handle -1'
if method == 'channel' and not channel_id:
return False
@@ -757,22 +754,21 @@ def on_play(self, context, re_match):
if not redirect:
context.log_debug('Redirecting playback, handle is -1')
context.execute(builtin % context.create_uri(['play'], {'video_id': params['video_id']}))
- return
+ return False
if 'playlist_id' in params and (context.get_handle() != -1):
builtin = 'RunPlugin(%s)'
stream_url = context.create_uri(['play'], params)
xbmcplugin.setResolvedUrl(handle=context.get_handle(), succeeded=False, listitem=xbmcgui.ListItem(path=stream_url))
context.execute(builtin % context.create_uri(['play'], params))
- return
+ return False
if 'video_id' in params and 'playlist_id' not in params:
return yt_play.play_video(self, context)
- elif 'playlist_id' in params:
+ if 'playlist_id' in params:
return yt_play.play_playlist(self, context)
- elif 'channel_id' in params and 'live' in params:
- if int(params['live']) > 0:
- return yt_play.play_channel_live(self, context)
+ if 'channel_id' in params and 'live' in params and int(params['live']) > 0:
+ return yt_play.play_channel_live(self, context)
return False
@kodion.RegisterProviderPath('^/video/(?P[^/]+)/$')
@@ -869,7 +865,7 @@ def switch_to_user(_user):
result = ui.on_select(context.localize(self.LOCAL_MAP['youtube.switch.user']), users)
if result == -1:
return True
- elif result == 0:
+ if result == 0:
user = add_user(access_manager_users)
else:
user = user_index_map[result - 1]
@@ -975,11 +971,13 @@ def _on_sign(self, context, re_match):
if (mode == 'in') and context.get_access_manager().has_refresh_token():
yt_login.process('out', self, context, sign_out_refresh=False)
- if not sign_out_confirmed:
- if (mode == 'out') and context.get_ui().on_yes_no_input(context.localize(self.LOCAL_MAP['youtube.sign.out']), context.localize(self.LOCAL_MAP['youtube.are.you.sure'])):
- sign_out_confirmed = True
+ if (not sign_out_confirmed and mode == 'out'
+ and context.get_ui().on_yes_no_input(
+ context.localize(self.LOCAL_MAP['youtube.sign.out']),
+ context.localize(self.LOCAL_MAP['youtube.are.you.sure']))):
+ sign_out_confirmed = True
- if (mode == 'in') or ((mode == 'out') and sign_out_confirmed):
+ if mode == 'in' or (mode == 'out' and sign_out_confirmed):
yt_login.process(mode, self, context)
return False
@@ -1111,12 +1109,9 @@ def configure_addon(self, context, re_match):
local_ranges = ('10.', '172.16.', '192.168.')
addresses = [iface[4][0] for iface in socket.getaddrinfo(socket.gethostname(), None) if iface[4][0].startswith(local_ranges)] + ['127.0.0.1', '0.0.0.0']
selected_address = context.get_ui().on_select(context.localize(self.LOCAL_MAP['youtube.select.listen.ip']), addresses)
- if selected_address == -1:
- return False
- else:
+ if selected_address != -1:
context.get_settings().set_httpd_listen(addresses[selected_address])
- else:
- return False
+ return False
# noinspection PyUnusedLocal
@kodion.RegisterProviderPath('^/my_subscriptions/filter/$')
@@ -1142,9 +1137,8 @@ def manage_my_subscription_filter(self, context, re_match):
if action == 'add':
if channel_name not in filter_list:
filter_list.append(channel_name)
- elif action == 'remove':
- if channel_name in filter_list:
- filter_list = [chan_name for chan_name in filter_list if chan_name != channel_name]
+ elif action == 'remove' and channel_name in filter_list:
+ filter_list = [chan_name for chan_name in filter_list if chan_name != channel_name]
modified_string = ','.join(filter_list).lstrip(',')
if filter_string != modified_string:
@@ -1180,26 +1174,25 @@ def maintenance_actions(self, context, re_match):
context.get_playback_history().clear()
context.get_ui().show_notification(context.localize(self.LOCAL_MAP['youtube.succeeded']))
elif action == 'reset':
- if maint_type == 'access_manager':
- if context.get_ui().on_yes_no_input(context.get_name(), context.localize(self.LOCAL_MAP['youtube.reset.access.manager.confirm'])):
- try:
- context.get_function_cache().clear()
- access_manager = context.get_access_manager()
- client = self.get_client(context)
- if access_manager.has_refresh_token():
- refresh_tokens = access_manager.get_refresh_token().split('|')
- refresh_tokens = list(set(refresh_tokens))
- for refresh_token in refresh_tokens:
- try:
- client.revoke(refresh_token)
- except:
- pass
- self.reset_client()
- access_manager.update_access_token(access_token='', refresh_token='')
- context.get_ui().refresh_container()
- context.get_ui().show_notification(context.localize(self.LOCAL_MAP['youtube.succeeded']))
- except:
- context.get_ui().show_notification(context.localize(self.LOCAL_MAP['youtube.failed']))
+ if maint_type == 'access_manager' and context.get_ui().on_yes_no_input(context.get_name(), context.localize(self.LOCAL_MAP['youtube.reset.access.manager.confirm'])):
+ try:
+ context.get_function_cache().clear()
+ access_manager = context.get_access_manager()
+ client = self.get_client(context)
+ if access_manager.has_refresh_token():
+ refresh_tokens = access_manager.get_refresh_token().split('|')
+ refresh_tokens = list(set(refresh_tokens))
+ for refresh_token in refresh_tokens:
+ try:
+ client.revoke(refresh_token)
+ except:
+ pass
+ self.reset_client()
+ access_manager.update_access_token(access_token='', refresh_token='')
+ context.get_ui().refresh_container()
+ context.get_ui().show_notification(context.localize(self.LOCAL_MAP['youtube.succeeded']))
+ except:
+ context.get_ui().show_notification(context.localize(self.LOCAL_MAP['youtube.failed']))
elif action == 'delete':
_maint_files = {'function_cache': 'cache.sqlite',
'search_cache': 'search.sqlite',
@@ -1240,16 +1233,15 @@ def maintenance_actions(self, context, re_match):
context.get_ui().show_notification(context.localize(self.LOCAL_MAP['youtube.succeeded']))
else:
context.get_ui().show_notification(context.localize(self.LOCAL_MAP['youtube.failed']))
- elif action == 'install':
- if maint_type == 'inputstreamhelper':
- if context.get_system_version().get_version()[0] >= 17:
- try:
- xbmcaddon.Addon('script.module.inputstreamhelper')
- context.get_ui().show_notification(context.localize(self.LOCAL_MAP['youtube.inputstreamhelper.is.installed']))
- except RuntimeError:
- context.execute('InstallAddon(script.module.inputstreamhelper)')
- else:
- context.get_ui().show_notification(context.localize(self.LOCAL_MAP['youtube.requires.krypton']))
+ elif action == 'install' and maint_type == 'inputstreamhelper':
+ if context.get_system_version().get_version()[0] >= 17:
+ try:
+ xbmcaddon.Addon('script.module.inputstreamhelper')
+ context.get_ui().show_notification(context.localize(self.LOCAL_MAP['youtube.inputstreamhelper.is.installed']))
+ except RuntimeError:
+ context.execute('InstallAddon(script.module.inputstreamhelper)')
+ else:
+ context.get_ui().show_notification(context.localize(self.LOCAL_MAP['youtube.requires.krypton']))
# noinspection PyUnusedLocal
@kodion.RegisterProviderPath('^/api/update/$')
diff --git a/resources/lib/youtube_requests.py b/resources/lib/youtube_requests.py
index 5f8c3831a..0a4217592 100644
--- a/resources/lib/youtube_requests.py
+++ b/resources/lib/youtube_requests.py
@@ -107,17 +107,22 @@ def get_items(_page_token=''):
for item in json_data.get('items', []):
items.append(item)
+ error = False
next_page_token = json_data.get('nextPageToken')
- if all_pages and (next_page_token is not None):
- get_items(_page_token=next_page_token)
- elif next_page_token is not None:
+ if not next_page_token:
+ return error
+ if all_pages:
+ error = get_items(_page_token=next_page_token)
+ else:
items.append({'nextPageToken': next_page_token})
+ return error
- get_items(_page_token=page_token)
+ error = get_items(_page_token=page_token)
+ if error:
+ return error
items = _append_missing_page_token(items)
-
return items
@@ -148,17 +153,22 @@ def get_items(_page_token=''):
for item in json_data.get('items', []):
items.append(item)
+ error = False
next_page_token = json_data.get('nextPageToken')
- if all_pages and (next_page_token is not None):
- get_items(_page_token=next_page_token)
- elif next_page_token is not None:
+ if not next_page_token:
+ return error
+ if all_pages:
+ error = get_items(_page_token=next_page_token)
+ else:
items.append({'nextPageToken': next_page_token})
+ return error
- get_items(_page_token=page_token)
+ error = get_items(_page_token=page_token)
+ if error:
+ return error
items = _append_missing_page_token(items)
-
return items
@@ -249,17 +259,22 @@ def get_items(_page_token=''):
for item in json_data.get('items', []):
items.append(item)
+ error = False
next_page_token = json_data.get('nextPageToken')
- if all_pages and (next_page_token is not None):
- get_items(_page_token=next_page_token)
- elif next_page_token is not None:
+ if not next_page_token:
+ return error
+ if all_pages:
+ error = get_items(_page_token=next_page_token)
+ else:
items.append({'nextPageToken': next_page_token})
+ return error
- get_items(_page_token=page_token)
+ error = get_items(_page_token=page_token)
+ if error:
+ return error
items = _append_missing_page_token(items)
-
return items
@@ -310,15 +325,18 @@ def get_items(_page_token=''):
for item in json_data.get('items', []):
if 'snippet' in item:
items.append(item)
+ error = False
next_page_token = json_data.get('nextPageToken')
- if next_page_token is not None:
+ if next_page_token:
items.append({'nextPageToken': next_page_token})
+ return error
- get_items(_page_token=page_token)
+ error = get_items(_page_token=page_token)
+ if error:
+ return error
items = _append_missing_page_token(items)
-
return items
@@ -360,15 +378,18 @@ def get_items(_page_token=''):
for item in json_data.get('items', []):
items.append(item)
+ error = False
next_page_token = json_data.get('nextPageToken')
- if next_page_token is not None:
+ if next_page_token:
items.append({'nextPageToken': next_page_token})
+ return error
- get_items(_page_token=page_token)
+ error = get_items(_page_token=page_token)
+ if error:
+ return error
items = _append_missing_page_token(items)
-
return items
From 1bb36f0256bd79f16302f8c1331619947e0c63cb Mon Sep 17 00:00:00 2001
From: MoojMidge <56883549+MoojMidge@users.noreply.github.com>
Date: Tue, 31 Oct 2023 11:13:45 +1100
Subject: [PATCH 012/141] Tidy up imports
- Group multiple imports
- Remove star imports
- Limit line length
- Use from import where appropriate
---
.../lib/youtube_plugin/kodion/__init__.py | 2 --
.../kodion/abstract_provider.py | 34 +++++++++++--------
.../kodion/impl/abstract_context.py | 14 +++++++-
.../kodion/impl/xbmc/info_labels.py | 2 +-
.../kodion/impl/xbmc/xbmc_context.py | 5 +--
.../kodion/impl/xbmc/xbmc_runner.py | 2 +-
.../youtube_plugin/kodion/utils/__init__.py | 13 ++++++-
.../youtube/client/login_client.py | 5 ++-
.../youtube/helper/subtitles.py | 2 +-
.../youtube/helper/video_info.py | 11 ++++--
.../lib/youtube_plugin/youtube/provider.py | 26 +++++++++++---
.../youtube/youtube_exceptions.py | 8 ++---
12 files changed, 88 insertions(+), 36 deletions(-)
diff --git a/resources/lib/youtube_plugin/kodion/__init__.py b/resources/lib/youtube_plugin/kodion/__init__.py
index 0591b2ea3..be1e324a8 100644
--- a/resources/lib/youtube_plugin/kodion/__init__.py
+++ b/resources/lib/youtube_plugin/kodion/__init__.py
@@ -20,8 +20,6 @@
# import specialized implementation into the kodion namespace
from .impl import Context
-from .constants import *
-
from . import logger
__all__ = ['KodionException', 'RegisterProviderPath', 'AbstractProvider', 'Context', 'utils', 'json_store', 'logger']
diff --git a/resources/lib/youtube_plugin/kodion/abstract_provider.py b/resources/lib/youtube_plugin/kodion/abstract_provider.py
index 925d91ae3..edd9efc40 100644
--- a/resources/lib/youtube_plugin/kodion/abstract_provider.py
+++ b/resources/lib/youtube_plugin/kodion/abstract_provider.py
@@ -14,9 +14,15 @@
from urllib.parse import unquote
from .exceptions import KodionException
-from . import items
+from .items import (
+ from_json,
+ to_jsons,
+ DirectoryItem,
+ NewSearchItem,
+ SearchHistoryItem
+)
+from .utils import to_unicode, to_utf8
from . import constants
-from . import utils
class AbstractProvider(object):
@@ -144,11 +150,11 @@ def _internal_favorite(context, re_match):
command = re_match.group('command')
if command == 'add':
- fav_item = items.from_json(params['item'])
+ fav_item = from_json(params['item'])
context.get_favorite_list().add(fav_item)
return None
if command == 'remove':
- fav_item = items.from_json(params['item'])
+ fav_item = from_json(params['item'])
context.get_favorite_list().remove(fav_item)
context.get_ui().refresh_container()
return None
@@ -158,7 +164,7 @@ def _internal_favorite(context, re_match):
for directory_item in directory_items:
context_menu = [(context.localize(constants.localize.WATCH_LATER_REMOVE),
'RunPlugin(%s)' % context.create_uri([constants.paths.FAVORITES, 'remove'],
- params={'item': items.to_jsons(directory_item)}))]
+ params={'item': to_jsons(directory_item)}))]
directory_item.set_context_menu(context_menu)
return directory_items
@@ -171,11 +177,11 @@ def _internal_watch_later(self, context, re_match):
command = re_match.group('command')
if command == 'add':
- item = items.from_json(params['item'])
+ item = from_json(params['item'])
context.get_watch_later_list().add(item)
return None
if command == 'remove':
- item = items.from_json(params['item'])
+ item = from_json(params['item'])
context.get_watch_later_list().remove(item)
context.get_ui().refresh_container()
return None
@@ -185,7 +191,7 @@ def _internal_watch_later(self, context, re_match):
for video_item in video_items:
context_menu = [(context.localize(constants.localize.WATCH_LATER_REMOVE),
'RunPlugin(%s)' % context.create_uri([constants.paths.WATCH_LATER, 'remove'],
- params={'item': items.to_jsons(video_item)}))]
+ params={'item': to_jsons(video_item)}))]
video_item.set_context_menu(context_menu)
return video_items
@@ -233,7 +239,7 @@ def _internal_search(self, context, re_match):
# came from page 1 of search query by '..'/back, user doesn't want to input on this path
if cached_query and cached_query.get('search_query', {}).get('query'):
query = cached_query.get('search_query', {}).get('query')
- query = utils.to_unicode(query)
+ query = to_unicode(query)
query = unquote(query)
else:
result, input_query = context.get_ui().on_keyboard_input(context.localize(constants.localize.SEARCH_TITLE))
@@ -246,7 +252,7 @@ def _internal_search(self, context, re_match):
incognito = str(context.get_param('incognito', False)).lower() == 'true'
channel_id = context.get_param('channel_id', '')
- query = utils.to_utf8(query)
+ query = to_utf8(query)
try:
self._data_cache.set('search_query', json.dumps({'query': quote(query)}))
except KeyError:
@@ -267,7 +273,7 @@ def _internal_search(self, context, re_match):
incognito = str(context.get_param('incognito', False)).lower() == 'true'
channel_id = context.get_param('channel_id', '')
query = params['q']
- query = utils.to_unicode(query)
+ query = to_unicode(query)
if not incognito and not channel_id:
try:
@@ -284,16 +290,16 @@ def _internal_search(self, context, re_match):
location = str(context.get_param('location', False)).lower() == 'true'
# 'New Search...'
- new_search_item = items.NewSearchItem(context, fanart=self.get_alternative_fanart(context), location=location)
+ new_search_item = NewSearchItem(context, fanart=self.get_alternative_fanart(context), location=location)
result.append(new_search_item)
for search in search_history.list():
# little fallback for old history entries
- if isinstance(search, items.DirectoryItem):
+ if isinstance(search, DirectoryItem):
search = search.get_name()
# we create a new instance of the SearchItem
- search_history_item = items.SearchHistoryItem(context, search, fanart=self.get_alternative_fanart(context), location=location)
+ search_history_item = SearchHistoryItem(context, search, fanart=self.get_alternative_fanart(context), location=location)
result.append(search_history_item)
if search_history.is_empty():
diff --git a/resources/lib/youtube_plugin/kodion/impl/abstract_context.py b/resources/lib/youtube_plugin/kodion/impl/abstract_context.py
index 078314c5c..8866a3f25 100644
--- a/resources/lib/youtube_plugin/kodion/impl/abstract_context.py
+++ b/resources/lib/youtube_plugin/kodion/impl/abstract_context.py
@@ -13,7 +13,19 @@
from .. import constants
from .. import logger
-from ..utils import *
+from ..utils import (
+ create_path,
+ create_uri_path,
+ to_utf8,
+ AccessManager,
+ DataCache,
+ FavoriteList,
+ FunctionCache,
+ PlaybackHistory,
+ SearchHistory,
+ SystemVersion,
+ WatchLaterList,
+)
class AbstractContext(object):
diff --git a/resources/lib/youtube_plugin/kodion/impl/xbmc/info_labels.py b/resources/lib/youtube_plugin/kodion/impl/xbmc/info_labels.py
index 2018d3bb3..e02728a18 100644
--- a/resources/lib/youtube_plugin/kodion/impl/xbmc/info_labels.py
+++ b/resources/lib/youtube_plugin/kodion/impl/xbmc/info_labels.py
@@ -9,7 +9,7 @@
"""
from ... import utils
-from ...items import *
+from ...items import AudioItem, DirectoryItem, ImageItem, VideoItem
def _process_date(info_labels, param):
diff --git a/resources/lib/youtube_plugin/kodion/impl/xbmc/xbmc_context.py b/resources/lib/youtube_plugin/kodion/impl/xbmc/xbmc_context.py
index f36f60e86..98ab90163 100644
--- a/resources/lib/youtube_plugin/kodion/impl/xbmc/xbmc_context.py
+++ b/resources/lib/youtube_plugin/kodion/impl/xbmc/xbmc_context.py
@@ -13,10 +13,7 @@
import os
import sys
import weakref
-from urllib.parse import quote
-from urllib.parse import unquote
-from urllib.parse import urlparse
-from urllib.parse import parse_qsl
+from urllib.parse import parse_qsl, quote, unquote, urlparse
import xbmc
import xbmcaddon
diff --git a/resources/lib/youtube_plugin/kodion/impl/xbmc/xbmc_runner.py b/resources/lib/youtube_plugin/kodion/impl/xbmc/xbmc_runner.py
index 46fda8a72..5d881a20b 100644
--- a/resources/lib/youtube_plugin/kodion/impl/xbmc/xbmc_runner.py
+++ b/resources/lib/youtube_plugin/kodion/impl/xbmc/xbmc_runner.py
@@ -13,7 +13,7 @@
from ..abstract_provider_runner import AbstractProviderRunner
from ...exceptions import KodionException
-from ...items import *
+from ...items import AudioItem, DirectoryItem, ImageItem, UriItem, VideoItem
from ... import AbstractProvider
from . import info_labels
from . import xbmc_items
diff --git a/resources/lib/youtube_plugin/kodion/utils/__init__.py b/resources/lib/youtube_plugin/kodion/utils/__init__.py
index 717fb4dc2..5365ec8f6 100644
--- a/resources/lib/youtube_plugin/kodion/utils/__init__.py
+++ b/resources/lib/youtube_plugin/kodion/utils/__init__.py
@@ -9,7 +9,18 @@
"""
from . import datetime_parser
-from .methods import *
+from .methods import (
+ create_path,
+ create_uri_path,
+ find_best_fit,
+ find_video_id,
+ loose_version,
+ make_dirs,
+ select_stream,
+ strip_html_from_text,
+ to_unicode,
+ to_utf8,
+)
from .search_history import SearchHistory
from .favorite_list import FavoriteList
from .watch_later_list import WatchLaterList
diff --git a/resources/lib/youtube_plugin/youtube/client/login_client.py b/resources/lib/youtube_plugin/youtube/client/login_client.py
index 4135ac025..e9d595302 100644
--- a/resources/lib/youtube_plugin/youtube/client/login_client.py
+++ b/resources/lib/youtube_plugin/youtube/client/login_client.py
@@ -15,7 +15,10 @@
from ...youtube.youtube_exceptions import InvalidGrant, LoginException
from ...kodion import Context
-from .__config__ import api, youtube_tv, developer_keys, keys_changed
+from .__config__ import (api,
+ developer_keys,
+ keys_changed,
+ youtube_tv,)
context = Context(plugin_id='plugin.video.youtube')
diff --git a/resources/lib/youtube_plugin/youtube/helper/subtitles.py b/resources/lib/youtube_plugin/youtube/helper/subtitles.py
index ef2f4ab2b..f61c02808 100644
--- a/resources/lib/youtube_plugin/youtube/helper/subtitles.py
+++ b/resources/lib/youtube_plugin/youtube/helper/subtitles.py
@@ -7,7 +7,7 @@
"""
from html import unescape
-from urllib.parse import (parse_qs, urlsplit, urlunsplit, urlencode, urljoin)
+from urllib.parse import parse_qs, urlsplit, urlunsplit, urlencode, urljoin
import xbmcvfs
import requests
diff --git a/resources/lib/youtube_plugin/youtube/helper/video_info.py b/resources/lib/youtube_plugin/youtube/helper/video_info.py
index 4f7621e9a..42d7b7826 100644
--- a/resources/lib/youtube_plugin/youtube/helper/video_info.py
+++ b/resources/lib/youtube_plugin/youtube/helper/video_info.py
@@ -14,8 +14,15 @@
from json import dumps as json_dumps, loads as json_loads
from html import unescape
-from urllib.parse import (parse_qs, urlsplit, urlunsplit, urlencode, urljoin,
- quote, unquote)
+from urllib.parse import (
+ parse_qs,
+ quote,
+ unquote,
+ urlsplit,
+ urlunsplit,
+ urlencode,
+ urljoin,
+)
import requests
import xbmcvfs
diff --git a/resources/lib/youtube_plugin/youtube/provider.py b/resources/lib/youtube_plugin/youtube/provider.py
index 1982a4777..280062ad3 100644
--- a/resources/lib/youtube_plugin/youtube/provider.py
+++ b/resources/lib/youtube_plugin/youtube/provider.py
@@ -17,11 +17,29 @@
from ..youtube.helper import yt_subscriptions
from .. import kodion
-from ..kodion.utils import FunctionCache, strip_html_from_text, get_client_ip_address, is_httpd_live, find_video_id
-from ..kodion.items import *
+from ..kodion.utils import (
+ find_video_id,
+ get_client_ip_address,
+ is_httpd_live,
+ strip_html_from_text,
+ FunctionCache,
+)
+from ..kodion.items import DirectoryItem
from ..youtube.client import YouTube
-from .helper import v3, ResourceManager, yt_specials, yt_playlist, yt_login, yt_setup_wizard, yt_video, \
- yt_context_menu, yt_play, yt_old_actions, UrlResolver, UrlToItemConverter
+from .helper import (
+ v3,
+ yt_context_menu,
+ yt_login,
+ yt_old_actions,
+ yt_play,
+ yt_playlist,
+ yt_setup_wizard,
+ yt_specials,
+ yt_video,
+ ResourceManager,
+ UrlResolver,
+ UrlToItemConverter,
+)
from .youtube_exceptions import InvalidGrant, LoginException
import xbmc
diff --git a/resources/lib/youtube_plugin/youtube/youtube_exceptions.py b/resources/lib/youtube_plugin/youtube/youtube_exceptions.py
index 1ff852d11..1e7cda824 100644
--- a/resources/lib/youtube_plugin/youtube/youtube_exceptions.py
+++ b/resources/lib/youtube_plugin/youtube/youtube_exceptions.py
@@ -8,16 +8,16 @@
See LICENSES/GPL-2.0-only for more information.
"""
-from .. import kodion
+from ..kodion import KodionException
-class LoginException(kodion.KodionException):
+class LoginException(KodionException):
pass
-class YouTubeException(kodion.KodionException):
+class YouTubeException(KodionException):
pass
-class InvalidGrant(kodion.KodionException):
+class InvalidGrant(KodionException):
pass
From 0466d93b233933f3adde2dbb5d060c263931a405 Mon Sep 17 00:00:00 2001
From: MoojMidge <56883549+MoojMidge@users.noreply.github.com>
Date: Tue, 31 Oct 2023 15:09:41 +1100
Subject: [PATCH 013/141] Misc optimisations and refactoring
Needs testing for regressions
---
.../kodion/impl/abstract_context.py | 2 +-
.../kodion/impl/abstract_settings.py | 2 +-
.../kodion/impl/xbmc/info_labels.py | 2 +-
.../kodion/impl/xbmc/xbmc_context.py | 42 ++--
.../kodion/impl/xbmc/xbmc_runner.py | 2 +-
.../kodion/items/search_item.py | 4 +-
.../lib/youtube_plugin/kodion/items/utils.py | 44 ++--
.../youtube_plugin/kodion/items/video_item.py | 15 +-
.../kodion/json_store/login_tokens.py | 27 +--
.../kodion/utils/access_manager.py | 15 +-
.../youtube_plugin/kodion/utils/data_cache.py | 7 +-
.../kodion/utils/http_server.py | 8 +-
.../youtube_plugin/kodion/utils/methods.py | 38 ++--
.../youtube/client/__config__.py | 5 +-
.../youtube_plugin/youtube/client/youtube.py | 4 +-
.../youtube/helper/resource_manager.py | 190 ++++++++---------
.../youtube/helper/url_to_item_converter.py | 37 ++--
.../youtube_plugin/youtube/helper/utils.py | 197 ++++++++++--------
.../lib/youtube_plugin/youtube/helper/v3.py | 68 +++---
.../youtube/helper/video_info.py | 22 +-
.../youtube_plugin/youtube/helper/yt_play.py | 2 +-
.../youtube/helper/yt_specials.py | 6 +-
.../lib/youtube_plugin/youtube/provider.py | 30 ++-
resources/lib/youtube_requests.py | 27 +--
24 files changed, 377 insertions(+), 419 deletions(-)
diff --git a/resources/lib/youtube_plugin/kodion/impl/abstract_context.py b/resources/lib/youtube_plugin/kodion/impl/abstract_context.py
index 8866a3f25..321087b66 100644
--- a/resources/lib/youtube_plugin/kodion/impl/abstract_context.py
+++ b/resources/lib/youtube_plugin/kodion/impl/abstract_context.py
@@ -159,7 +159,7 @@ def create_uri(self, path='/', params=None):
else:
uri = "%s://%s/" % ('plugin', str(self._plugin_id))
- if len(params) > 0:
+ if params:
# make a copy of the map
uri_params = {}
uri_params.update(params)
diff --git a/resources/lib/youtube_plugin/kodion/impl/abstract_settings.py b/resources/lib/youtube_plugin/kodion/impl/abstract_settings.py
index 14868fab6..c231cea4d 100644
--- a/resources/lib/youtube_plugin/kodion/impl/abstract_settings.py
+++ b/resources/lib/youtube_plugin/kodion/impl/abstract_settings.py
@@ -57,7 +57,7 @@ def get_bool(self, setting_id, default_value):
if value is None or value == '':
return default_value
- if value != 'false' and value != 'true':
+ if value not in {'false', 'true'}:
return default_value
return value == 'true'
diff --git a/resources/lib/youtube_plugin/kodion/impl/xbmc/info_labels.py b/resources/lib/youtube_plugin/kodion/impl/xbmc/info_labels.py
index e02728a18..86f8f9427 100644
--- a/resources/lib/youtube_plugin/kodion/impl/xbmc/info_labels.py
+++ b/resources/lib/youtube_plugin/kodion/impl/xbmc/info_labels.py
@@ -169,7 +169,7 @@ def create_from_item(base_item):
_process_list_value(info_labels, 'cast', base_item.get_cast())
# Audio and Video
- if isinstance(base_item, AudioItem) or isinstance(base_item, VideoItem):
+ if isinstance(base_item, (AudioItem, VideoItem)):
# 'title' = 'Blow Your Head Off' (string)
_process_string_value(info_labels, 'title', base_item.get_title())
diff --git a/resources/lib/youtube_plugin/kodion/impl/xbmc/xbmc_context.py b/resources/lib/youtube_plugin/kodion/impl/xbmc/xbmc_context.py
index 98ab90163..867296b51 100644
--- a/resources/lib/youtube_plugin/kodion/impl/xbmc/xbmc_context.py
+++ b/resources/lib/youtube_plugin/kodion/impl/xbmc/xbmc_context.py
@@ -56,14 +56,9 @@ def __init__(self, path='/', params=None, plugin_name='', plugin_id='', override
# after that try to get the params
if len(sys.argv) > 2:
params = sys.argv[2][1:]
- if len(params) > 0:
+ if params:
self._uri = '?'.join([self._uri, params])
-
- self._params = {}
- params = dict(parse_qsl(params))
- for _param in params:
- item = params[_param]
- self._params[_param] = item
+ self._params = dict(parse_qsl(params))
self._ui = None
self._video_playlist = None
@@ -184,29 +179,22 @@ def get_settings(self):
return self._settings
def localize(self, text_id, default_text=''):
- result = None
- if isinstance(text_id, int):
- """
- We want to use all localization strings!
- Addons should only use the range 30000 thru 30999 (see: http://kodi.wiki/view/Language_support) but we
- do it anyway. I want some of the localized strings for the views of a skin.
- """
- if text_id >= 0 and (text_id < 30000 or text_id > 30999):
- result = xbmc.getLocalizedString(text_id)
- if result is not None and result:
- result = utils.to_unicode(result)
-
- if not result:
+ if not isinstance(text_id, int):
try:
- result = self._addon.getLocalizedString(int(text_id))
- if result is not None and result:
- result = utils.to_unicode(result)
+ text_id = int(text_id)
except ValueError:
- pass
-
- if not result:
- result = default_text
+ return default_text
+ if text_id <= 0:
+ return default_text
+ """
+ We want to use all localization strings!
+ Addons should only use the range 30000 thru 30999 (see: http://kodi.wiki/view/Language_support) but we
+ do it anyway. I want some of the localized strings for the views of a skin.
+ """
+ source = self._addon if 30000 <= text_id < 31000 else xbmc
+ result = source.getLocalizedString(text_id)
+ result = utils.to_unicode(result) if result else default_text
return result
def set_content_type(self, content_type):
diff --git a/resources/lib/youtube_plugin/kodion/impl/xbmc/xbmc_runner.py b/resources/lib/youtube_plugin/kodion/impl/xbmc/xbmc_runner.py
index 5d881a20b..454ab1d11 100644
--- a/resources/lib/youtube_plugin/kodion/impl/xbmc/xbmc_runner.py
+++ b/resources/lib/youtube_plugin/kodion/impl/xbmc/xbmc_runner.py
@@ -46,7 +46,7 @@ def run(self, provider, context=None):
if isinstance(result, bool) and not result:
xbmcplugin.endOfDirectory(self.handle, succeeded=False)
- elif isinstance(result, VideoItem) or isinstance(result, AudioItem) or isinstance(result, UriItem):
+ elif isinstance(result, (VideoItem, AudioItem, UriItem)):
self._set_resolved_url(context, result)
elif isinstance(result, DirectoryItem):
self._add_directory(context, result)
diff --git a/resources/lib/youtube_plugin/kodion/items/search_item.py b/resources/lib/youtube_plugin/kodion/items/search_item.py
index 273da2831..f04e3ed6e 100644
--- a/resources/lib/youtube_plugin/kodion/items/search_item.py
+++ b/resources/lib/youtube_plugin/kodion/items/search_item.py
@@ -21,9 +21,7 @@ def __init__(self, context, alt_name=None, image=None, fanart=None, location=Fal
if image is None:
image = context.create_resource_path('media/search.png')
- params = dict()
- if location:
- params = {'location': location}
+ params = {'location': location} if location else {}
DirectoryItem.__init__(self, name, context.create_uri([constants.paths.SEARCH, 'list'], params=params), image=image)
if fanart:
diff --git a/resources/lib/youtube_plugin/kodion/items/utils.py b/resources/lib/youtube_plugin/kodion/items/utils.py
index 9cdd6b6a4..cabfc1885 100644
--- a/resources/lib/youtube_plugin/kodion/items/utils.py
+++ b/resources/lib/youtube_plugin/kodion/items/utils.py
@@ -10,10 +10,18 @@
import json
-from .video_item import VideoItem
-from .directory_item import DirectoryItem
from .audio_item import AudioItem
+from .directory_item import DirectoryItem
from .image_item import ImageItem
+from .video_item import VideoItem
+
+
+_ITEM_TYPES = {
+ 'AudioItem': AudioItem,
+ 'DirectoryItem': DirectoryItem,
+ 'ImageItem': ImageItem,
+ 'VideoItem': VideoItem,
+}
def from_json(json_data):
@@ -24,25 +32,16 @@ def from_json(json_data):
"""
def _from_json(_json_data):
- mapping = {'VideoItem': lambda: VideoItem('', ''),
- 'DirectoryItem': lambda: DirectoryItem('', ''),
- 'AudioItem': lambda: AudioItem('', ''),
- 'ImageItem': lambda: ImageItem('', '')}
-
- item = None
- item_type = _json_data.get('type', None)
- for key in mapping:
- if item_type == key:
- item = mapping[key]()
- break
-
- if item is None:
+ item_type = _json_data.get('type')
+ if not item_type or item_type not in _ITEM_TYPES:
return _json_data
+ item = _ITEM_TYPES[item_type]()
+
data = _json_data.get('data', {})
- for key in data:
+ for key, value in data.items():
if hasattr(item, key):
- setattr(item, key, data[key])
+ setattr(item, key, value)
return item
@@ -66,14 +65,9 @@ def _to_json(obj):
if isinstance(obj, dict):
return obj.__dict__
- mapping = {VideoItem: 'VideoItem',
- DirectoryItem: 'DirectoryItem',
- AudioItem: 'AudioItem',
- ImageItem: 'ImageItem'}
-
- for key in mapping:
- if isinstance(obj, key):
- return {'type': mapping[key], 'data': obj.__dict__}
+ for name, item_type in _ITEM_TYPES.items():
+ if isinstance(obj, item_type):
+ return {'type': name, 'data': obj.__dict__}
return obj.__dict__
diff --git a/resources/lib/youtube_plugin/kodion/items/video_item.py b/resources/lib/youtube_plugin/kodion/items/video_item.py
index acb0620f9..5bcb90384 100644
--- a/resources/lib/youtube_plugin/kodion/items/video_item.py
+++ b/resources/lib/youtube_plugin/kodion/items/video_item.py
@@ -56,7 +56,7 @@ def __init__(self, name, uri, image='', fanart=''):
self._playlist_item_id = None
def set_play_count(self, play_count):
- self._play_count = int(play_count)
+ self._play_count = int(play_count or 0)
def get_play_count(self):
return self._play_count
@@ -177,7 +177,7 @@ def set_duration_from_minutes(self, minutes):
self.set_duration_from_seconds(int(minutes) * 60)
def set_duration_from_seconds(self, seconds):
- self._duration = int(seconds)
+ self._duration = int(seconds or 0)
def get_duration(self):
return self._duration
@@ -245,7 +245,10 @@ def set_mediatype(self, mediatype):
self._mediatype = mediatype
def get_mediatype(self):
- if self._mediatype not in ['video', 'movie', 'tvshow', 'season', 'episode', 'musicvideo']:
+ if (self._mediatype not in {'video',
+ 'movie',
+ 'tvshow', 'season', 'episode',
+ 'musicvideo'}):
self._mediatype = 'video'
return self._mediatype
@@ -265,19 +268,19 @@ def get_license_key(self):
return self.license_key
def set_last_played(self, last_played):
- self._last_played = last_played
+ self._last_played = last_played or ''
def get_last_played(self):
return self._last_played
def set_start_percent(self, start_percent):
- self._start_percent = start_percent
+ self._start_percent = start_percent or ''
def get_start_percent(self):
return self._start_percent
def set_start_time(self, start_time):
- self._start_time = start_time
+ self._start_time = start_time or ''
def get_start_time(self):
return self._start_time
diff --git a/resources/lib/youtube_plugin/kodion/json_store/login_tokens.py b/resources/lib/youtube_plugin/kodion/json_store/login_tokens.py
index e575ae3ff..58d586f9c 100644
--- a/resources/lib/youtube_plugin/kodion/json_store/login_tokens.py
+++ b/resources/lib/youtube_plugin/kodion/json_store/login_tokens.py
@@ -32,7 +32,7 @@ def set_defaults(self):
if 'last_origin' not in data['access_manager']:
data['access_manager']['last_origin'] = 'plugin.video.youtube'
if 'developers' not in data['access_manager']:
- data['access_manager']['developers'] = dict()
+ data['access_manager']['developers'] = {}
# clean up
if data['access_manager']['current_user'] == 'default':
@@ -61,24 +61,13 @@ def set_defaults(self):
data['access_manager']['users'][current_user]['watch_history'] = 'HL'
# ensure all users have uuid
- uuids = list()
- uuid_update = False
- for k in list(data['access_manager']['users'].keys()):
- c_uuid = data['access_manager']['users'][k].get('id')
- if c_uuid:
- uuids.append(c_uuid)
- else:
- if not uuid_update:
- uuid_update = True
-
- if uuid_update:
- for k in list(data['access_manager']['users'].keys()):
- c_uuid = data['access_manager']['users'][k].get('id')
- if not c_uuid:
- g_uuid = uuid.uuid4().hex
- while g_uuid in uuids:
- g_uuid = uuid.uuid4().hex
- data['access_manager']['users'][k]['id'] = g_uuid
+ uuids = set()
+ for user in data['access_manager']['users'].values():
+ c_uuid = user.get('id')
+ while not c_uuid or c_uuid in uuids:
+ c_uuid = uuid.uuid4().hex
+ uuids.add(c_uuid)
+ user['id'] = c_uuid
# end uuid check
self.save(data)
diff --git a/resources/lib/youtube_plugin/kodion/utils/access_manager.py b/resources/lib/youtube_plugin/kodion/utils/access_manager.py
index f3d3a0464..f542aa747 100644
--- a/resources/lib/youtube_plugin/kodion/utils/access_manager.py
+++ b/resources/lib/youtube_plugin/kodion/utils/access_manager.py
@@ -39,15 +39,12 @@ def get_new_user(self, user_name=''):
:param user_name: string, users name
:return: a new user dict
"""
- uuids = list()
- new_uuid = uuid.uuid4().hex
-
- for k in list(self._json['access_manager']['users'].keys()):
- user_uuid = self._json['access_manager']['users'][k].get('id')
- if user_uuid:
- uuids.append(user_uuid)
-
- while new_uuid in uuids:
+ uuids = [
+ user.get('id')
+ for user in self._json['access_manager']['users'].values()
+ ]
+ new_uuid = None
+ while not new_uuid or new_uuid in uuids:
new_uuid = uuid.uuid4().hex
return {'access_token': '', 'refresh_token': '', 'token_expires': -1, 'last_key_hash': '',
diff --git a/resources/lib/youtube_plugin/kodion/utils/data_cache.py b/resources/lib/youtube_plugin/kodion/utils/data_cache.py
index 62ced2269..abee69e65 100644
--- a/resources/lib/youtube_plugin/kodion/utils/data_cache.py
+++ b/resources/lib/youtube_plugin/kodion/utils/data_cache.py
@@ -49,7 +49,9 @@ def _decode(obj):
for item in query_result:
cached_time = item[1]
if cached_time is None:
- logger.log_error('Data Cache [get_items]: cached_time is None while getting {content_id}'.format(content_id=str(item[0])))
+ logger.log_error('Data Cache [get_items]: cached_time is None while getting {content_id}'.format(
+ content_id=item[0]
+ ))
cached_time = current_time
# this is so stupid, but we have the function 'total_seconds' only starting with python 2.7
diff_seconds = self.get_seconds_diff(cached_time)
@@ -116,8 +118,7 @@ def _encode(obj):
self._open()
- for key in list(items.keys()):
- item = items[key]
+ for key, item in items.items():
self._execute(needs_commit, query, values=[key, current_time, _encode(json.dumps(item))])
needs_commit = False
diff --git a/resources/lib/youtube_plugin/kodion/utils/http_server.py b/resources/lib/youtube_plugin/kodion/utils/http_server.py
index 8e3202719..c63f0a02a 100644
--- a/resources/lib/youtube_plugin/kodion/utils/http_server.py
+++ b/resources/lib/youtube_plugin/kodion/utils/http_server.py
@@ -237,11 +237,11 @@ def do_POST(self):
if size_limit:
self.send_header('X-Limit-Video', 'max={size_limit}px'.format(size_limit=str(size_limit)))
- for d in list(result.headers.items()):
- if re.match('^[Cc]ontent-[Ll]ength$', d[0]):
- self.send_header(d[0], response_length)
+ for header, value in result.headers.items():
+ if re.match('^[Cc]ontent-[Ll]ength$', header):
+ self.send_header(header, response_length)
else:
- self.send_header(d[0], d[1])
+ self.send_header(header, value)
self.end_headers()
for chunk in self.get_chunks(response_body):
diff --git a/resources/lib/youtube_plugin/kodion/utils/methods.py b/resources/lib/youtube_plugin/kodion/utils/methods.py
index 490759552..c8daf481c 100644
--- a/resources/lib/youtube_plugin/kodion/utils/methods.py
+++ b/resources/lib/youtube_plugin/kodion/utils/methods.py
@@ -55,7 +55,7 @@ def to_utf8(text):
def to_unicode(text):
result = text
- if isinstance(text, str) or isinstance(text, bytes):
+ if isinstance(text, (bytes, str)):
try:
result = text.decode('utf-8', 'ignore')
except (AttributeError, UnicodeEncodeError):
@@ -65,27 +65,24 @@ def to_unicode(text):
def find_best_fit(data, compare_method=None):
+ if isinstance(data, dict):
+ data = data.values()
+
try:
- return next(item for item in data if item['container'] == 'mpd')
+ return next(item for item in data if item.get('container') == 'mpd')
except StopIteration:
pass
- result = None
+ if not compare_method:
+ return None
+ result = None
last_fit = -1
- if isinstance(data, dict):
- for key in list(data.keys()):
- item = data[key]
- fit = abs(compare_method(item))
- if last_fit == -1 or fit < last_fit:
- last_fit = fit
- result = item
- elif isinstance(data, list):
- for item in data:
- fit = abs(compare_method(item))
- if last_fit == -1 or fit < last_fit:
- last_fit = fit
- result = item
+ for item in data:
+ fit = abs(compare_method(item))
+ if last_fit == -1 or fit < last_fit:
+ last_fit = fit
+ result = item
return result
@@ -144,7 +141,7 @@ def _find_best_fit_video(_stream_data):
sorted_stream_data_list = sorted(stream_data_list, key=_sort_stream_data)
context.log_debug('selectable streams: %d' % len(sorted_stream_data_list))
- log_streams = list()
+ log_streams = []
for sorted_stream_data in sorted_stream_data_list:
log_data = copy.deepcopy(sorted_stream_data)
if 'license_info' in log_data:
@@ -157,9 +154,10 @@ def _find_best_fit_video(_stream_data):
selected_stream_data = None
if ask_for_quality and len(sorted_stream_data_list) > 1:
- items = list()
- for sorted_stream_data in sorted_stream_data_list:
- items.append((sorted_stream_data['title'], sorted_stream_data))
+ items = [
+ (sorted_stream_data['title'], sorted_stream_data)
+ for sorted_stream_data in sorted_stream_data_list
+ ]
result = context.get_ui().on_select(context.localize(localize.SELECT_VIDEO_QUALITY), items)
if result != -1:
diff --git a/resources/lib/youtube_plugin/youtube/client/__config__.py b/resources/lib/youtube_plugin/youtube/client/__config__.py
index ac952299c..a7ee4818b 100644
--- a/resources/lib/youtube_plugin/youtube/client/__config__.py
+++ b/resources/lib/youtube_plugin/youtube/client/__config__.py
@@ -220,11 +220,10 @@ def _strip_api_keys(self, api_key, client_id, client_secret):
keys_changed = _api_check.changed
current_user = _api_check.get_current_user()
-api = dict()
-youtube_tv = dict()
-
+api = {}
api['key'], api['id'], api['secret'] = _api_check.get_api_keys(_api_check.get_current_switch())
+youtube_tv = {}
youtube_tv['key'], youtube_tv['id'], youtube_tv['secret'] = _api_check.get_api_keys('youtube-tv')
developer_keys = _api_check.get_api_keys('developer')
diff --git a/resources/lib/youtube_plugin/youtube/client/youtube.py b/resources/lib/youtube_plugin/youtube/client/youtube.py
index 41e7eb3ca..834d56daa 100644
--- a/resources/lib/youtube_plugin/youtube/client/youtube.py
+++ b/resources/lib/youtube_plugin/youtube/client/youtube.py
@@ -398,7 +398,7 @@ def helper(video_id, responses):
channel_counts.setdefault(channel_id, 0)
if channel_counts[channel_id] <= 3:
# Use the item
- channel_counts[channel_id] = channel_counts[channel_id] + 1
+ channel_counts[channel_id] += 1
item["page_number"] = counter // 50
sorted_items.append(item)
else:
@@ -775,7 +775,7 @@ def _perform(_page_token, _offset, _result):
_result['items'] = cached[cache_items_key]
""" no cache, get uploads data from web """
- if len(_result['items']) == 0:
+ if not _result['items']:
# get all subscriptions channel ids
sub_page_token = True
sub_channel_ids = []
diff --git a/resources/lib/youtube_plugin/youtube/helper/resource_manager.py b/resources/lib/youtube_plugin/youtube/helper/resource_manager.py
index d8b1eeb64..ee62dad50 100644
--- a/resources/lib/youtube_plugin/youtube/helper/resource_manager.py
+++ b/resources/lib/youtube_plugin/youtube/helper/resource_manager.py
@@ -35,13 +35,8 @@ def _get_playlist_data(self, playlist_id):
return self._playlist_data.get(playlist_id, {})
def _update_channels(self, channel_ids):
- result = dict()
- json_data = dict()
- channel_ids_to_update = list()
- channel_ids_cached = list()
- updated_channel_ids = list()
-
- data_cache = self._context.get_data_cache()
+ json_data = None
+ updated_channel_ids = []
function_cache = self._context.get_function_cache()
for channel_id in channel_ids:
@@ -55,102 +50,90 @@ def _update_channels(self, channel_ids):
self._context.log_debug('Channel "mine" not found: %s' % json_data)
channel_id = None
- json_data = dict()
+ json_data = None
if channel_id:
updated_channel_ids.append(channel_id)
channel_ids = updated_channel_ids
+ data_cache = self._context.get_data_cache()
channel_data = data_cache.get_items(DataCache.ONE_MONTH, channel_ids)
- for channel_id in channel_ids:
- if not channel_data.get(channel_id):
- channel_ids_to_update.append(channel_id)
- else:
- channel_ids_cached.append(channel_id)
- result.update(channel_data)
- if len(channel_ids_cached) > 0:
- self._context.log_debug('Found cached data for channels |%s|' % ', '.join(channel_ids_cached))
-
- if len(channel_ids_to_update) > 0:
- self._context.log_debug('No data for channels |%s| cached' % ', '.join(channel_ids_to_update))
- data = []
- list_of_50s = self._make_list_of_50(channel_ids_to_update)
- for list_of_50 in list_of_50s:
- data.append(self._youtube_client.get_channels(list_of_50))
+ channel_ids = set(channel_ids)
+ channel_ids_cached = set(channel_data)
+ channel_ids_to_update = channel_ids - channel_ids_cached
+ channel_ids_cached = channel_ids & channel_ids_cached
- channel_data = dict()
- yt_items = []
- for response in data:
- yt_items += response.get('items', [])
-
- for yt_item in yt_items:
- channel_id = str(yt_item['id'])
- channel_data[channel_id] = yt_item
- result[channel_id] = yt_item
+ result = channel_data
+ if channel_ids_cached:
+ self._context.log_debug('Found cached data for channels |%s|' % ', '.join(channel_ids_cached))
+ if channel_ids_to_update:
+ self._context.log_debug('No data for channels |%s| cached' % ', '.join(channel_ids_to_update))
+ json_data = [
+ self._youtube_client.get_channels(list_of_50)
+ for list_of_50 in self._list_batch(channel_ids_to_update, n=50)
+ ]
+ channel_data = {
+ yt_item['id']: yt_item
+ for batch in json_data
+ for yt_item in batch.get('items', [])
+ if yt_item
+ }
+ result.update(channel_data)
data_cache.set_all(channel_data)
- self._context.log_debug('Cached data for channels |%s|' % ', '.join(list(channel_data.keys())))
+ self._context.log_debug('Cached data for channels |%s|' % ', '.join(channel_data))
if self.handle_error(json_data):
return result
-
- return result
+ return {}
def _update_videos(self, video_ids, live_details=False, suppress_errors=False):
- result = dict()
- json_data = dict()
- video_ids_to_update = list()
- video_ids_cached = list()
-
+ json_data = None
data_cache = self._context.get_data_cache()
-
video_data = data_cache.get_items(DataCache.ONE_MONTH, video_ids)
- for video_id in video_ids:
- if not video_data.get(video_id):
- video_ids_to_update.append(video_id)
- else:
- video_ids_cached.append(video_id)
- result.update(video_data)
- if len(video_ids_cached) > 0:
+
+ video_ids = set(video_ids)
+ video_ids_cached = set(video_data)
+ video_ids_to_update = video_ids - video_ids_cached
+ video_ids_cached = video_ids & video_ids_cached
+
+ result = video_data
+ if video_ids_cached:
self._context.log_debug('Found cached data for videos |%s|' % ', '.join(video_ids_cached))
- if len(video_ids_to_update) > 0:
+ if video_ids_to_update:
self._context.log_debug('No data for videos |%s| cached' % ', '.join(video_ids_to_update))
json_data = self._youtube_client.get_videos(video_ids_to_update, live_details)
- video_data = dict()
- yt_items = json_data.get('items', [])
- for yt_item in yt_items:
- video_id = str(yt_item['id'])
- video_data[video_id] = yt_item
- result[video_id] = yt_item
+ video_data = {
+ yt_item['id']: yt_item
+ for yt_item in json_data.get('items', [])
+ if yt_item
+ }
+ result.update(video_data)
data_cache.set_all(video_data)
- self._context.log_debug('Cached data for videos |%s|' % ', '.join(list(video_data.keys())))
+ self._context.log_debug('Cached data for videos |%s|' % ', '.join(video_data))
- played_items = dict()
if self._context.get_settings().use_local_history():
playback_history = self._context.get_playback_history()
played_items = playback_history.get_items(video_ids)
-
- for k in list(result.keys()):
- result[k]['play_data'] = played_items.get(k, dict())
+ for video_id, play_data in played_items.items():
+ result[video_id]['play_data'] = play_data
if self.handle_error(json_data, suppress_errors) or suppress_errors:
return result
return {}
@staticmethod
- def _make_list_of_50(list_of_ids):
- list_of_50 = []
- pos = 0
- while pos < len(list_of_ids):
- list_of_50.append(list_of_ids[pos:pos + 50])
- pos += 50
- return list_of_50
+ def _list_batch(input_list, n=50):
+ if not isinstance(input_list, (list, tuple)):
+ input_list = list(input_list)
+ for i in range(0, len(input_list), n):
+ yield input_list[i:i + n]
def get_videos(self, video_ids, live_details=False, suppress_errors=False):
- list_of_50s = self._make_list_of_50(video_ids)
+ list_of_50s = self._list_batch(video_ids, n=50)
result = {}
for list_of_50 in list_of_50s:
@@ -158,41 +141,37 @@ def get_videos(self, video_ids, live_details=False, suppress_errors=False):
return result
def _update_playlists(self, playlists_ids):
- result = dict()
- json_data = dict()
- playlist_ids_to_update = list()
- playlists_ids_cached = list()
-
+ json_data = None
data_cache = self._context.get_data_cache()
-
playlist_data = data_cache.get_items(DataCache.ONE_MONTH, playlists_ids)
- for playlist_id in playlists_ids:
- if not playlist_data.get(playlist_id):
- playlist_ids_to_update.append(playlist_id)
- else:
- playlists_ids_cached.append(playlist_id)
- result.update(playlist_data)
- if len(playlists_ids_cached) > 0:
+
+ playlists_ids = set(playlists_ids)
+ playlists_ids_cached = set(playlist_data)
+ playlist_ids_to_update = playlists_ids - playlists_ids_cached
+ playlists_ids_cached = playlists_ids & playlists_ids_cached
+
+ result = playlist_data
+ if playlists_ids_cached:
self._context.log_debug('Found cached data for playlists |%s|' % ', '.join(playlists_ids_cached))
- if len(playlist_ids_to_update) > 0:
+ if playlist_ids_to_update:
self._context.log_debug('No data for playlists |%s| cached' % ', '.join(playlist_ids_to_update))
json_data = self._youtube_client.get_playlists(playlist_ids_to_update)
- playlist_data = dict()
- yt_items = json_data.get('items', [])
- for yt_item in yt_items:
- playlist_id = str(yt_item['id'])
- playlist_data[playlist_id] = yt_item
- result[playlist_id] = yt_item
+ playlist_data = {
+ yt_item['id']: yt_item
+ for yt_item in json_data.get('items', [])
+ if yt_item
+ }
+ result.update(playlist_data)
data_cache.set_all(playlist_data)
- self._context.log_debug('Cached data for playlists |%s|' % ', '.join(list(playlist_data.keys())))
+ self._context.log_debug('Cached data for playlists |%s|' % ', '.join(playlist_data))
if self.handle_error(json_data):
return result
return {}
def get_playlists(self, playlists_ids):
- list_of_50s = self._make_list_of_50(playlists_ids)
+ list_of_50s = self._list_batch(playlists_ids, n=50)
result = {}
for list_of_50 in list_of_50s:
@@ -207,8 +186,9 @@ def get_related_playlists(self, channel_id):
if channel_id != 'mine':
item = result.get(channel_id, {})
else:
- for key in list(result.keys()):
- item = result[key]
+ for item in result.values():
+ if item:
+ break
if item is None:
return {}
@@ -216,7 +196,7 @@ def get_related_playlists(self, channel_id):
return item.get('contentDetails', {}).get('relatedPlaylists', {})
def get_channels(self, channel_ids):
- list_of_50s = self._make_list_of_50(channel_ids)
+ list_of_50s = self._list_batch(channel_ids, n=50)
result = {}
for list_of_50 in list_of_50s:
@@ -228,20 +208,20 @@ def get_fanarts(self, channel_ids):
return {}
result = self._update_channels(channel_ids)
-
+ banners = ['bannerTvMediumImageUrl', 'bannerTvLowImageUrl',
+ 'bannerTvImageUrl', 'bannerExternalUrl']
# transform
- for key in list(result.keys()):
- item = result[key]
-
- # set an empty url
- result[key] = u''
+ for key, item in result.items():
images = item.get('brandingSettings', {}).get('image', {})
- banners = ['bannerTvMediumImageUrl', 'bannerTvLowImageUrl', 'bannerTvImageUrl', 'bannerExternalUrl']
for banner in banners:
- image = images.get(banner, '')
- if image:
- result[key] = image
- break
+ image = images.get(banner)
+ if not image:
+ continue
+ result[key] = image
+ break
+ else:
+ # set an empty url
+ result[key] = ''
return result
@@ -262,7 +242,7 @@ def handle_error(self, json_data, suppress_errors=False):
message = context.localize(30731)
ok_dialog = True
- if reason == 'quotaExceeded' or reason == 'dailyLimitExceeded':
+ elif reason in {'quotaExceeded', 'dailyLimitExceeded'}:
message_timeout = 7000
if not suppress_errors:
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 b8859d7cb..a8e3c2d69 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
@@ -87,7 +87,7 @@ def add_urls(self, urls, provider, context):
def get_items(self, provider, context, title_required=True):
result = []
- if self._flatten and len(self._channel_ids) > 0:
+ if self._flatten and self._channel_ids:
# remove duplicates
self._channel_ids = list(set(self._channel_ids))
@@ -98,7 +98,7 @@ def get_items(self, provider, context, title_required=True):
channels_item.set_fanart(provider.get_fanart(context))
result.append(channels_item)
- if self._flatten and len(self._playlist_ids) > 0:
+ if self._flatten and self._playlist_ids:
# remove duplicates
self._playlist_ids = list(set(self._playlist_ids))
@@ -124,39 +124,42 @@ def get_video_items(self, provider, context, title_required=True):
incognito = str(context.get_param('incognito', False)).lower() == 'true'
use_play_data = not incognito
- if len(self._video_items) == 0:
+ if not self._video_items:
channel_id_dict = {}
utils.update_video_infos(provider, context, self._video_id_dict, None, channel_id_dict, use_play_data=use_play_data)
utils.update_fanarts(provider, context, channel_id_dict)
- for key in self._video_id_dict:
- video_item = self._video_id_dict[key]
- if not title_required or (title_required and video_item.get_title()):
- self._video_items.append(video_item)
+ self._video_items = [
+ video_item
+ for video_item in self._video_id_dict.values()
+ if not title_required or video_item.get_title()
+ ]
return self._video_items
def get_playlist_items(self, provider, context):
- if len(self._playlist_items) == 0:
+ if not self._playlist_items:
channel_id_dict = {}
utils.update_playlist_infos(provider, context, self._playlist_id_dict, channel_id_dict)
utils.update_fanarts(provider, context, channel_id_dict)
- for key in self._playlist_id_dict:
- playlist_item = self._playlist_id_dict[key]
- if playlist_item.get_name():
- self._playlist_items.append(playlist_item)
+ self._playlist_items = [
+ playlist_item
+ for playlist_item in self._playlist_id_dict.values()
+ if playlist_item.get_name()
+ ]
return self._playlist_items
def get_channel_items(self, provider, context):
- if len(self._channel_items) == 0:
+ if not self._channel_items:
channel_id_dict = {}
utils.update_fanarts(provider, context, channel_id_dict)
- for key in self._channel_id_dict:
- channel_item = self._channel_id_dict[key]
- if channel_item.get_name():
- self._channel_items.append(channel_item)
+ self._channel_items = [
+ channel_item
+ for channel_item in self._channel_id_dict.values()
+ if channel_item.get_name()
+ ]
return self._channel_items
diff --git a/resources/lib/youtube_plugin/youtube/helper/utils.py b/resources/lib/youtube_plugin/youtube/helper/utils.py
index e10804369..1539216f0 100644
--- a/resources/lib/youtube_plugin/youtube/helper/utils.py
+++ b/resources/lib/youtube_plugin/youtube/helper/utils.py
@@ -94,16 +94,23 @@ def make_comment_item(context, provider, snippet, uri, total_replies=0):
return comment_item
-def update_channel_infos(provider, context, channel_id_dict, subscription_id_dict=None, channel_items_dict=None):
- if subscription_id_dict is None:
- subscription_id_dict = {}
+def update_channel_infos(provider, context, channel_id_dict,
+ subscription_id_dict=None,
+ channel_items_dict=None,
+ data=None):
+ channel_ids = list(channel_id_dict)
+ if not channel_ids and not data:
+ return
+
+ if not data:
+ resource_manager = provider.get_resource_manager(context)
+ data = resource_manager.get_channels(channel_ids)
- channel_ids = list(channel_id_dict.keys())
- if len(channel_ids) == 0:
+ if not data:
return
- resource_manager = provider.get_resource_manager(context)
- channel_data = resource_manager.get_channels(channel_ids)
+ if subscription_id_dict is None:
+ subscription_id_dict = {}
filter_list = []
if context.get_path() == '/subscriptions/list/':
@@ -112,9 +119,10 @@ def update_channel_infos(provider, context, channel_id_dict, subscription_id_dic
filter_list = filter_string.split(',')
filter_list = [x.lower() for x in filter_list]
- thumb_size = context.get_settings().use_thumbnail_size()
- for channel_id in list(channel_data.keys()):
- yt_item = channel_data[channel_id]
+ thumb_size = context.get_settings().use_thumbnail_size
+ banners = ['bannerTvMediumImageUrl', 'bannerTvLowImageUrl', 'bannerTvImageUrl']
+
+ for channel_id, yt_item in data.items():
channel_item = channel_id_dict[channel_id]
snippet = yt_item['snippet']
@@ -144,17 +152,15 @@ def update_channel_infos(provider, context, channel_id_dict, subscription_id_dic
yt_context_menu.append_remove_my_subscriptions_filter(context_menu, provider, context, title)
else:
yt_context_menu.append_add_my_subscriptions_filter(context_menu, provider, context, title)
-
channel_item.set_context_menu(context_menu)
- fanart = u''
fanart_images = yt_item.get('brandingSettings', {}).get('image', {})
- banners = ['bannerTvMediumImageUrl', 'bannerTvLowImageUrl', 'bannerTvImageUrl']
for banner in banners:
- fanart = fanart_images.get(banner, u'')
+ fanart = fanart_images.get(banner)
if fanart:
break
-
+ else:
+ fanart = ''
channel_item.set_fanart(fanart)
# update channel mapping
@@ -164,21 +170,26 @@ def update_channel_infos(provider, context, channel_id_dict, subscription_id_dic
channel_items_dict[channel_id].append(channel_item)
-def update_playlist_infos(provider, context, playlist_id_dict, channel_items_dict=None):
- playlist_ids = list(playlist_id_dict.keys())
- if len(playlist_ids) == 0:
+def update_playlist_infos(provider, context, playlist_id_dict,
+ channel_items_dict=None,
+ data=None):
+ playlist_ids = list(playlist_id_dict)
+ if not playlist_ids and not data:
return
- resource_manager = provider.get_resource_manager(context)
- access_manager = context.get_access_manager()
- playlist_data = resource_manager.get_playlists(playlist_ids)
+ if not data:
+ resource_manager = provider.get_resource_manager(context)
+ data = resource_manager.get_playlists(playlist_ids)
+
+ if not data:
+ return
+ access_manager = context.get_access_manager()
custom_watch_later_id = access_manager.get_watch_later_id()
custom_history_id = access_manager.get_watch_history_id()
-
thumb_size = context.get_settings().use_thumbnail_size()
- for playlist_id in list(playlist_data.keys()):
- yt_item = playlist_data[playlist_id]
+
+ for playlist_id, yt_item in data.items():
playlist_item = playlist_id_dict[playlist_id]
snippet = yt_item['snippet']
@@ -221,7 +232,7 @@ def update_playlist_infos(provider, context, playlist_id_dict, channel_items_dic
else:
yt_context_menu.append_set_as_history(context_menu, provider, context, playlist_id, title)
- if len(context_menu) > 0:
+ if context_menu:
playlist_item.set_context_menu(context_menu)
# update channel mapping
@@ -231,25 +242,37 @@ def update_playlist_infos(provider, context, playlist_id_dict, channel_items_dic
channel_items_dict[channel_id].append(playlist_item)
-def update_video_infos(provider, context, video_id_dict, playlist_item_id_dict=None, channel_items_dict=None, live_details=False, use_play_data=True):
- settings = context.get_settings()
- ui = context.get_ui()
+def update_video_infos(provider, context, video_id_dict,
+ playlist_item_id_dict=None,
+ channel_items_dict=None,
+ live_details=False,
+ use_play_data=True,
+ data=None):
+ video_ids = list(video_id_dict)
+ if not video_ids and not data:
+ return
+
+ if not data:
+ resource_manager = provider.get_resource_manager(context)
+ data = resource_manager.get_videos(video_ids,
+ live_details=live_details,
+ suppress_errors=True)
- video_ids = list(video_id_dict.keys())
- if len(video_ids) == 0:
+ if not data:
return
if not playlist_item_id_dict:
playlist_item_id_dict = {}
- resource_manager = provider.get_resource_manager(context)
- video_data = resource_manager.get_videos(video_ids, live_details=live_details,
- suppress_errors=True)
+ settings = context.get_settings()
+ show_channel_name = settings.get_bool('youtube.view.description.show_channel_name', True)
+ alternate_player = settings.is_support_alternative_player_enabled()
thumb_size = settings.use_thumbnail_size()
thumb_stamp = get_thumb_timestamp()
- for video_id in list(video_data.keys()):
+ ui = context.get_ui()
+
+ for video_id, yt_item in data.items():
datetime = None
- yt_item = video_data.get(video_id)
video_item = video_id_dict[video_id]
# set mediatype
@@ -259,36 +282,36 @@ def update_video_infos(provider, context, video_id_dict, playlist_item_id_dict=N
continue
snippet = yt_item['snippet'] # crash if not conform
- play_data = yt_item['play_data']
+ play_data = use_play_data and yt_item.get('play_data')
video_item.live = snippet.get('liveBroadcastContent') == 'live'
# duration
- if not video_item.live and use_play_data and play_data.get('total_time'):
- video_item.set_duration_from_seconds(float(play_data.get('total_time')))
+ if not video_item.live and play_data and 'total_time' in play_data:
+ duration = float(play_data['total_time'] or 0)
else:
- duration = yt_item.get('contentDetails', {}).get('duration', '')
+ duration = yt_item.get('contentDetails', {}).get('duration')
if duration:
- duration = utils.datetime_parser.parse(duration)
- # we subtract 1 seconds because YouTube returns +1 second to much
- video_item.set_duration_from_seconds(duration.seconds - 1)
+ # subtract 1s because YouTube duration is +1s too long
+ duration = utils.datetime_parser.parse(duration).seconds - 1
+ if duration:
+ video_item.set_duration_from_seconds(duration)
- if not video_item.live and use_play_data:
- # play count
- if play_data.get('play_count'):
- video_item.set_play_count(int(play_data.get('play_count')))
+ if not video_item.live and play_data:
+ if 'play_count' in play_data:
+ video_item.set_play_count(play_data['play_count'])
- if play_data.get('played_percent'):
- video_item.set_start_percent(play_data.get('played_percent'))
+ if 'played_percent' in play_data:
+ video_item.set_start_percent(play_data['played_percent'])
- if play_data.get('played_time'):
- video_item.set_start_time(play_data.get('played_time'))
+ if 'played_time' in play_data:
+ video_item.set_start_time(play_data['played_time'])
- if play_data.get('last_played'):
- video_item.set_last_played(play_data.get('last_played'))
+ if 'last_played' in play_data:
+ video_item.set_last_played(play_data['last_played'])
elif video_item.live:
video_item.set_play_count(0)
- scheduled_start = video_data[video_id].get('liveStreamingDetails', {}).get('scheduledStartTime')
+ scheduled_start = yt_item.get('liveStreamingDetails', {}).get('scheduledStartTime')
if scheduled_start:
datetime = utils.datetime_parser.parse(scheduled_start)
video_item.set_scheduled_start_utc(datetime)
@@ -324,7 +347,7 @@ def update_video_infos(provider, context, video_id_dict, playlist_item_id_dict=N
# plot
channel_name = snippet.get('channelTitle', '')
description = kodion.utils.strip_html_from_text(snippet['description'])
- if channel_name and settings.get_bool('youtube.view.description.show_channel_name', True):
+ if show_channel_name and channel_name:
description = '%s[CR][CR]%s' % (ui.uppercase(ui.bold(channel_name)), description)
video_item.set_studio(channel_name)
# video_item.add_cast(channel_name)
@@ -384,7 +407,7 @@ def update_video_infos(provider, context, video_id_dict, playlist_item_id_dict=N
yt_context_menu.append_play_all_from_playlist(context_menu, provider, context, playlist_id)
# 'play with...' (external player)
- if settings.is_support_alternative_player_enabled():
+ if alternate_player:
yt_context_menu.append_play_with(context_menu, provider, context)
if provider.is_logged_in():
@@ -424,7 +447,7 @@ def update_video_infos(provider, context, video_id_dict, playlist_item_id_dict=N
video_item.set_subscription_id(channel_id)
yt_context_menu.append_subscribe_to_channel(context_menu, provider, context, channel_id, channel_name)
- if not video_item.live and use_play_data:
+ if not video_item.live and play_data:
if play_data.get('play_count') is None or int(play_data.get('play_count')) == 0:
yt_context_menu.append_mark_watched(context_menu, provider, context, video_id)
else:
@@ -447,7 +470,7 @@ def update_video_infos(provider, context, video_id_dict, playlist_item_id_dict=N
yt_context_menu.append_play_ask_for_quality(context_menu, provider, context, video_id)
- if len(context_menu) > 0:
+ if context_menu:
video_item.set_context_menu(context_menu, replace=replace_context_menu)
@@ -522,7 +545,7 @@ def update_play_info(provider, context, video_id, video_item, video_stream, use_
yt_item = video_data[video_id]
snippet = yt_item['snippet'] # crash if not conform
- play_data = yt_item['play_data']
+ play_data = use_play_data and yt_item.get('play_data')
video_item.live = snippet.get('liveBroadcastContent') == 'live'
# set the title
@@ -530,28 +553,28 @@ def update_play_info(provider, context, video_id, video_item, video_stream, use_
video_item.set_title(snippet['title'])
# duration
- if not video_item.live and use_play_data and play_data.get('total_time'):
- video_item.set_duration_from_seconds(float(play_data.get('total_time')))
+ if not video_item.live and play_data and 'total_time' in play_data:
+ duration = float(play_data['total_time'] or 0)
else:
- duration = yt_item.get('contentDetails', {}).get('duration', '')
+ duration = yt_item.get('contentDetails', {}).get('duration')
if duration:
- duration = utils.datetime_parser.parse(duration)
- # we subtract 1 seconds because YouTube returns +1 second to much
- video_item.set_duration_from_seconds(duration.seconds - 1)
+ # subtract 1s because YouTube duration is +1s too long
+ duration = utils.datetime_parser.parse(duration).seconds - 1
+ if duration:
+ video_item.set_duration_from_seconds(duration)
- if not video_item.live and use_play_data:
- # play count
- if play_data.get('play_count'):
- video_item.set_play_count(int(play_data.get('play_count')))
+ if not video_item.live and play_data:
+ if 'play_count' in play_data:
+ video_item.set_play_count(play_data['play_count'])
- if play_data.get('played_percent'):
- video_item.set_start_percent(play_data.get('played_percent'))
+ if 'played_percent' in play_data:
+ video_item.set_start_percent(play_data['played_percent'])
- if play_data.get('played_time'):
- video_item.set_start_time(play_data.get('played_time'))
+ if 'played_time' in play_data:
+ video_item.set_start_time(play_data['played_time'])
- if play_data.get('last_played'):
- video_item.set_last_played(play_data.get('last_played'))
+ if 'last_played' in play_data:
+ video_item.set_last_played(play_data['last_played'])
# plot
channel_name = snippet.get('channelTitle', '')
@@ -581,19 +604,23 @@ def update_play_info(provider, context, video_id, video_item, video_stream, use_
return video_item
-def update_fanarts(provider, context, channel_items_dict):
+def update_fanarts(provider, context, channel_items_dict, data=None):
# at least we need one channel id
- channel_ids = list(channel_items_dict.keys())
- if len(channel_ids) == 0:
+ channel_ids = list(channel_items_dict)
+ if not channel_ids and not data:
return
- fanarts = provider.get_resource_manager(context).get_fanarts(channel_ids)
+ if not data:
+ resource_manager = provider.get_resource_manager(context)
+ data = resource_manager.get_fanarts(channel_ids)
+
+ if not data:
+ return
- for channel_id in channel_ids:
- channel_items = channel_items_dict[channel_id]
+ for channel_id, channel_items in channel_items_dict.items():
for channel_item in channel_items:
# only set not empty fanarts
- fanart = fanarts.get(channel_id, '')
+ fanart = data.get(channel_id, '')
if fanart:
channel_item.set_fanart(fanart)
@@ -623,11 +650,15 @@ def get_shelf_index_by_title(context, json_data, shelf_title):
title = shelf.get('shelfRenderer', {}).get('title', {}).get('runs', [{}])[0].get('text', '')
if title.lower() == shelf_title.lower():
shelf_index = idx
- context.log_debug('Found shelf index |{index}| for |{title}|'.format(index=str(shelf_index), title=shelf_title))
+ context.log_debug('Found shelf index |{index}| for |{title}|'.format(
+ index=shelf_index, title=shelf_title
+ ))
break
if shelf_index is not None and 0 > shelf_index >= len(contents):
- context.log_debug('Shelf index |{index}| out of range |0-{content_length}|'.format(index=str(shelf_index), content_length=str(len(contents))))
+ context.log_debug('Shelf index |{index}| out of range |0-{content_length}|'.format(
+ index=shelf_index, content_length=len(contents)
+ ))
shelf_index = None
return shelf_index
diff --git a/resources/lib/youtube_plugin/youtube/helper/v3.py b/resources/lib/youtube_plugin/youtube/helper/v3.py
index 403cfc5b4..186a60c33 100644
--- a/resources/lib/youtube_plugin/youtube/helper/v3.py
+++ b/resources/lib/youtube_plugin/youtube/helper/v3.py
@@ -27,7 +27,7 @@ def _process_list_response(provider, context, json_data):
thumb_size = context.get_settings().use_thumbnail_size()
yt_items = json_data.get('items', [])
- if len(yt_items) == 0:
+ if not yt_items:
context.log_warning('List of search result is empty')
return result
@@ -48,9 +48,9 @@ def _process_list_response(provider, context, json_data):
image = utils.get_thumbnail(thumb_size, snippet.get('thumbnails', {}))
item_params = {'video_id': video_id}
if incognito:
- item_params.update({'incognito': incognito})
+ item_params['incognito'] = incognito
if addon_id:
- item_params.update({'addon_id': addon_id})
+ item_params['addon_id'] = addon_id
item_uri = context.create_uri(['play'], item_params)
video_item = items.VideoItem(title, item_uri, image=image)
video_item.video_id = video_id
@@ -66,9 +66,9 @@ def _process_list_response(provider, context, json_data):
image = utils.get_thumbnail(thumb_size, snippet.get('thumbnails', {}))
item_params = {}
if incognito:
- item_params.update({'incognito': incognito})
+ item_params['incognito'] = incognito
if addon_id:
- item_params.update({'addon_id': addon_id})
+ item_params['addon_id'] = addon_id
item_uri = context.create_uri(['channel', channel_id], item_params)
channel_item = items.DirectoryItem(title, item_uri, image=image)
channel_item.set_fanart(provider.get_fanart(context))
@@ -86,9 +86,9 @@ def _process_list_response(provider, context, json_data):
title = snippet.get('title', context.localize(provider.LOCAL_MAP['youtube.untitled']))
item_params = {'guide_id': guide_id}
if incognito:
- item_params.update({'incognito': incognito})
+ item_params['incognito'] = incognito
if addon_id:
- item_params.update({'addon_id': addon_id})
+ item_params['addon_id'] = addon_id
item_uri = context.create_uri(['special', 'browse_channels'], item_params)
guide_item = items.DirectoryItem(title, item_uri)
guide_item.set_fanart(provider.get_fanart(context))
@@ -100,9 +100,9 @@ def _process_list_response(provider, context, json_data):
channel_id = snippet['resourceId']['channelId']
item_params = {}
if incognito:
- item_params.update({'incognito': incognito})
+ item_params['incognito'] = incognito
if addon_id:
- item_params.update({'addon_id': addon_id})
+ item_params['addon_id'] = addon_id
item_uri = context.create_uri(['channel', channel_id], item_params)
channel_item = items.DirectoryItem(title, item_uri, image=image)
channel_item.set_fanart(provider.get_fanart(context))
@@ -125,9 +125,9 @@ def _process_list_response(provider, context, json_data):
channel_id = 'mine'
item_params = {}
if incognito:
- item_params.update({'incognito': incognito})
+ item_params['incognito'] = incognito
if addon_id:
- item_params.update({'addon_id': addon_id})
+ item_params['addon_id'] = addon_id
item_uri = context.create_uri(['channel', channel_id, 'playlist', playlist_id], item_params)
playlist_item = items.DirectoryItem(title, item_uri, image=image)
playlist_item.set_fanart(provider.get_fanart(context))
@@ -144,9 +144,9 @@ def _process_list_response(provider, context, json_data):
image = utils.get_thumbnail(thumb_size, snippet.get('thumbnails', {}))
item_params = {'video_id': video_id}
if incognito:
- item_params.update({'incognito': incognito})
+ item_params['incognito'] = incognito
if addon_id:
- item_params.update({'addon_id': addon_id})
+ item_params['addon_id'] = addon_id
item_uri = context.create_uri(['play'], item_params)
video_item = items.VideoItem(title, item_uri, image=image)
video_item.video_id = video_id
@@ -175,9 +175,9 @@ def _process_list_response(provider, context, json_data):
image = utils.get_thumbnail(thumb_size, snippet.get('thumbnails', {}))
item_params = {'video_id': video_id}
if incognito:
- item_params.update({'incognito': incognito})
+ item_params['incognito'] = incognito
if addon_id:
- item_params.update({'addon_id': addon_id})
+ item_params['addon_id'] = addon_id
item_uri = context.create_uri(['play'], item_params)
video_item = items.VideoItem(title, item_uri, image=image)
video_item.video_id = video_id
@@ -213,9 +213,9 @@ def _process_list_response(provider, context, json_data):
image = utils.get_thumbnail(thumb_size, snippet.get('thumbnails', {}))
item_params = {'video_id': video_id}
if incognito:
- item_params.update({'incognito': incognito})
+ item_params['incognito'] = incognito
if addon_id:
- item_params.update({'addon_id': addon_id})
+ item_params['addon_id'] = addon_id
item_uri = context.create_uri(['play'], item_params)
video_item = items.VideoItem(title, item_uri, image=image)
video_item.video_id = video_id
@@ -238,9 +238,9 @@ def _process_list_response(provider, context, json_data):
# channel_name = snippet.get('channelTitle', '')
item_params = {}
if incognito:
- item_params.update({'incognito': incognito})
+ item_params['incognito'] = incognito
if addon_id:
- item_params.update({'addon_id': addon_id})
+ item_params['addon_id'] = addon_id
item_uri = context.create_uri(['channel', channel_id, 'playlist', playlist_id], item_params)
playlist_item = items.DirectoryItem(title, item_uri, image=image)
playlist_item.set_fanart(provider.get_fanart(context))
@@ -253,9 +253,9 @@ def _process_list_response(provider, context, json_data):
image = utils.get_thumbnail(thumb_size, snippet.get('thumbnails', {}))
item_params = {}
if incognito:
- item_params.update({'incognito': incognito})
+ item_params['incognito'] = incognito
if addon_id:
- item_params.update({'addon_id': addon_id})
+ item_params['addon_id'] = addon_id
item_uri = context.create_uri(['channel', channel_id], item_params)
channel_item = items.DirectoryItem(title, item_uri, image=image)
channel_item.set_fanart(provider.get_fanart(context))
@@ -352,7 +352,7 @@ def handle_error(provider, context, json_data):
message = context.localize(provider.LOCAL_MAP['youtube.api.key.incorrect'])
message_timeout = 7000
- if reason == 'quotaExceeded' or reason == 'dailyLimitExceeded':
+ if reason in {'quotaExceeded', 'dailyLimitExceeded'}:
message_timeout = 7000
if ok_dialog:
@@ -366,23 +366,7 @@ def handle_error(provider, context, json_data):
def _parse_kind(item):
- kind = item.get('kind', '').split('#')
-
- if len(kind) < 1:
- return False, ''
-
- if len(kind) < 2:
- try:
- _ = kind.index('youtube')
- return True, ''
- except ValueError:
- return False, str(kind[0]).lower()
-
- try:
- idx = kind.index('youtube')
- if idx == 0:
- return True, str(kind[1]).lower()
- except ValueError:
- pass
-
- return False, str(kind[1]).lower()
+ parts = item.get('kind', '').split('#')
+ is_youtube = parts[0] == 'youtube'
+ kind = parts[1 if len(parts) > 1 else 0].lower()
+ return is_youtube, kind
diff --git a/resources/lib/youtube_plugin/youtube/helper/video_info.py b/resources/lib/youtube_plugin/youtube/helper/video_info.py
index 42d7b7826..0c6fc0040 100644
--- a/resources/lib/youtube_plugin/youtube/helper/video_info.py
+++ b/resources/lib/youtube_plugin/youtube/helper/video_info.py
@@ -2143,13 +2143,15 @@ def _filter_group(previous_group, previous_stream, item):
main_stream['multi_audio'] = True
filepath = '{0}{1}.mpd'.format(basepath, self.video_id)
- success = None
- with xbmcvfs.File(filepath, 'w') as mpd_file:
- success = mpd_file.write(str(out))
- if not success:
- return None, None
- return 'http://{0}:{1}/{2}.mpd'.format(
- _settings.httpd_listen(for_request=True),
- _settings.httpd_port(),
- self.video_id
- ), main_stream
+ try:
+ with xbmcvfs.File(filepath, 'w') as mpd_file:
+ success = mpd_file.write(str(out))
+ except (IOError, OSError):
+ success = False
+ if success:
+ return 'http://{0}:{1}/{2}.mpd'.format(
+ _settings.httpd_listen(for_request=True),
+ _settings.httpd_port(),
+ self.video_id
+ ), main_stream
+ return None, None
diff --git a/resources/lib/youtube_plugin/youtube/helper/yt_play.py b/resources/lib/youtube_plugin/youtube/helper/yt_play.py
index e62163555..e4e2ec096 100644
--- a/resources/lib/youtube_plugin/youtube/helper/yt_play.py
+++ b/resources/lib/youtube_plugin/youtube/helper/yt_play.py
@@ -53,7 +53,7 @@ def play_video(provider, context):
context.log_error(traceback.print_exc())
return False
- if len(video_streams) == 0:
+ if not video_streams:
message = context.localize(provider.LOCAL_MAP['youtube.error.no_video_streams_found'])
context.get_ui().show_notification(message, time_milliseconds=5000)
return False
diff --git a/resources/lib/youtube_plugin/youtube/helper/yt_specials.py b/resources/lib/youtube_plugin/youtube/helper/yt_specials.py
index c7b5c3fe8..8a9c0e114 100644
--- a/resources/lib/youtube_plugin/youtube/helper/yt_specials.py
+++ b/resources/lib/youtube_plugin/youtube/helper/yt_specials.py
@@ -183,7 +183,7 @@ def _extract_urls(_video_id):
progress_dialog.close()
- if len(result) == 0:
+ if not result:
progress_dialog.close()
context.get_ui().on_ok(title=context.localize(provider.LOCAL_MAP['youtube.video.description.links']),
text=context.localize(
@@ -253,13 +253,13 @@ def _display_playlists(_playlist_ids):
channel_ids = context.get_param('channel_ids', '')
if channel_ids:
channel_ids = channel_ids.split(',')
- if len(channel_ids) > 0:
+ if channel_ids:
return _display_channels(channel_ids)
playlist_ids = context.get_param('playlist_ids', '')
if playlist_ids:
playlist_ids = playlist_ids.split(',')
- if len(playlist_ids) > 0:
+ if playlist_ids:
return _display_playlists(playlist_ids)
context.log_error('Missing video_id or playlist_ids for description links')
diff --git a/resources/lib/youtube_plugin/youtube/provider.py b/resources/lib/youtube_plugin/youtube/provider.py
index 280062ad3..7ede578b6 100644
--- a/resources/lib/youtube_plugin/youtube/provider.py
+++ b/resources/lib/youtube_plugin/youtube/provider.py
@@ -226,7 +226,7 @@ def get_dev_config(context, addon_id, dev_configs):
_dev_config = context.get_ui().get_home_window_property('configs')
context.get_ui().clear_home_window_property('configs')
- dev_config = dict()
+ dev_config = {}
if _dev_config is not None:
context.log_debug('Using window property for developer keys is deprecated, instead use the youtube_registration module.')
try:
@@ -246,7 +246,7 @@ def get_dev_config(context, addon_id, dev_configs):
or not dev_config['main'].get('id') or not dev_config['main'].get('secret'):
context.log_error('Error loading developer config: |invalid structure| '
'expected: |{"origin": ADDON_ID, "main": {"system": SYSTEM_NAME, "key": API_KEY, "id": CLIENT_ID, "secret": CLIENT_SECRET}}|')
- return dict()
+ return {}
dev_origin = dev_config['origin']
dev_main = dev_config['main']
dev_system = dev_main['system']
@@ -261,7 +261,7 @@ def get_dev_config(context, addon_id, dev_configs):
context.log_debug('Using developer config: origin: |{0}| system |{1}|'.format(dev_origin, dev_system))
return {'origin': dev_origin, 'main': {'id': dev_id, 'secret': dev_secret, 'key': dev_key, 'system': dev_system}}
- return dict()
+ return {}
def reset_client(self):
self._client = None
@@ -285,12 +285,10 @@ def get_client(self, context):
dev_id = context.get_param('addon_id', None)
dev_configs = YouTube.CONFIGS.get('developer')
dev_config = self.get_dev_config(context, dev_id, dev_configs)
- dev_keys = dict()
- if dev_config:
- dev_keys = dev_config.get('main')
+ dev_keys = dev_config.get('main') if dev_config else None
client = None
- refresh_tokens = list()
+ refresh_tokens = []
if dev_id:
dev_origin = dev_config.get('origin') if dev_config.get('origin') else dev_id
@@ -308,13 +306,13 @@ def get_client(self, context):
if len(access_tokens) != 2 or access_manager.is_dev_access_token_expired(dev_id):
# reset access_token
access_manager.update_dev_access_token(dev_id, '')
- access_tokens = list()
+ access_tokens = []
else:
access_tokens = access_manager.get_access_token().split('|')
if len(access_tokens) != 2 or access_manager.is_access_token_expired():
# reset access_token
access_manager.update_access_token('')
- access_tokens = list()
+ access_tokens = []
if dev_id:
if dev_keys:
@@ -405,7 +403,7 @@ def get_client(self, context):
context.log_debug('User is logged in' if self._is_logged_in else
'User is not logged in')
- if len(access_tokens) == 0:
+ if not access_tokens:
access_tokens = ['', '']
client.set_access_token(access_token=access_tokens[1])
client.set_access_token_tv(access_token_tv=access_tokens[0])
@@ -445,7 +443,7 @@ def on_uri2addon(self, context, re_match):
url_converter = UrlToItemConverter(flatten=True)
url_converter.add_urls([res_url], self, context)
items = url_converter.get_items(self, context, title_required=False)
- if len(items) > 0:
+ if items:
return items[0]
return False
@@ -596,7 +594,7 @@ def _on_channel(self, context, re_match):
# we correct the channel id based on the username
items = json_data.get('items', [])
- if len(items) > 0:
+ if items:
if method == 'user':
channel_id = items[0]['id']
else:
@@ -666,7 +664,7 @@ def _on_my_location(self, context, re_match):
self.set_content_type(context, kodion.constants.content_type.FILES)
settings = context.get_settings()
- result = list()
+ result = []
# search
search_item = kodion.items.SearchItem(context, image=context.create_resource_path('media', 'search.png'),
@@ -1199,8 +1197,7 @@ def maintenance_actions(self, context, re_match):
client = self.get_client(context)
if access_manager.has_refresh_token():
refresh_tokens = access_manager.get_refresh_token().split('|')
- refresh_tokens = list(set(refresh_tokens))
- for refresh_token in refresh_tokens:
+ for refresh_token in set(refresh_tokens):
try:
client.revoke(refresh_token)
except:
@@ -1614,8 +1611,7 @@ def set_content_type(context, content_type):
kodion.constants.sort_method.DATE)
def handle_exception(self, context, exception_to_handle):
- if (isinstance(exception_to_handle, InvalidGrant) or
- isinstance(exception_to_handle, LoginException)):
+ if isinstance(exception_to_handle, (InvalidGrant, LoginException)):
ok_dialog = False
message_timeout = 5000
diff --git a/resources/lib/youtube_requests.py b/resources/lib/youtube_requests.py
index 0a4217592..513546a64 100644
--- a/resources/lib/youtube_requests.py
+++ b/resources/lib/youtube_requests.py
@@ -78,7 +78,7 @@ def get_videos(video_id, addon_id=None):
if not handle_error(context, json_data):
return [json_data]
- return [item for item in json_data.get('items', [])]
+ return json_data.get('items', [])
def get_activities(channel_id, page_token='', all_pages=False, addon_id=None):
@@ -105,8 +105,7 @@ def get_items(_page_token=''):
if not handle_error(context, json_data):
return [json_data]
- for item in json_data.get('items', []):
- items.append(item)
+ items.extend(json_data.get('items', []))
error = False
next_page_token = json_data.get('nextPageToken')
@@ -151,8 +150,7 @@ def get_items(_page_token=''):
if not handle_error(context, json_data):
return [json_data]
- for item in json_data.get('items', []):
- items.append(item)
+ items.extend(json_data.get('items', []))
error = False
next_page_token = json_data.get('nextPageToken')
@@ -189,7 +187,7 @@ def get_channel_id(channel_name, addon_id=None):
if not handle_error(context, json_data):
return [json_data]
- return [item for item in json_data.get('items', [])]
+ return json_data.get('items', [])
def get_channels(channel_id, addon_id=None):
@@ -209,7 +207,7 @@ def get_channels(channel_id, addon_id=None):
if not handle_error(context, json_data):
return [json_data]
- return [item for item in json_data.get('items', [])]
+ return json_data.get('items', [])
def get_channel_sections(channel_id, addon_id=None):
@@ -229,7 +227,7 @@ def get_channel_sections(channel_id, addon_id=None):
if not handle_error(context, json_data):
return [json_data]
- return [item for item in json_data.get('items', [])]
+ return json_data.get('items', [])
def get_playlists_of_channel(channel_id, page_token='', all_pages=False, addon_id=None):
@@ -257,8 +255,7 @@ def get_items(_page_token=''):
if not handle_error(context, json_data):
return [json_data]
- for item in json_data.get('items', []):
- items.append(item)
+ items.extend(json_data.get('items', []))
error = False
next_page_token = json_data.get('nextPageToken')
@@ -295,7 +292,7 @@ def get_playlists(playlist_id, addon_id=None):
if not handle_error(context, json_data):
return [json_data]
- return [item for item in json_data.get('items', [])]
+ return json_data.get('items', [])
def get_related_videos(video_id, page_token='', addon_id=None):
@@ -322,9 +319,8 @@ def get_items(_page_token=''):
if not handle_error(context, json_data):
return [json_data]
- for item in json_data.get('items', []):
- if 'snippet' in item:
- items.append(item)
+ items.extend([item for item in json_data.get('items', [])
+ if 'snippet' in item])
error = False
next_page_token = json_data.get('nextPageToken')
@@ -376,8 +372,7 @@ def get_items(_page_token=''):
if not handle_error(context, json_data):
return [json_data]
- for item in json_data.get('items', []):
- items.append(item)
+ items.extend(json_data.get('items', []))
error = False
next_page_token = json_data.get('nextPageToken')
From 97dfa3a32734d56a46ecafa0212f48ba9df17744 Mon Sep 17 00:00:00 2001
From: MoojMidge <56883549+MoojMidge@users.noreply.github.com>
Date: Tue, 31 Oct 2023 15:42:59 +1100
Subject: [PATCH 014/141] Update release names
---
resources/lib/youtube_plugin/kodion/utils/system_version.py | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/resources/lib/youtube_plugin/kodion/utils/system_version.py b/resources/lib/youtube_plugin/kodion/utils/system_version.py
index 09f0553fe..de01118ce 100644
--- a/resources/lib/youtube_plugin/kodion/utils/system_version.py
+++ b/resources/lib/youtube_plugin/kodion/utils/system_version.py
@@ -45,9 +45,9 @@ def __init__(self, version, releasename, appname):
self._releasename = 'Unknown Release'
if self._version >= (21, 0):
- self._releasename = 'O*****'
+ self._releasename = 'Omega'
elif self._version >= (20, 0):
- self._releasename = 'N*****'
+ self._releasename = 'Nexus'
elif self._version >= (19, 0):
self._releasename = 'Matrix'
elif self._version >= (18, 0):
From 86d1792d3039520b50dbb85d159313a9a7755c09 Mon Sep 17 00:00:00 2001
From: MoojMidge <56883549+MoojMidge@users.noreply.github.com>
Date: Tue, 31 Oct 2023 16:01:03 +1100
Subject: [PATCH 015/141] ISA - fix logic & naming for text, vars and methods
- IA to ISA, ia to isa, adaptive to isa
- MPD to ISA, mpd to isa when non MPEG-DASH specific
- new HLS/hls variables and methods for HLS specific functionality
---
.../resource.language.en_au/strings.po | 14 ++---
.../resource.language.en_gb/strings.po | 14 ++---
.../resource.language.en_nz/strings.po | 24 ++++-----
.../resource.language.en_us/strings.po | 14 ++---
.../kodion/constants/const_settings.py | 4 +-
.../kodion/impl/abstract_settings.py | 20 +++----
.../kodion/impl/xbmc/xbmc_context.py | 14 ++---
.../kodion/impl/xbmc/xbmc_items.py | 53 ++++++++-----------
.../youtube_plugin/kodion/items/video_item.py | 20 +++++--
.../kodion/utils/http_server.py | 4 +-
.../youtube_plugin/kodion/utils/methods.py | 2 +-
.../youtube_plugin/kodion/utils/monitor.py | 2 +-
.../youtube_plugin/youtube/helper/utils.py | 7 ++-
.../youtube/helper/video_info.py | 8 +--
.../lib/youtube_plugin/youtube/provider.py | 4 +-
resources/settings.xml | 31 ++++++-----
16 files changed, 120 insertions(+), 115 deletions(-)
diff --git a/resources/language/resource.language.en_au/strings.po b/resources/language/resource.language.en_au/strings.po
index e83f6d632..287c9965c 100644
--- a/resources/language/resource.language.en_au/strings.po
+++ b/resources/language/resource.language.en_au/strings.po
@@ -53,7 +53,7 @@ msgstr ""
# empty strings from id 30003 to 30006
msgctxt "#30007"
-msgid "Use MPEG-DASH"
+msgid "Use InputStream Adaptive"
msgstr ""
msgctxt "#30008"
@@ -614,7 +614,7 @@ msgid "Force SSL certificate verification"
msgstr ""
msgctxt "#30579"
-msgid "MPEG-DASH is enabled in the YouTube settings, however InputStream Adaptive appears to be disabled. Would you like to enable InputStream Adaptive now?"
+msgid "InputStream Adaptive is activated in the YouTube settings, however the add-on has been disabled. Would you like to enable InputStream Adaptive now?"
msgstr ""
msgctxt "#30580"
@@ -766,11 +766,11 @@ msgid "Must be signed in."
msgstr ""
msgctxt "#30617"
-msgid "MPEG-DASH"
+msgid "InputStream Adaptive"
msgstr ""
msgctxt "#30618"
-msgid "Enable mpeg-dash proxy"
+msgid "Enable MPEG-DASH proxy"
msgstr ""
msgctxt "#30619"
@@ -782,7 +782,7 @@ msgid "Port %s already in use. Cannot start http server."
msgstr ""
msgctxt "#30621"
-msgid "Proxy is required for mpeg-dash vods (see HTTP Server)"
+msgid "Proxy is required for MPEG-DASH VODs (see HTTP Server)"
msgstr ""
msgctxt "#30622"
@@ -1050,7 +1050,7 @@ msgid "data cache"
msgstr ""
msgctxt "#30688"
-msgid "Use for videos"
+msgid "Use MPEG-DASH for videos"
msgstr ""
msgctxt "#30689"
@@ -1190,7 +1190,7 @@ msgid "Enable HDR video"
msgstr ""
msgctxt "#30723"
-msgid "Proxy is required for mpeg-dash vods (see HTTP Server)[CR]> 1080p and HDR requires InputStream Adaptive >= 2.3.14"
+msgid "Proxy is required for MPEG-DASH VODs (see HTTP Server)[CR]HDR and >1080p video requires InputStream Adaptive >= 2.3.14"
msgstr ""
msgctxt "#30724"
diff --git a/resources/language/resource.language.en_gb/strings.po b/resources/language/resource.language.en_gb/strings.po
index 6856bd109..a78adb225 100644
--- a/resources/language/resource.language.en_gb/strings.po
+++ b/resources/language/resource.language.en_gb/strings.po
@@ -53,7 +53,7 @@ msgstr ""
# empty strings from id 30003 to 30006
msgctxt "#30007"
-msgid "Use MPEG-DASH"
+msgid "Use InputStream Adaptive"
msgstr ""
msgctxt "#30008"
@@ -614,7 +614,7 @@ msgid "Force SSL certificate verification"
msgstr ""
msgctxt "#30579"
-msgid "MPEG-DASH is enabled in the YouTube settings, however InputStream Adaptive appears to be disabled. Would you like to enable InputStream Adaptive now?"
+msgid "InputStream Adaptive is activated in the YouTube settings, however the add-on has been disabled. Would you like to enable InputStream Adaptive now?"
msgstr ""
msgctxt "#30580"
@@ -766,11 +766,11 @@ msgid "Must be signed in."
msgstr ""
msgctxt "#30617"
-msgid "MPEG-DASH"
+msgid "InputStream Adaptive"
msgstr ""
msgctxt "#30618"
-msgid "Enable mpeg-dash proxy"
+msgid "Enable MPEG-DASH proxy"
msgstr ""
msgctxt "#30619"
@@ -782,7 +782,7 @@ msgid "Port %s already in use. Cannot start http server."
msgstr ""
msgctxt "#30621"
-msgid "Proxy is required for mpeg-dash vods (see HTTP Server)"
+msgid "Proxy is required for MPEG-DASH VODs (see HTTP Server)"
msgstr ""
msgctxt "#30622"
@@ -1050,7 +1050,7 @@ msgid "data cache"
msgstr ""
msgctxt "#30688"
-msgid "Use for videos"
+msgid "Use MPEG-DASH for videos"
msgstr ""
msgctxt "#30689"
@@ -1190,7 +1190,7 @@ msgid "Enable HDR video"
msgstr ""
msgctxt "#30723"
-msgid "Proxy is required for mpeg-dash vods (see HTTP Server)[CR]> 1080p and HDR requires InputStream Adaptive >= 2.3.14"
+msgid "Proxy is required for MPEG-DASH VODs (see HTTP Server)[CR]HDR and >1080p video requires InputStream Adaptive >= 2.3.14"
msgstr ""
msgctxt "#30724"
diff --git a/resources/language/resource.language.en_nz/strings.po b/resources/language/resource.language.en_nz/strings.po
index a768a5e92..7dc6ba76d 100644
--- a/resources/language/resource.language.en_nz/strings.po
+++ b/resources/language/resource.language.en_nz/strings.po
@@ -52,7 +52,7 @@ msgstr ""
# empty strings from id 30003 to 30006
msgctxt "#30007"
-msgid "Use MPEG-DASH"
+msgid "Use InputStream Adaptive"
msgstr ""
msgctxt "#30008"
@@ -610,7 +610,7 @@ msgid "Force SSL certificate verification"
msgstr ""
msgctxt "#30579"
-msgid "MPEG-DASH is enabled in the YouTube settings, however InputStream Adaptive appears to be disabled. Would you like to enable InputStream Adaptive now?"
+msgid "InputStream Adaptive is activated in the YouTube settings, however the add-on has been disabled. Would you like to enable InputStream Adaptive now?"
msgstr ""
msgctxt "#30580"
@@ -762,11 +762,11 @@ msgid "Must be signed in."
msgstr ""
msgctxt "#30617"
-msgid "MPEG-DASH"
+msgid "InputStream Adaptive"
msgstr ""
msgctxt "#30618"
-msgid "Enable mpeg-dash proxy"
+msgid "Enable MPEG-DASH proxy"
msgstr ""
msgctxt "#30619"
@@ -778,7 +778,7 @@ msgid "Port %s already in use. Cannot start http server."
msgstr ""
msgctxt "#30621"
-msgid "Proxy is required for mpeg-dash vods (see HTTP Server)"
+msgid "Proxy is required for MPEG-DASH VODs (see HTTP Server)"
msgstr ""
msgctxt "#30622"
@@ -1046,7 +1046,7 @@ msgid "data cache"
msgstr ""
msgctxt "#30688"
-msgid "Use for videos"
+msgid "Use MPEG-DASH for videos"
msgstr ""
msgctxt "#30689"
@@ -1182,19 +1182,19 @@ msgid "Default to WEBM adaptation set (4K)"
msgstr ""
msgctxt "#30722"
-msgid "HDR"
+msgid "Enable HDR video"
msgstr ""
msgctxt "#30723"
-msgid "Proxy is required for mpeg-dash vods (see HTTP Server)[CR]> 1080p and HDR requires InputStream Adaptive >= 2.3.14"
+msgid "Proxy is required for MPEG-DASH VODs (see HTTP Server)[CR]HDR and >1080p video requires InputStream Adaptive >= 2.3.14"
msgstr ""
msgctxt "#30724"
-msgid "Limit to 30fps"
+msgid "Enable high framerate video"
msgstr ""
msgctxt "#30725"
-msgid "1440p (HD)"
+msgid "1440p (QHD)"
msgstr ""
msgctxt "#30726"
@@ -1202,11 +1202,11 @@ msgid "Uploads"
msgstr ""
msgctxt "#30727"
-msgid "Adaptive (MP4/H264)"
+msgid "Enable H.264 video"
msgstr ""
msgctxt "#30728"
-msgid "Adaptive (WEBM/VP9)"
+msgid "Enable VP9 video"
msgstr ""
msgctxt "#30729"
diff --git a/resources/language/resource.language.en_us/strings.po b/resources/language/resource.language.en_us/strings.po
index 8e30389e1..574fb67d4 100644
--- a/resources/language/resource.language.en_us/strings.po
+++ b/resources/language/resource.language.en_us/strings.po
@@ -54,7 +54,7 @@ msgstr "Password"
# empty strings from id 30003 to 30006
msgctxt "#30007"
-msgid "Use MPEG-DASH"
+msgid "Use InputStream Adaptive"
msgstr ""
msgctxt "#30008"
@@ -615,7 +615,7 @@ msgid "Force SSL certificate verification"
msgstr ""
msgctxt "#30579"
-msgid "MPEG-DASH is enabled in the YouTube settings, however InputStream Adaptive appears to be disabled. Would you like to enable InputStream Adaptive now?"
+msgid "InputStream Adaptive is activated in the YouTube settings, however the add-on has been disabled. Would you like to enable InputStream Adaptive now?"
msgstr ""
msgctxt "#30580"
@@ -767,11 +767,11 @@ msgid "Must be signed in."
msgstr ""
msgctxt "#30617"
-msgid "MPEG-DASH"
+msgid "InputStream Adaptive"
msgstr ""
msgctxt "#30618"
-msgid "Enable mpeg-dash proxy"
+msgid "Enable MPEG-DASH proxy"
msgstr ""
msgctxt "#30619"
@@ -783,7 +783,7 @@ msgid "Port %s already in use. Cannot start http server."
msgstr ""
msgctxt "#30621"
-msgid "Proxy is required for mpeg-dash vods (see HTTP Server)"
+msgid "Proxy is required for MPEG-DASH VODs (see HTTP Server)"
msgstr ""
msgctxt "#30622"
@@ -1051,7 +1051,7 @@ msgid "data cache"
msgstr ""
msgctxt "#30688"
-msgid "Use for videos"
+msgid "Use MPEG-DASH for videos"
msgstr ""
msgctxt "#30689"
@@ -1191,7 +1191,7 @@ msgid "Enable HDR video"
msgstr ""
msgctxt "#30723"
-msgid "Proxy is required for mpeg-dash vods (see HTTP Server)[CR]> 1080p and HDR requires InputStream Adaptive >= 2.3.14"
+msgid "Proxy is required for MPEG-DASH VODs (see HTTP Server)[CR]HDR and >1080p video requires InputStream Adaptive >= 2.3.14"
msgstr ""
msgctxt "#30724"
diff --git a/resources/lib/youtube_plugin/kodion/constants/const_settings.py b/resources/lib/youtube_plugin/kodion/constants/const_settings.py
index 6f64ca460..0ad5123bd 100644
--- a/resources/lib/youtube_plugin/kodion/constants/const_settings.py
+++ b/resources/lib/youtube_plugin/kodion/constants/const_settings.py
@@ -35,8 +35,8 @@
VIDEO_QUALITY = 'kodion.video.quality' # (int)
VIDEO_QUALITY_ASK = 'kodion.video.quality.ask' # (bool)
-USE_MPD = 'kodion.video.quality.mpd' # (bool)
-LIVE_STREAMS = 'kodion.mpd.live_stream.selection' # (int)
+USE_ISA = 'kodion.video.quality.isa' # (bool)
+LIVE_STREAMS = 'kodion.live_stream.selection' # (int)
MPD_VIDEOS = 'kodion.mpd.videos' # (bool)
MPD_QUALITY_SELECTION = 'kodion.mpd.quality.selection' # (int)
MPD_STREAM_FEATURES = 'kodion.mpd.stream.features' # (list[string])
diff --git a/resources/lib/youtube_plugin/kodion/impl/abstract_settings.py b/resources/lib/youtube_plugin/kodion/impl/abstract_settings.py
index c231cea4d..bb1c2b437 100644
--- a/resources/lib/youtube_plugin/kodion/impl/abstract_settings.py
+++ b/resources/lib/youtube_plugin/kodion/impl/abstract_settings.py
@@ -96,8 +96,8 @@ def is_support_alternative_player_enabled(self):
def alternative_player_web_urls(self):
return self.get_bool(SETTINGS.ALTERNATIVE_PLAYER_WEB_URLS, False)
- def use_mpd(self):
- return self.get_bool(SETTINGS.USE_MPD, False)
+ def use_isa(self):
+ return self.get_bool(SETTINGS.USE_ISA, False)
def subtitle_languages(self):
return self.get_int(SETTINGS.SUBTITLE_LANGUAGE, 0)
@@ -137,31 +137,31 @@ def allow_dev_keys(self):
return self.get_bool(SETTINGS.ALLOW_DEV_KEYS, False)
def use_mpd_videos(self):
- if self.use_mpd():
+ if self.use_isa():
return self.get_bool(SETTINGS.MPD_VIDEOS, False)
return False
_LIVE_STREAM_TYPES = {
0: 'mpegts',
1: 'hls',
- 2: 'ia_hls',
- 3: 'ia_mpd',
+ 2: 'isa_hls',
+ 3: 'isa_mpd',
}
def get_live_stream_type(self):
- if self.use_mpd():
+ if self.use_isa():
stream_type = self.get_int(SETTINGS.LIVE_STREAMS + '.1', 0)
else:
stream_type = self.get_int(SETTINGS.LIVE_STREAMS + '.2', 0)
return self._LIVE_STREAM_TYPES.get(stream_type) or self._LIVE_STREAM_TYPES[0]
- def use_adaptive_live_streams(self):
- if self.use_mpd():
+ def use_isa_live_streams(self):
+ if self.use_isa():
return self.get_int(SETTINGS.LIVE_STREAMS + '.1', 0) > 1
- return self.get_int(SETTINGS.LIVE_STREAMS + '.2', 0) > 1
+ return False
def use_mpd_live_streams(self):
- if self.use_mpd():
+ if self.use_isa():
return self.get_int(SETTINGS.LIVE_STREAMS + '.1', 0) == 3
return False
diff --git a/resources/lib/youtube_plugin/kodion/impl/xbmc/xbmc_context.py b/resources/lib/youtube_plugin/kodion/impl/xbmc/xbmc_context.py
index 867296b51..3fc817fb1 100644
--- a/resources/lib/youtube_plugin/kodion/impl/xbmc/xbmc_context.py
+++ b/resources/lib/youtube_plugin/kodion/impl/xbmc/xbmc_context.py
@@ -272,7 +272,7 @@ def send_notification(self, method, data):
self.execute('NotifyAll(plugin.video.youtube,%s,%s)' % (method, data))
def use_inputstream_adaptive(self):
- if self._settings.use_mpd_videos() or self._settings.use_adaptive_live_streams():
+ if self._settings.use_isa():
if self.addon_enabled('inputstream.adaptive'):
success = True
elif self.get_ui().on_yes_no_input(self.get_name(), self.localize(30579)):
@@ -287,7 +287,7 @@ def use_inputstream_adaptive(self):
# - required version number as string for comparison with actual installed InputStream.Adaptive version
# - any Falsey value to exclude capability regardless of version
# - True to include capability regardless of version
- _IA_CAPABILITIES = {
+ _ISA_CAPABILITIES = {
'live': '2.0.12',
'drm': '2.2.12',
# audio codecs
@@ -315,16 +315,16 @@ def inputstream_adaptive_capabilities(self, capability=None):
if not self.use_inputstream_adaptive() or not inputstream_version:
return frozenset() if capability is None else None
- ia_loose_version = utils.loose_version(inputstream_version)
+ isa_loose_version = utils.loose_version(inputstream_version)
if capability is None:
capabilities = frozenset(
- capability for capability, version in self._IA_CAPABILITIES.items()
+ capability for capability, version in self._ISA_CAPABILITIES.items()
if version is True
- or version and ia_loose_version >= utils.loose_version(version)
+ or version and isa_loose_version >= utils.loose_version(version)
)
return capabilities
- version = self._IA_CAPABILITIES.get(capability)
- return version is True or version and ia_loose_version >= utils.loose_version(version)
+ version = self._ISA_CAPABILITIES.get(capability)
+ return version is True or version and isa_loose_version >= utils.loose_version(version)
def inputstream_adaptive_auto_stream_selection(self):
try:
diff --git a/resources/lib/youtube_plugin/kodion/impl/xbmc/xbmc_items.py b/resources/lib/youtube_plugin/kodion/impl/xbmc/xbmc_items.py
index 9c43c5b49..7f715b1c2 100644
--- a/resources/lib/youtube_plugin/kodion/impl/xbmc/xbmc_items.py
+++ b/resources/lib/youtube_plugin/kodion/impl/xbmc/xbmc_items.py
@@ -33,7 +33,8 @@ def set_info(self, *args, **kwargs):
def to_play_item(context, play_item):
- context.log_debug('Converting PlayItem |%s|' % play_item.get_uri())
+ uri = play_item.get_uri()
+ context.log_debug('Converting PlayItem |%s|' % uri)
is_strm = str(context.get_param('strm', False)).lower() == 'true'
@@ -56,36 +57,28 @@ def to_play_item(context, play_item):
list_item.setArt({'icon': thumb, 'thumb': thumb, 'fanart': fanart})
- if settings.is_support_alternative_player_enabled() and \
- settings.alternative_player_web_urls() and \
- not play_item.get_license_key():
- play_item.set_uri('https://www.youtube.com/watch?v={video_id}'.format(video_id=play_item.video_id))
+ headers = play_item.get_headers()
+ license_key = play_item.get_license_key()
+ alternative_player = settings.is_support_alternative_player_enabled()
- ia_enabled = context.addon_enabled('inputstream.adaptive')
+ if (alternative_player and settings.alternative_player_web_urls()
+ and not license_key):
+ play_item.set_uri('https://www.youtube.com/watch?v={video_id}'.format(
+ video_id=play_item.video_id
+ ))
- if ia_enabled and play_item.use_mpd_video() and not play_item.live:
- list_item.setContentLookup(False)
- list_item.setMimeType('application/xml+dash')
- list_item.setProperty('inputstream', 'inputstream.adaptive')
- list_item.setProperty('inputstream.adaptive.manifest_type', 'mpd')
- if 'auto' in settings.stream_select():
- list_item.setProperty('inputstream.adaptive.stream_selection_type', 'adaptive')
-
- if play_item.get_headers():
- list_item.setProperty('inputstream.adaptive.manifest_headers', play_item.get_headers())
- list_item.setProperty('inputstream.adaptive.stream_headers', play_item.get_headers())
+ isa_enabled = settings.use_isa() and context.addon_enabled('inputstream.adaptive')
- if play_item.get_license_key():
- list_item.setProperty('inputstream.adaptive.license_type', 'com.widevine.alpha')
- list_item.setProperty('inputstream.adaptive.license_key', play_item.get_license_key())
-
- elif ia_enabled and play_item.live and settings.use_adaptive_live_streams():
- if settings.use_mpd_live_streams():
+ if isa_enabled and play_item.use_isa_video():
+ if play_item.use_mpd_video():
manifest_type = 'mpd'
mime_type = 'application/xml+dash'
# MPD manifest update is currently broken
# Following line will force a full update but restart live stream from start
- # list_item.setProperty('inputstream.adaptive.manifest_update_parameter', 'full')
+ # if play_item.live:
+ # list_item.setProperty('inputstream.adaptive.manifest_update_parameter', 'full')
+ if 'auto' in settings.stream_select():
+ list_item.setProperty('inputstream.adaptive.stream_selection_type', 'adaptive')
else:
manifest_type = 'hls'
mime_type = 'application/x-mpegURL'
@@ -95,16 +88,14 @@ def to_play_item(context, play_item):
list_item.setProperty('inputstream', 'inputstream.adaptive')
list_item.setProperty('inputstream.adaptive.manifest_type', manifest_type)
- if play_item.get_headers():
- list_item.setProperty('inputstream.adaptive.manifest_headers', play_item.get_headers())
- list_item.setProperty('inputstream.adaptive.stream_headers', play_item.get_headers())
+ if headers:
+ list_item.setProperty('inputstream.adaptive.manifest_headers', headers)
+ list_item.setProperty('inputstream.adaptive.stream_headers', headers)
- if play_item.get_license_key():
+ if license_key:
list_item.setProperty('inputstream.adaptive.license_type', 'com.widevine.alpha')
- list_item.setProperty('inputstream.adaptive.license_key', play_item.get_license_key())
-
+ list_item.setProperty('inputstream.adaptive.license_key', license_key)
else:
- uri = play_item.get_uri()
if 'mime=' in uri:
try:
mime_type = uri.split('mime=', 1)[-1].split('&', 1)[0].replace('%2F', '/', 1)
diff --git a/resources/lib/youtube_plugin/kodion/items/video_item.py b/resources/lib/youtube_plugin/kodion/items/video_item.py
index 5bcb90384..35a819be9 100644
--- a/resources/lib/youtube_plugin/kodion/items/video_item.py
+++ b/resources/lib/youtube_plugin/kodion/items/video_item.py
@@ -40,7 +40,7 @@ def __init__(self, name, uri, image='', fanart=''):
self._studio = None
self._artist = None
self._play_count = None
- self._uses_mpd = None
+ self._uses_isa = None
self._mediatype = None
self._last_played = None
self._start_percent = None
@@ -235,11 +235,23 @@ def set_date_from_datetime(self, date_time):
def get_date(self):
return self._date
- def set_use_mpd_video(self, value=True):
- self._uses_mpd = value
+ def set_isa_video(self, value=True):
+ self._uses_isa = value
+
+ def use_isa_video(self):
+ return self._uses_isa
+
+ def use_hls_video(self):
+ uri = self.get_uri()
+ if 'manifest/hls' in uri or uri.endswith('.m3u8'):
+ return True
+ return False
def use_mpd_video(self):
- return self._uses_mpd is True and ('manifest/dash' in self.get_uri() or self.get_uri().endswith('.mpd'))
+ uri = self.get_uri()
+ if 'manifest/dash' in uri or uri.endswith('.mpd'):
+ return True
+ return False
def set_mediatype(self, mediatype):
self._mediatype = mediatype
diff --git a/resources/lib/youtube_plugin/kodion/utils/http_server.py b/resources/lib/youtube_plugin/kodion/utils/http_server.py
index c63f0a02a..909ab0034 100644
--- a/resources/lib/youtube_plugin/kodion/utils/http_server.py
+++ b/resources/lib/youtube_plugin/kodion/utils/http_server.py
@@ -62,7 +62,7 @@ def connection_allowed(self):
# noinspection PyPep8Naming
def do_GET(self):
addon = xbmcaddon.Addon('plugin.video.youtube')
- mpd_proxy_enabled = addon.getSetting('kodion.mpd.videos') == 'true' and addon.getSetting('kodion.video.quality.mpd') == 'true'
+ mpd_proxy_enabled = addon.getSetting('kodion.mpd.videos') == 'true' and addon.getSetting('kodion.video.quality.isa') == 'true'
api_config_enabled = addon.getSetting('youtube.api.config.page') == 'true'
# Strip trailing slash if present
@@ -166,7 +166,7 @@ def do_HEAD(self):
self.send_error(403)
else:
addon = xbmcaddon.Addon('plugin.video.youtube')
- mpd_proxy_enabled = addon.getSetting('kodion.mpd.videos') == 'true' and addon.getSetting('kodion.video.quality.mpd') == 'true'
+ mpd_proxy_enabled = addon.getSetting('kodion.mpd.videos') == 'true' and addon.getSetting('kodion.video.quality.isa') == 'true'
if mpd_proxy_enabled and self.path.endswith('.mpd'):
file_path = os.path.join(self.base_path, self.path.strip('/').strip('\\'))
if not os.path.isfile(file_path):
diff --git a/resources/lib/youtube_plugin/kodion/utils/methods.py b/resources/lib/youtube_plugin/kodion/utils/methods.py
index c8daf481c..dc5df817f 100644
--- a/resources/lib/youtube_plugin/kodion/utils/methods.py
+++ b/resources/lib/youtube_plugin/kodion/utils/methods.py
@@ -97,7 +97,7 @@ def _sort_stream_data(_stream_data):
ask_for_quality = context.get_settings().ask_for_video_quality() if ask_for_quality is None else ask_for_quality
video_quality = settings.get_video_quality(quality_map_override=quality_map_override)
audio_only = audio_only if audio_only is not None else settings.audio_only()
- adaptive_live = settings.use_adaptive_live_streams() and context.inputstream_adaptive_capabilities('live')
+ adaptive_live = settings.use_isa_live_streams() and context.inputstream_adaptive_capabilities('live')
if not ask_for_quality:
stream_data_list = [item for item in stream_data_list
diff --git a/resources/lib/youtube_plugin/kodion/utils/monitor.py b/resources/lib/youtube_plugin/kodion/utils/monitor.py
index 4105b4fd0..3c86eda88 100644
--- a/resources/lib/youtube_plugin/kodion/utils/monitor.py
+++ b/resources/lib/youtube_plugin/kodion/utils/monitor.py
@@ -35,7 +35,7 @@ def __init__(self, *args, **kwargs):
self._whitelist = addon.getSetting('kodion.http.ip.whitelist')
self._httpd_port = int(addon.getSetting('kodion.mpd.proxy.port'))
self._old_httpd_port = self._httpd_port
- self._use_httpd = (addon.getSetting('kodion.mpd.videos') == 'true' and addon.getSetting('kodion.video.quality.mpd') == 'true') or \
+ self._use_httpd = (addon.getSetting('kodion.mpd.videos') == 'true' and addon.getSetting('kodion.video.quality.isa') == 'true') or \
(addon.getSetting('youtube.api.config.page') == 'true')
self._httpd_address = addon.getSetting('kodion.http.listen')
self._old_httpd_address = self._httpd_address
diff --git a/resources/lib/youtube_plugin/youtube/helper/utils.py b/resources/lib/youtube_plugin/youtube/helper/utils.py
index 1539216f0..30ca5f284 100644
--- a/resources/lib/youtube_plugin/youtube/helper/utils.py
+++ b/resources/lib/youtube_plugin/youtube/helper/utils.py
@@ -494,8 +494,11 @@ def update_play_info(provider, context, video_id, video_item, video_stream, use_
if 'headers' in video_stream:
video_item.set_headers(video_stream['headers'])
- # set uses_mpd
- video_item.set_use_mpd_video(settings.use_mpd_videos())
+ # set _uses_isa
+ if video_item.live:
+ video_item.set_isa_video(settings.use_isa_live_streams())
+ elif video_item.use_hls_video() or video_item.use_mpd_video():
+ video_item.set_isa_video(settings.use_isa())
license_info = video_stream.get('license_info', {})
diff --git a/resources/lib/youtube_plugin/youtube/helper/video_info.py b/resources/lib/youtube_plugin/youtube/helper/video_info.py
index 0c6fc0040..811fae6bc 100644
--- a/resources/lib/youtube_plugin/youtube/helper/video_info.py
+++ b/resources/lib/youtube_plugin/youtube/helper/video_info.py
@@ -1514,7 +1514,7 @@ def _get_video_info(self):
'watchtime_url': '',
}
- httpd_is_live = (_settings.use_mpd() and
+ httpd_is_live = (_settings.use_isa() and
is_httpd_live(port=_settings.httpd_port()))
pa_li_info = streaming_data.get('licenseInfos', [])
@@ -1557,7 +1557,7 @@ def _get_video_info(self):
manifest_url = None
if is_live:
live_type = _settings.get_live_stream_type()
- if live_type == 'ia_mpd':
+ if live_type == 'isa_mpd':
manifest_url = streaming_data.get('dashManifestUrl', '')
else:
stream_list.extend(self._load_hls_manifest(
@@ -1637,7 +1637,7 @@ def _get_video_info(self):
def _process_stream_data(self, stream_data, default_lang_code='und'):
_settings = self._context.get_settings()
qualities = _settings.get_mpd_video_qualities()
- ia_capabilities = self._context.inputstream_adaptive_capabilities()
+ isa_capabilities = self._context.inputstream_adaptive_capabilities()
stream_features = _settings.stream_features()
allow_hdr = 'hdr' in stream_features
allow_hfr = 'hfr' in stream_features
@@ -1704,7 +1704,7 @@ def _process_stream_data(self, stream_data, default_lang_code='und'):
codec = 'vp9'
elif codec.startswith('dts'):
codec = 'dts'
- if codec not in stream_features or codec not in ia_capabilities:
+ if codec not in stream_features or codec not in isa_capabilities:
continue
media_type, container = mime_type.split('/')
bitrate = stream.get('bitrate', 0)
diff --git a/resources/lib/youtube_plugin/youtube/provider.py b/resources/lib/youtube_plugin/youtube/provider.py
index 7ede578b6..15f27c65f 100644
--- a/resources/lib/youtube_plugin/youtube/provider.py
+++ b/resources/lib/youtube_plugin/youtube/provider.py
@@ -1090,11 +1090,11 @@ def configure_addon(self, context, re_match):
if switch == 'youtube':
context.addon().openSettings()
context.get_ui().refresh_container()
- elif switch == 'mpd':
+ elif switch == 'isa':
if context.use_inputstream_adaptive():
xbmcaddon.Addon(id='inputstream.adaptive').openSettings()
else:
- settings.set_bool('kodion.video.quality.mpd', False)
+ settings.set_bool('kodion.video.quality.isa', False)
elif switch == 'subtitles':
yt_language = context.get_settings().get_string('youtube.language', 'en-US')
sub_setting = context.get_settings().subtitle_languages()
diff --git a/resources/settings.xml b/resources/settings.xml
index 70429df9f..2ef4169f6 100644
--- a/resources/settings.xml
+++ b/resources/settings.xml
@@ -200,9 +200,9 @@
-
+
-
+
0
false
@@ -212,15 +212,15 @@
-
+
0
- RunPlugin(plugin://plugin.video.youtube/config/mpd/)
+ RunPlugin(plugin://plugin.video.youtube/config/isa/)
true
- true
+ true
@@ -234,7 +234,7 @@
true
- true
+ true
@@ -312,37 +312,36 @@
-
+
0
0
-
-
+
+
- true
+ true
-
+
0
0
-
- false
+ false
@@ -358,9 +357,9 @@
- true
- 1
- 1
+ true
+ 1
+ 1
From ea485a5ae3aeb9b784c8a68e054548c3398b8ff9 Mon Sep 17 00:00:00 2001
From: MoojMidge <56883549+MoojMidge@users.noreply.github.com>
Date: Tue, 31 Oct 2023 16:28:52 +1100
Subject: [PATCH 016/141] Add preliminary support for iOS premium streams
Can be enabled from
Settings > Advanced > Use alternate client details > Alternate #1
- Partially fixes #505
---
.../youtube/helper/video_info.py | 51 ++++++++++++-------
1 file changed, 32 insertions(+), 19 deletions(-)
diff --git a/resources/lib/youtube_plugin/youtube/helper/video_info.py b/resources/lib/youtube_plugin/youtube/helper/video_info.py
index 811fae6bc..f57a5cd2d 100644
--- a/resources/lib/youtube_plugin/youtube/helper/video_info.py
+++ b/resources/lib/youtube_plugin/youtube/helper/video_info.py
@@ -533,6 +533,14 @@ class VideoInfo(object):
'title': 'ac-3@384',
'dash/audio': True,
'audio': {'bitrate': 384, 'encoding': 'ac-3'}},
+ # === HLS
+ '9994': {'container': 'hls',
+ 'sort': [-1080, -1],
+ 'title': 'HLS',
+ 'hls/audio': True,
+ 'hls/video': True,
+ 'audio': {'bitrate': 0, 'encoding': 'aac'},
+ 'video': {'height': 0, 'encoding': 'h.264'}},
# === Live HLS
'9995': {'container': 'hls',
'Live': True,
@@ -837,30 +845,30 @@ def __init__(self, context, access_token='', language='en-US'):
self._selected_client = None
client_selection = settings.client_selection()
- # All client selections use the Android client as the first option to
- # ensure that the age gate setting is enforced, regardless of login
- # status
+ # Default client selection uses the Android or iOS client as the first
+ # option to ensure that the age gate setting is enforced, regardless of
+ # login status
# Alternate #1
- # Will play most videos with subtitles at full resolution with HDR
- # Some restricted videos may only play at 720p
- # Some restricted videos require additional requests for subtitles
+ # Enable iOS client to access premium streams, however other stream
+ # types are limited
if client_selection == 1:
self._prioritised_clients = (
+ 'ios',
'android',
- 'android_embedded',
'android_youtube_tv',
'android_testsuite',
+ 'android_embedded',
)
# Alternate #2
- # Will play most videos at full resolution with HDR
- # Most videos wont show subtitles
- # Useful for testing AV1 HDR
+ # Used to bypass age restriction, however streams are obfuscated and
+ # throttled. Useful for testing n-sig de-obfuscation.
elif client_selection == 2:
self._prioritised_clients = (
+ 'smarttv_embedded',
'android',
- 'android_testsuite',
'android_youtube_tv',
+ 'android_testsuite',
'android_embedded',
)
# Default
@@ -1105,14 +1113,15 @@ def _load_hls_manifest(self, url, live_type=None, meta_info=None,
if playback_stats is None:
playback_stats = {}
- if live_type is None:
- live_type = self._context.get_settings().get_live_stream_type()
-
- if 'hls' in live_type:
- if live_type == 'hls':
- yt_format = self.FORMAT['9995']
- else:
- yt_format = self.FORMAT['9996']
+ yt_format = None
+ if not live_type:
+ yt_format = self.FORMAT['9994']
+ elif live_type == 'hls':
+ yt_format = self.FORMAT['9995']
+ elif live_type == 'isa_hls':
+ yt_format = self.FORMAT['9996']
+
+ if yt_format:
stream = {'url': url,
'meta': meta_info,
'headers': curl_headers,
@@ -1571,6 +1580,10 @@ def _get_video_info(self):
manifest_url, main_stream = self._generate_mpd_manifest(
video_data, audio_data, license_info.get('url')
)
+ stream_list.extend(self._load_hls_manifest(
+ streaming_data.get('hlsManifestUrl'),
+ None, meta_info, client['headers'], playback_stats
+ ))
if manifest_url:
video_stream = {
From 63162196b3b543803ff3c8a808adb6ef3f260383 Mon Sep 17 00:00:00 2001
From: MoojMidge <56883549+MoojMidge@users.noreply.github.com>
Date: Tue, 31 Oct 2023 17:07:05 +1100
Subject: [PATCH 017/141] Update defaults as ISA is now a required dependency
---
resources/settings.xml | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/resources/settings.xml b/resources/settings.xml
index 2ef4169f6..fa8ad911f 100644
--- a/resources/settings.xml
+++ b/resources/settings.xml
@@ -204,7 +204,7 @@
0
- false
+ true
System.HasAddon(inputstream.adaptive)
@@ -295,7 +295,7 @@
0
- 1
+ 3
@@ -314,7 +314,7 @@
0
- 0
+ 2
From be03bd039bb9cac13d75312c63bc5c63aafacae4 Mon Sep 17 00:00:00 2001
From: MoojMidge <56883549+MoojMidge@users.noreply.github.com>
Date: Tue, 31 Oct 2023 17:26:52 +1100
Subject: [PATCH 018/141] Move youtube requests to login_client
- Use common method for all Youtube requests
- Use common connection pool
- Use as context manager
- TODO migrate subtitle and video_info client requests
---
.../resource.language.en_au/strings.po | 8 +
.../resource.language.en_gb/strings.po | 8 +
.../resource.language.en_nz/strings.po | 8 +
.../resource.language.en_us/strings.po | 8 +
.../kodion/constants/const_settings.py | 5 +-
.../lib/youtube_plugin/kodion/exceptions.py | 11 +-
.../kodion/impl/abstract_settings.py | 5 +
.../youtube/client/login_client.py | 333 ++++++++++--------
.../youtube_plugin/youtube/client/youtube.py | 52 +--
resources/settings.xml | 28 +-
10 files changed, 271 insertions(+), 195 deletions(-)
diff --git a/resources/language/resource.language.en_au/strings.po b/resources/language/resource.language.en_au/strings.po
index 287c9965c..7d72626af 100644
--- a/resources/language/resource.language.en_au/strings.po
+++ b/resources/language/resource.language.en_au/strings.po
@@ -1352,3 +1352,11 @@ msgstr ""
msgctxt "#30763"
msgid "Multi-audio"
msgstr ""
+
+msgctxt "#30764"
+msgid "Requests connect timeout"
+msgstr ""
+
+msgctxt "#30765"
+msgid "Requests read timeout"
+msgstr ""
diff --git a/resources/language/resource.language.en_gb/strings.po b/resources/language/resource.language.en_gb/strings.po
index a78adb225..461237005 100644
--- a/resources/language/resource.language.en_gb/strings.po
+++ b/resources/language/resource.language.en_gb/strings.po
@@ -1352,3 +1352,11 @@ msgstr ""
msgctxt "#30763"
msgid "Multi-audio"
msgstr ""
+
+msgctxt "#30764"
+msgid "Requests connect timeout"
+msgstr ""
+
+msgctxt "#30765"
+msgid "Requests read timeout"
+msgstr ""
diff --git a/resources/language/resource.language.en_nz/strings.po b/resources/language/resource.language.en_nz/strings.po
index 7dc6ba76d..168654a38 100644
--- a/resources/language/resource.language.en_nz/strings.po
+++ b/resources/language/resource.language.en_nz/strings.po
@@ -1348,3 +1348,11 @@ msgstr ""
msgctxt "#30763"
msgid "Multi-audio"
msgstr ""
+
+msgctxt "#30764"
+msgid "Requests connect timeout"
+msgstr ""
+
+msgctxt "#30765"
+msgid "Requests read timeout"
+msgstr ""
diff --git a/resources/language/resource.language.en_us/strings.po b/resources/language/resource.language.en_us/strings.po
index 574fb67d4..731014011 100644
--- a/resources/language/resource.language.en_us/strings.po
+++ b/resources/language/resource.language.en_us/strings.po
@@ -1353,3 +1353,11 @@ msgstr ""
msgctxt "#30763"
msgid "Multi-audio"
msgstr ""
+
+msgctxt "#30764"
+msgid "Requests connect timeout"
+msgstr ""
+
+msgctxt "#30765"
+msgid "Requests read timeout"
+msgstr ""
diff --git a/resources/lib/youtube_plugin/kodion/constants/const_settings.py b/resources/lib/youtube_plugin/kodion/constants/const_settings.py
index 0ad5123bd..58ac464a6 100644
--- a/resources/lib/youtube_plugin/kodion/constants/const_settings.py
+++ b/resources/lib/youtube_plugin/kodion/constants/const_settings.py
@@ -19,7 +19,6 @@
SUBTITLE_LANGUAGE = 'kodion.subtitle.languages.num' # (int)
SUBTITLE_DOWNLOAD = 'kodion.subtitle.download' # (bool)
SETUP_WIZARD = 'kodion.setup_wizard' # (bool)
-VERIFY_SSL = 'simple.requests.ssl.verify' # (bool)
LOCATION = 'youtube.location' # (str)
LOCATION_RADIUS = 'youtube.location.radius' # (int)
PLAY_COUNT_MIN_PERCENT = 'kodion.play_count.percent' # (int)
@@ -42,6 +41,10 @@
MPD_STREAM_FEATURES = 'kodion.mpd.stream.features' # (list[string])
MPD_STREAM_SELECT = 'kodion.mpd.stream.select' # (int)
+VERIFY_SSL = 'requests.ssl.verify' # (bool)
+CONNECT_TIMEOUT = 'requests.timeout.connect' # (int)
+READ_TIMEOUT = 'requests.timeout.read' # (int)
+
HTTPD_PORT = 'kodion.mpd.proxy.port' # (number)
HTTPD_LISTEN = 'kodion.http.listen' # (string)
HTTPD_WHITELIST = 'kodion.http.ip.whitelist' # (string)
diff --git a/resources/lib/youtube_plugin/kodion/exceptions.py b/resources/lib/youtube_plugin/kodion/exceptions.py
index 7b4aa469b..543a28e65 100644
--- a/resources/lib/youtube_plugin/kodion/exceptions.py
+++ b/resources/lib/youtube_plugin/kodion/exceptions.py
@@ -10,9 +10,12 @@
class KodionException(Exception):
- def __init__(self, message):
- Exception.__init__(self, message)
- self._message = message
+ def __init__(self, message, **kwargs):
+ super(KodionException, self).__init__(message)
+ attrs = self.__dict__
+ for attr, value in kwargs.items():
+ if attr not in attrs:
+ setattr(self, attr, value)
def get_message(self):
- return self._message
+ return str(self)
diff --git a/resources/lib/youtube_plugin/kodion/impl/abstract_settings.py b/resources/lib/youtube_plugin/kodion/impl/abstract_settings.py
index bb1c2b437..2dd651f39 100644
--- a/resources/lib/youtube_plugin/kodion/impl/abstract_settings.py
+++ b/resources/lib/youtube_plugin/kodion/impl/abstract_settings.py
@@ -133,6 +133,11 @@ def verify_ssl(self):
verify = False
return verify
+ def get_timeout(self):
+ 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)
+
def allow_dev_keys(self):
return self.get_bool(SETTINGS.ALLOW_DEV_KEYS, False)
diff --git a/resources/lib/youtube_plugin/youtube/client/login_client.py b/resources/lib/youtube_plugin/youtube/client/login_client.py
index e9d595302..e9c68d4dd 100644
--- a/resources/lib/youtube_plugin/youtube/client/login_client.py
+++ b/resources/lib/youtube_plugin/youtube/client/login_client.py
@@ -8,20 +8,23 @@
See LICENSES/GPL-2.0-only for more information.
"""
+import atexit
import time
from urllib.parse import parse_qsl
-import requests
+from requests import Session
+from requests.adapters import HTTPAdapter, Retry
+from requests.exceptions import InvalidJSONError, RequestException
-from ...youtube.youtube_exceptions import InvalidGrant, LoginException
+from ...youtube.youtube_exceptions import (InvalidGrant,
+ LoginException,
+ YouTubeException,)
from ...kodion import Context
from .__config__ import (api,
developer_keys,
keys_changed,
youtube_tv,)
-context = Context(plugin_id='plugin.video.youtube')
-
class LoginClient(object):
api_keys_changed = keys_changed
@@ -42,10 +45,24 @@ class LoginClient(object):
'developer': developer_keys
}
+ http_adapter = HTTPAdapter(
+ pool_maxsize=10,
+ pool_block=True,
+ max_retries=Retry(
+ total=3,
+ backoff_factor=1,
+ status_forcelist={500, 502, 503, 504},
+ allowed_methods=None,
+ )
+ )
+
+ context = Context(plugin_id='plugin.video.youtube')
+
def __init__(self, config=None, language='en-US', region='', access_token='', access_token_tv=''):
self._config = self.CONFIGS['main'] if config is None else config
self._config_tv = self.CONFIGS['youtube-tv']
- self._verify = context.get_settings().verify_ssl()
+ self._verify = self.context.get_settings().verify_ssl()
+ self._timeout = self.context.get_settings().get_timeout()
# the default language is always en_US (like YouTube on the WEB)
if not language:
language = 'en_US'
@@ -58,6 +75,115 @@ def __init__(self, config=None, language='en-US', region='', access_token='', ac
self._access_token_tv = access_token_tv
self._log_error_callback = None
+ self._session = Session()
+ self._session.verify = self._verify
+ self._session.mount('https://', self.http_adapter)
+ atexit.register(self._session.close)
+
+ def __enter__(self):
+ return self
+
+ def __exit__(self, exc_type, exc_value, traceback):
+ self._session.close()
+
+ @staticmethod
+ def _login_json_hook(response):
+ json_data = None
+ try:
+ json_data = response.json()
+ if 'error' in json_data:
+ raise YouTubeException('"error" in response JSON data',
+ json_data=json_data)
+ except ValueError as error:
+ raise InvalidJSONError(error, response=response)
+ return response, json_data
+
+ @staticmethod
+ def _login_error_hook(error, response):
+ json_data = getattr(error, 'json_data', None)
+ if not json_data:
+ return None, None, None, None, LoginException
+ if json_data['error'] == 'authorization_pending':
+ return None, None, None, False, False
+ if (json_data['error'] == 'invalid_grant'
+ and json_data.get('code') == '400'):
+ return None, None, json_data, False, InvalidGrant(json_data)
+ return None, None, json_data, False, LoginException(json_data)
+
+ def _request(self, url, method='GET',
+ cookies=None, data=None, headers=None, json=None, params=None,
+ response_hook=None, error_hook=None,
+ error_title=None, error_info=None, raise_exc=False, **_):
+ response = None
+ try:
+ response = self._session.request(method, url,
+ verify=self._verify,
+ allow_redirects=True,
+ timeout=self._timeout,
+ cookies=cookies,
+ data=data,
+ headers=headers,
+ json=json,
+ params=params)
+ response.raise_for_status()
+ if response_hook:
+ response = response_hook(response)
+
+ except (RequestException, YouTubeException) as exc:
+ from traceback import format_exc, format_stack
+
+ response_text = exc.response and exc.response.text
+ stack_trace = format_stack()
+ exc_tb = format_exc()
+
+ if error_hook:
+ error_response = error_hook(exc, response)
+ _title, _info, _response, _trace, _exc = error_response
+ if _title is not None:
+ error_title = _title
+ if _info is not None:
+ error_info = _info
+ if _response is not None:
+ response_text = _response
+ if _trace is not None:
+ stack_trace = _trace
+ if _exc is not None:
+ raise_exc = _exc
+
+ if error_title is None:
+ error_title = 'Request failed'
+
+ if error_info is None:
+ error_info = str(exc)
+ elif '{' in error_info:
+ try:
+ error_info = error_info.format(exc=exc)
+ except (AttributeError, IndexError, KeyError):
+ error_info = str(exc)
+
+ if response_text:
+ response_text = 'Request response:\n{0}'.format(response_text)
+
+ if stack_trace:
+ stack_trace = (
+ 'Stack trace (most recent call last):\n{0}'.format(
+ ''.join(stack_trace)
+ )
+ )
+
+ self.context.log_error('\n'.join([part for part in [
+ error_title, error_info, response_text, stack_trace, exc_tb
+ ] if part]))
+
+ if raise_exc:
+ if isinstance(raise_exc, BaseException):
+ raise raise_exc
+ if not callable(raise_exc):
+ raise YouTubeException(error_title)
+ raise raise_exc(error_title)
+
+ return response
+
def set_log_error(self, callback):
self._log_error_callback = callback
@@ -84,24 +210,14 @@ def revoke(self, refresh_token):
post_data = {'token': refresh_token}
- # url
- url = 'https://accounts.google.com/o/oauth2/revoke'
-
- result = requests.post(url, data=post_data, headers=headers, verify=self._verify)
-
- try:
- json_data = result.json()
- if 'error' in json_data:
- context.log_error('Revoke failed: Code: |%s| JSON: |%s|' % (str(result.status_code), json_data))
- json_data.update({'code': str(result.status_code)})
- raise LoginException(json_data)
- except ValueError:
- json_data = None
-
- if result.status_code != requests.codes.ok:
- response_dump = self._get_response_dump(result, json_data)
- context.log_error('Revoke failed: Code: |%s| Response dump: |%s|' % (str(result.status_code), response_dump))
- raise LoginException('Logout Failed')
+ self._request('https://accounts.google.com/o/oauth2/revoke',
+ method='POST', data=post_data, headers=headers,
+ response_hook=self._login_json_hook,
+ error_hook=self._login_error_hook,
+ error_title='Logout Failed',
+ error_info='Revoke failed: {exc}',
+ raise_exc=LoginException
+ )
def refresh_token_tv(self, refresh_token):
client_id = str(self.CONFIGS['youtube-tv']['id'])
@@ -116,45 +232,32 @@ def refresh_token(self, refresh_token, client_id='', client_secret=''):
client_id = client_id or self._config['id']
client_secret = client_secret or self._config['secret']
-
post_data = {'client_id': client_id,
'client_secret': client_secret,
'refresh_token': refresh_token,
'grant_type': 'refresh_token'}
- # url
- url = 'https://www.googleapis.com/oauth2/v4/token'
-
config_type = self._get_config_type(client_id, client_secret)
- context.log_debug('Refresh token: Config: |%s| Client id [:5]: |%s| Client secret [:5]: |%s|' %
- (config_type, client_id[:5], client_secret[:5]))
+ client_summary = ''.join([
+ '(config_type: |', config_type, '|',
+ ' client_id: |', client_id[:5], '...|',
+ ' client_secret: |', client_secret[:5], '...|)'
+ ])
+ self.context.log_debug('Refresh token for ' + client_summary)
+
+ result, json_data = self._request('https://www.googleapis.com/oauth2/v4/token',
+ method='POST', data=post_data, headers=headers,
+ response_hook=self._login_json_hook,
+ error_hook=self._login_error_hook,
+ error_title='Login Failed',
+ error_info='Refresh failed for ' + client_summary + ': {exc}',
+ raise_exc=LoginException
+ )
- result = requests.post(url, data=post_data, headers=headers, verify=self._verify)
-
- try:
- json_data = result.json()
- if 'error' in json_data:
- context.log_error('Refresh Failed: Code: |%s| JSON: |%s|' % (str(result.status_code), json_data))
- json_data.update({'code': str(result.status_code)})
- if json_data['error'] == 'invalid_grant' and json_data['code'] == '400':
- raise InvalidGrant(json_data)
- raise LoginException(json_data)
- except ValueError:
- json_data = None
-
- if result.status_code != requests.codes.ok:
- response_dump = self._get_response_dump(result, json_data)
- context.log_error('Refresh failed: Config: |%s| Client id [:5]: |%s| Client secret [:5]: |%s| Code: |%s| Response dump |%s|' %
- (config_type, client_id[:5], client_secret[:5], str(result.status_code), response_dump))
- raise LoginException('Login Failed')
-
- if result.headers.get('content-type', '').startswith('application/json'):
- if not json_data:
- json_data = result.json()
+ if json_data:
access_token = json_data['access_token']
expires_in = time.time() + int(json_data.get('expires_in', 3600))
return access_token, expires_in
-
return '', ''
def request_access_token_tv(self, code, client_id='', client_secret=''):
@@ -170,50 +273,28 @@ def request_access_token(self, code, client_id='', client_secret=''):
client_id = client_id or self._config['id']
client_secret = client_secret or self._config['secret']
-
post_data = {'client_id': client_id,
'client_secret': client_secret,
'code': code,
'grant_type': 'http://oauth.net/grant_type/device/1.0'}
- # url
- url = 'https://www.googleapis.com/oauth2/v4/token'
-
config_type = self._get_config_type(client_id, client_secret)
- context.log_debug('Requesting access token: Config: |%s| Client id [:5]: |%s| Client secret [:5]: |%s|' %
- (config_type, client_id[:5], client_secret[:5]))
-
- result = requests.post(url, data=post_data, headers=headers, verify=self._verify)
-
- authorization_pending = False
- try:
- json_data = result.json()
- if 'error' in json_data:
- if json_data['error'] != u'authorization_pending':
- context.log_error('Requesting access token: Code: |%s| JSON: |%s|' % (str(result.status_code), json_data))
- json_data.update({'code': str(result.status_code)})
- raise LoginException(json_data)
- else:
- authorization_pending = True
- except ValueError:
- json_data = None
-
- if (result.status_code != requests.codes.ok) and not authorization_pending:
- response_dump = self._get_response_dump(result, json_data)
- context.log_error('Requesting access token: Config: |%s| Client id [:5]: |%s| Client secret [:5]: |%s| Code: |%s| Response dump |%s|' %
- (config_type, client_id[:5], client_secret[:5], str(result.status_code), response_dump))
- raise LoginException('Login Failed: Code %s' % str(result.status_code))
-
- if result.headers.get('content-type', '').startswith('application/json'):
- if json_data:
- return json_data
- else:
- return result.json()
- else:
- response_dump = self._get_response_dump(result, json_data)
- context.log_error('Requesting access token: Config: |%s| Client id [:5]: |%s| Client secret [:5]: |%s| Code: |%s| Response dump |%s|' %
- (config_type, client_id[:5], client_secret[:5], str(result.status_code), response_dump))
- raise LoginException('Login Failed: Unknown response')
+ client_summary = ''.join([
+ '(config_type: |', config_type, '|',
+ ' client_id: |', client_id[:5], '...|',
+ ' client_secret: |', client_secret[:5], '...|)'
+ ])
+ self.context.log_debug('Requesting access token for ' + client_summary)
+
+ result, json_data = self._request('https://www.googleapis.com/oauth2/v4/token',
+ method='POST', data=post_data, headers=headers,
+ response_hook=self._login_json_hook,
+ error_hook=self._login_error_hook,
+ error_title='Login Failed',
+ error_info='Access token request failed for ' + client_summary + ': {exc}',
+ raise_exc=LoginException('Login Failed: Unknown response')
+ )
+ return json_data
def request_device_and_user_code_tv(self):
client_id = str(self.CONFIGS['youtube-tv']['id'])
@@ -226,44 +307,25 @@ def request_device_and_user_code(self, client_id=''):
'Content-Type': 'application/x-www-form-urlencoded'}
client_id = client_id or self._config['id']
-
post_data = {'client_id': client_id,
'scope': 'https://www.googleapis.com/auth/youtube'}
- # url
- url = 'https://accounts.google.com/o/oauth2/device/code'
-
config_type = self._get_config_type(client_id)
- context.log_debug('Requesting device and user code: Config: |%s| Client id [:5]: |%s|' %
- (config_type, client_id[:5]))
-
- result = requests.post(url, data=post_data, headers=headers, verify=self._verify)
-
- try:
- json_data = result.json()
- if 'error' in json_data:
- context.log_error('Requesting device and user code failed: Code: |%s| JSON: |%s|' % (str(result.status_code), json_data))
- json_data.update({'code': str(result.status_code)})
- raise LoginException(json_data)
- except ValueError:
- json_data = None
-
- if result.status_code != requests.codes.ok:
- response_dump = self._get_response_dump(result, json_data)
- context.log_error('Requesting device and user code failed: Config: |%s| Client id [:5]: |%s| Code: |%s| Response dump |%s|' %
- (config_type, client_id[:5], str(result.status_code), response_dump))
- raise LoginException('Login Failed')
-
- if result.headers.get('content-type', '').startswith('application/json'):
- if json_data:
- return json_data
- else:
- return result.json()
- else:
- response_dump = self._get_response_dump(result, json_data)
- context.log_error('Requesting access token: Config: |%s| Client id [:5]: |%s| Code: |%s| Response dump |%s|' %
- (config_type, client_id[:5], str(result.status_code), response_dump))
- raise LoginException('Login Failed: Unknown response')
+ client_summary = ''.join([
+ '(config_type: |', config_type, '|',
+ ' client_id: |', client_id[:5], '...|)',
+ ])
+ self.context.log_debug('Requesting device and user code for ' + client_summary)
+
+ result, json_data = self._request('https://accounts.google.com/o/oauth2/device/code',
+ method='POST', data=post_data, headers=headers,
+ response_hook=self._login_json_hook,
+ error_hook=self._login_error_hook,
+ error_title='Login Failed',
+ error_info='Requesting device and user code failed for ' + client_summary + ': {exc}',
+ raise_exc=LoginException('Login Failed: Unknown response')
+ )
+ return json_data
def get_access_token(self):
return self._access_token
@@ -300,12 +362,11 @@ def authenticate(self, username, password):
# 'callerSig': '24bb24c05e47e0aefa68a58a766179d9b613a600',
'Passwd': password.encode('utf-8')}
- # url
- url = 'https://android.clients.google.com/auth'
-
- result = requests.post(url, data=post_data, headers=headers, verify=self._verify)
- if result.status_code != requests.codes.ok:
- raise LoginException('Login Failed')
+ result = self._request('https://android.clients.google.com/auth',
+ method='POST', data=post_data, headers=headers,
+ error_title='Login Failed',
+ raise_exc=LoginException
+ )
lines = result.text.replace('\n', '&')
params = dict(parse_qsl(lines))
@@ -331,15 +392,3 @@ def _get_config_type(self, client_id, client_secret=None):
if using_conf_main:
return 'YouTube-Kodi'
return 'Unknown'
-
- @staticmethod
- def _get_response_dump(response, json_data=None):
- if json_data:
- return json_data
- try:
- return response.json()
- except ValueError:
- try:
- return response.text
- except:
- return 'None'
diff --git a/resources/lib/youtube_plugin/youtube/client/youtube.py b/resources/lib/youtube_plugin/youtube/client/youtube.py
index 834d56daa..ee64358aa 100644
--- a/resources/lib/youtube_plugin/youtube/client/youtube.py
+++ b/resources/lib/youtube_plugin/youtube/client/youtube.py
@@ -12,13 +12,9 @@
import json
import re
import threading
-import traceback
import xml.etree.ElementTree as ET
-import requests
-
from .login_client import LoginClient
-from ..youtube_exceptions import YouTubeException
from ..helper.video_info import VideoInfo
from ...kodion import Context
from ...kodion.utils import datetime_parser
@@ -90,10 +86,8 @@ def update_watch_history(self, video_id, url):
if self._access_token:
params['access_token'] = self._access_token
- try:
- _ = requests.get(url, params=params, headers=headers, verify=self._verify, allow_redirects=True)
- except:
- _context.log_error('Failed to update watch history |%s|' % traceback.print_exc())
+ self._request(url, params=params, headers=headers,
+ error_msg='Failed to update watch history')
def get_video_streams(self, context, video_id):
video_info = VideoInfo(context, access_token=self._access_token_tv,
@@ -822,22 +816,12 @@ def _perform(_page_token, _offset, _result):
'Accept-Language': 'en-US,en;q=0.7,de;q=0.3'
}
- session = requests.Session()
- session.headers = headers
- session.verify = self._verify
- adapter = requests.adapters.HTTPAdapter(pool_maxsize=5, pool_block=True)
- session.mount("https://", adapter)
responses = []
def fetch_xml(_url, _responses):
- try:
- _response = session.get(_url, timeout=(3.05, 27))
- _response.raise_for_status()
- except requests.exceptions.RequestException as error:
- _context.log_debug('Response: {0}'.format(error.response and error.response.text))
- _context.log_error('Failed |%s|' % traceback.print_exc())
- return
- _responses.append(_response)
+ _response = self._request(_url, headers=headers)
+ if _response:
+ _responses.append(_response)
threads = []
for channel_id in sub_channel_ids:
@@ -851,7 +835,6 @@ def fetch_xml(_url, _responses):
for thread in threads:
thread.join(30)
- session.close()
for response in responses:
if response:
@@ -1045,31 +1028,6 @@ def _perform(_playlist_idx, _page_token, _offset, _result):
return result
- def _request(self, url, method='GET',
- cookies=None, data=None, headers=None, json=None, params=None,
- error_msg=None, raise_error=False, timeout=(3.05, 27), **_):
- try:
- result = requests.request(method, url,
- verify=self._verify,
- allow_redirects=True,
- timeout=timeout,
- cookies=cookies,
- data=data,
- headers=headers,
- json=json,
- params=params)
- result.raise_for_status()
- except requests.exceptions.RequestException as error:
- response = error.response and error.response.text
- _context.log_debug('Response: {0}'.format(response))
- _context.log_error('{0}\n{1}'.format(
- error_msg or 'Request failed', traceback.format_exc()
- ))
- if raise_error:
- raise YouTubeException(error_msg)
- return None
- return result
-
def perform_v3_request(self, method='GET', headers=None, path=None,
post_data=None, params=None, no_login=False):
diff --git a/resources/settings.xml b/resources/settings.xml
index fa8ad911f..ae4d15438 100644
--- a/resources/settings.xml
+++ b/resources/settings.xml
@@ -623,11 +623,37 @@
-
+
0
true
+
+ 0
+ 9
+
+ 3
+ 3
+ 120
+
+
+ false
+ 14045
+
+
+
+ 0
+ 27
+
+ 10
+ 1
+ 120
+
+
+ false
+ 14045
+
+
0
false
From 98fc07df5b1620742bc2e92b4cc875faae40927d Mon Sep 17 00:00:00 2001
From: MoojMidge <56883549+MoojMidge@users.noreply.github.com>
Date: Tue, 31 Oct 2023 17:28:22 +1100
Subject: [PATCH 019/141] Use new client request context manager
---
.../lib/youtube_plugin/youtube/provider.py | 37 +++++++------------
1 file changed, 14 insertions(+), 23 deletions(-)
diff --git a/resources/lib/youtube_plugin/youtube/provider.py b/resources/lib/youtube_plugin/youtube/provider.py
index 15f27c65f..34bdd8433 100644
--- a/resources/lib/youtube_plugin/youtube/provider.py
+++ b/resources/lib/youtube_plugin/youtube/provider.py
@@ -339,13 +339,6 @@ def get_client(self, context):
if refresh_tokens:
refresh_tokens = refresh_tokens.split('|')
context.log_debug('Access token count: |%d| Refresh token count: |%d|' % (len(access_tokens), len(refresh_tokens)))
- # create a new access_token
-
- if dev_keys:
- client = YouTube(language=language, region=region, items_per_page=items_per_page, config=dev_keys)
- else:
- client = YouTube(language=language, region=region, items_per_page=items_per_page, config=youtube_config)
-
else:
context.log_debug('Selecting YouTube config "%s"' % youtube_config['system'])
@@ -363,19 +356,23 @@ def get_client(self, context):
if refresh_tokens:
refresh_tokens = refresh_tokens.split('|')
context.log_debug('Access token count: |%d| Refresh token count: |%d|' % (len(access_tokens), len(refresh_tokens)))
- # create a new access_token
- client = YouTube(language=language, region=region, items_per_page=items_per_page, config=youtube_config)
- if client:
- if len(access_tokens) != 2 and len(refresh_tokens) == 2:
- try:
+ client = YouTube(language=language,
+ region=region,
+ items_per_page=items_per_page,
+ config=dev_keys if dev_keys else youtube_config)
- access_token_kodi, expires_in_kodi = client.refresh_token(refresh_tokens[1])
+ with client:
+ if not refresh_tokens or not refresh_tokens[0]:
+ client.set_log_error(context.log_error)
+ self._client = client
+ # create new access tokens
+ elif len(access_tokens) != 2 and len(refresh_tokens) == 2:
+ try:
+ access_token_kodi, expires_in_kodi = client.refresh_token(refresh_tokens[1])
access_token_tv, expires_in_tv = client.refresh_token_tv(refresh_tokens[0])
-
access_tokens = [access_token_tv, access_token_kodi]
-
access_token = '%s|%s' % (access_token_tv, access_token_kodi)
expires_in = min(expires_in_tv, expires_in_kodi)
if dev_id:
@@ -407,15 +404,9 @@ def get_client(self, context):
access_tokens = ['', '']
client.set_access_token(access_token=access_tokens[1])
client.set_access_token_tv(access_token_tv=access_tokens[0])
- self._client = client
- self._client.set_log_error(context.log_error)
- else:
- self._client = YouTube(items_per_page=items_per_page, language=language, region=region, config=youtube_config)
- self._client.set_log_error(context.log_error)
-
- # in debug log the login status
- context.log_debug('User is not logged in')
+ client.set_log_error(context.log_error)
+ self._client = client
return self._client
def get_resource_manager(self, context):
From b024e41d717a63c5478c21a92c4324e8ce85b8ca Mon Sep 17 00:00:00 2001
From: MoojMidge <56883549+MoojMidge@users.noreply.github.com>
Date: Tue, 31 Oct 2023 18:26:59 +1100
Subject: [PATCH 020/141] Ensure headers are added to cURL playback requests
---
resources/lib/youtube_plugin/kodion/impl/xbmc/xbmc_items.py | 2 ++
1 file changed, 2 insertions(+)
diff --git a/resources/lib/youtube_plugin/kodion/impl/xbmc/xbmc_items.py b/resources/lib/youtube_plugin/kodion/impl/xbmc/xbmc_items.py
index 7f715b1c2..a23ce255b 100644
--- a/resources/lib/youtube_plugin/kodion/impl/xbmc/xbmc_items.py
+++ b/resources/lib/youtube_plugin/kodion/impl/xbmc/xbmc_items.py
@@ -103,6 +103,8 @@ def to_play_item(context, play_item):
list_item.setContentLookup(False)
except:
pass
+ if not alternative_player and headers and uri.startswith('http'):
+ play_item.set_uri('|'.join([uri, headers]))
if not is_strm:
if play_item.get_play_count() == 0:
From 51e0b6addd098d0ef3e5a0312bfe8076cc20d3b0 Mon Sep 17 00:00:00 2001
From: MoojMidge <56883549+MoojMidge@users.noreply.github.com>
Date: Wed, 1 Nov 2023 16:32:09 +1100
Subject: [PATCH 021/141] Split LoginClient into base & client request class
Replace client build and request methods from VideoInfo
---
.../youtube_plugin/kodion/utils/requests.py | 120 +++++++
.../youtube/client/login_client.py | 158 ++-------
.../youtube/client/request_client.py | 322 +++++++++++++++++
.../youtube_plugin/youtube/client/youtube.py | 22 +-
.../youtube/helper/video_info.py | 334 +-----------------
.../lib/youtube_plugin/youtube/provider.py | 3 +-
6 files changed, 505 insertions(+), 454 deletions(-)
create mode 100644 resources/lib/youtube_plugin/kodion/utils/requests.py
create mode 100644 resources/lib/youtube_plugin/youtube/client/request_client.py
diff --git a/resources/lib/youtube_plugin/kodion/utils/requests.py b/resources/lib/youtube_plugin/kodion/utils/requests.py
new file mode 100644
index 000000000..64594907e
--- /dev/null
+++ b/resources/lib/youtube_plugin/kodion/utils/requests.py
@@ -0,0 +1,120 @@
+# -*- coding: utf-8 -*-
+"""
+
+ Copyright (C) 2023-present plugin.video.youtube
+
+ SPDX-License-Identifier: GPL-2.0-only
+ See LICENSES/GPL-2.0-only for more information.
+"""
+
+import atexit
+
+from traceback import format_exc, format_stack
+
+from requests import Session
+from requests.adapters import HTTPAdapter, Retry
+from requests.exceptions import RequestException
+
+
+class BaseRequestsClass(object):
+ http_adapter = HTTPAdapter(
+ pool_maxsize=10,
+ pool_block=True,
+ max_retries=Retry(
+ total=3,
+ backoff_factor=1,
+ status_forcelist={500, 502, 503, 504},
+ allowed_methods=None,
+ )
+ )
+
+ def __init__(self, context, exc_type=RequestException):
+ self._context = context
+ self._verify = self._context.get_settings().verify_ssl()
+ self._timeout = self._context.get_settings().get_timeout()
+ self._default_exc = exc_type
+
+ self._session = Session()
+ self._session.verify = self._verify
+ self._session.mount('https://', self.http_adapter)
+ atexit.register(self._session.close)
+
+ def __enter__(self):
+ return self
+
+ def __exit__(self, exc_type, exc_value, traceback):
+ self._session.close()
+
+ def request(self, url, method='GET',
+ cookies=None, data=None, headers=None, json=None, params=None,
+ response_hook=None, error_hook=None,
+ error_title=None, error_info=None, raise_exc=False, **_):
+ response = None
+ try:
+ response = self._session.request(method, url,
+ verify=self._verify,
+ allow_redirects=True,
+ timeout=self._timeout,
+ cookies=cookies,
+ data=data,
+ headers=headers,
+ json=json,
+ params=params)
+ if response_hook:
+ response = response_hook(response)
+ else:
+ response.raise_for_status()
+
+ except (RequestException, self._default_exc) as exc:
+ response_text = exc.response and exc.response.text
+ stack_trace = format_stack()
+ exc_tb = format_exc()
+
+ if error_hook:
+ error_response = error_hook(exc, response)
+ _title, _info, _response, _trace, _exc = error_response
+ if _title is not None:
+ error_title = _title
+ if _info is not None:
+ error_info = _info
+ if _response is not None:
+ response = _response
+ response_text = str(_response)
+ if _trace is not None:
+ stack_trace = _trace
+ if _exc is not None:
+ raise_exc = _exc
+
+ if error_title is None:
+ error_title = 'Request failed'
+
+ if error_info is None:
+ error_info = str(exc)
+ elif '{' in error_info:
+ try:
+ error_info = error_info.format(exc=exc)
+ except (AttributeError, IndexError, KeyError):
+ error_info = str(exc)
+
+ if response_text:
+ response_text = 'Request response:\n{0}'.format(response_text)
+
+ if stack_trace:
+ stack_trace = (
+ 'Stack trace (most recent call last):\n{0}'.format(
+ ''.join(stack_trace)
+ )
+ )
+
+ self._context.log_error('\n'.join([part for part in [
+ error_title, error_info, response_text, stack_trace, exc_tb
+ ] if part]))
+
+ if raise_exc:
+ if isinstance(raise_exc, BaseException):
+ raise raise_exc from exc
+ if not callable(raise_exc):
+ raise self._default_exc(error_title) from exc
+ raise raise_exc(error_title) from exc
+
+ return response
diff --git a/resources/lib/youtube_plugin/youtube/client/login_client.py b/resources/lib/youtube_plugin/youtube/client/login_client.py
index e9c68d4dd..0524027e9 100644
--- a/resources/lib/youtube_plugin/youtube/client/login_client.py
+++ b/resources/lib/youtube_plugin/youtube/client/login_client.py
@@ -8,25 +8,26 @@
See LICENSES/GPL-2.0-only for more information.
"""
-import atexit
import time
from urllib.parse import parse_qsl
-from requests import Session
-from requests.adapters import HTTPAdapter, Retry
-from requests.exceptions import InvalidJSONError, RequestException
+from requests.exceptions import InvalidJSONError
-from ...youtube.youtube_exceptions import (InvalidGrant,
- LoginException,
- YouTubeException,)
-from ...kodion import Context
-from .__config__ import (api,
- developer_keys,
- keys_changed,
- youtube_tv,)
+from .request_client import YouTubeRequestClient
+from ...youtube.youtube_exceptions import (
+ InvalidGrant,
+ LoginException,
+ YouTubeException,
+)
+from .__config__ import (
+ api,
+ developer_keys,
+ keys_changed,
+ youtube_tv,
+)
-class LoginClient(object):
+class LoginClient(YouTubeRequestClient):
api_keys_changed = keys_changed
CONFIGS = {
@@ -45,46 +46,25 @@ class LoginClient(object):
'developer': developer_keys
}
- http_adapter = HTTPAdapter(
- pool_maxsize=10,
- pool_block=True,
- max_retries=Retry(
- total=3,
- backoff_factor=1,
- status_forcelist={500, 502, 503, 504},
- allowed_methods=None,
- )
- )
-
- context = Context(plugin_id='plugin.video.youtube')
+ def __init__(self, context, config=None, language='en-US', region='',
+ access_token='', access_token_tv=''):
+ self._context = context
- def __init__(self, config=None, language='en-US', region='', access_token='', access_token_tv=''):
self._config = self.CONFIGS['main'] if config is None else config
self._config_tv = self.CONFIGS['youtube-tv']
- self._verify = self.context.get_settings().verify_ssl()
- self._timeout = self.context.get_settings().get_timeout()
# the default language is always en_US (like YouTube on the WEB)
if not language:
language = 'en_US'
-
language = language.replace('-', '_')
-
self._language = language
self._region = region
+
self._access_token = access_token
self._access_token_tv = access_token_tv
- self._log_error_callback = None
- self._session = Session()
- self._session.verify = self._verify
- self._session.mount('https://', self.http_adapter)
- atexit.register(self._session.close)
-
- def __enter__(self):
- return self
+ self._log_error_callback = None
- def __exit__(self, exc_type, exc_value, traceback):
- self._session.close()
+ super(LoginClient, self).__init__(context=context)
@staticmethod
def _login_json_hook(response):
@@ -93,10 +73,12 @@ def _login_json_hook(response):
json_data = response.json()
if 'error' in json_data:
raise YouTubeException('"error" in response JSON data',
- json_data=json_data)
+ json_data=json_data,
+ response=response,)
except ValueError as error:
raise InvalidJSONError(error, response=response)
- return response, json_data
+ response.raise_for_status()
+ return json_data
@staticmethod
def _login_error_hook(error, response):
@@ -104,86 +86,12 @@ def _login_error_hook(error, response):
if not json_data:
return None, None, None, None, LoginException
if json_data['error'] == 'authorization_pending':
- return None, None, None, False, False
+ return None, None, json_data, False, False
if (json_data['error'] == 'invalid_grant'
and json_data.get('code') == '400'):
return None, None, json_data, False, InvalidGrant(json_data)
return None, None, json_data, False, LoginException(json_data)
- def _request(self, url, method='GET',
- cookies=None, data=None, headers=None, json=None, params=None,
- response_hook=None, error_hook=None,
- error_title=None, error_info=None, raise_exc=False, **_):
- response = None
- try:
- response = self._session.request(method, url,
- verify=self._verify,
- allow_redirects=True,
- timeout=self._timeout,
- cookies=cookies,
- data=data,
- headers=headers,
- json=json,
- params=params)
- response.raise_for_status()
- if response_hook:
- response = response_hook(response)
-
- except (RequestException, YouTubeException) as exc:
- from traceback import format_exc, format_stack
-
- response_text = exc.response and exc.response.text
- stack_trace = format_stack()
- exc_tb = format_exc()
-
- if error_hook:
- error_response = error_hook(exc, response)
- _title, _info, _response, _trace, _exc = error_response
- if _title is not None:
- error_title = _title
- if _info is not None:
- error_info = _info
- if _response is not None:
- response_text = _response
- if _trace is not None:
- stack_trace = _trace
- if _exc is not None:
- raise_exc = _exc
-
- if error_title is None:
- error_title = 'Request failed'
-
- if error_info is None:
- error_info = str(exc)
- elif '{' in error_info:
- try:
- error_info = error_info.format(exc=exc)
- except (AttributeError, IndexError, KeyError):
- error_info = str(exc)
-
- if response_text:
- response_text = 'Request response:\n{0}'.format(response_text)
-
- if stack_trace:
- stack_trace = (
- 'Stack trace (most recent call last):\n{0}'.format(
- ''.join(stack_trace)
- )
- )
-
- self.context.log_error('\n'.join([part for part in [
- error_title, error_info, response_text, stack_trace, exc_tb
- ] if part]))
-
- if raise_exc:
- if isinstance(raise_exc, BaseException):
- raise raise_exc
- if not callable(raise_exc):
- raise YouTubeException(error_title)
- raise raise_exc(error_title)
-
- return response
-
def set_log_error(self, callback):
self._log_error_callback = callback
@@ -210,7 +118,7 @@ def revoke(self, refresh_token):
post_data = {'token': refresh_token}
- self._request('https://accounts.google.com/o/oauth2/revoke',
+ self.request('https://accounts.google.com/o/oauth2/revoke',
method='POST', data=post_data, headers=headers,
response_hook=self._login_json_hook,
error_hook=self._login_error_hook,
@@ -243,9 +151,9 @@ def refresh_token(self, refresh_token, client_id='', client_secret=''):
' client_id: |', client_id[:5], '...|',
' client_secret: |', client_secret[:5], '...|)'
])
- self.context.log_debug('Refresh token for ' + client_summary)
+ self._context.log_debug('Refresh token for ' + client_summary)
- result, json_data = self._request('https://www.googleapis.com/oauth2/v4/token',
+ json_data = self.request('https://www.googleapis.com/oauth2/v4/token',
method='POST', data=post_data, headers=headers,
response_hook=self._login_json_hook,
error_hook=self._login_error_hook,
@@ -284,9 +192,9 @@ def request_access_token(self, code, client_id='', client_secret=''):
' client_id: |', client_id[:5], '...|',
' client_secret: |', client_secret[:5], '...|)'
])
- self.context.log_debug('Requesting access token for ' + client_summary)
+ self._context.log_debug('Requesting access token for ' + client_summary)
- result, json_data = self._request('https://www.googleapis.com/oauth2/v4/token',
+ json_data = self.request('https://www.googleapis.com/oauth2/v4/token',
method='POST', data=post_data, headers=headers,
response_hook=self._login_json_hook,
error_hook=self._login_error_hook,
@@ -315,9 +223,9 @@ def request_device_and_user_code(self, client_id=''):
'(config_type: |', config_type, '|',
' client_id: |', client_id[:5], '...|)',
])
- self.context.log_debug('Requesting device and user code for ' + client_summary)
+ self._context.log_debug('Requesting device and user code for ' + client_summary)
- result, json_data = self._request('https://accounts.google.com/o/oauth2/device/code',
+ json_data = self.request('https://accounts.google.com/o/oauth2/device/code',
method='POST', data=post_data, headers=headers,
response_hook=self._login_json_hook,
error_hook=self._login_error_hook,
@@ -362,7 +270,7 @@ def authenticate(self, username, password):
# 'callerSig': '24bb24c05e47e0aefa68a58a766179d9b613a600',
'Passwd': password.encode('utf-8')}
- result = self._request('https://android.clients.google.com/auth',
+ result = self.request('https://android.clients.google.com/auth',
method='POST', data=post_data, headers=headers,
error_title='Login Failed',
raise_exc=LoginException
diff --git a/resources/lib/youtube_plugin/youtube/client/request_client.py b/resources/lib/youtube_plugin/youtube/client/request_client.py
new file mode 100644
index 000000000..7b2c482fd
--- /dev/null
+++ b/resources/lib/youtube_plugin/youtube/client/request_client.py
@@ -0,0 +1,322 @@
+# -*- coding: utf-8 -*-
+"""
+
+ Copyright (C) 2023-present plugin.video.youtube
+
+ SPDX-License-Identifier: GPL-2.0-only
+ See LICENSES/GPL-2.0-only for more information.
+"""
+
+from ...kodion.utils.requests import BaseRequestsClass
+from ...youtube.youtube_exceptions import YouTubeException
+
+
+class YouTubeRequestClient(BaseRequestsClass):
+ CLIENTS = {
+ # 4k no VP9 HDR
+ # Limited subtitle availability
+ 'android_testsuite': {
+ '_id': 30,
+ '_query_subtitles': True,
+ 'json': {
+ 'params': '2AMBCgIQBg',
+ 'context': {
+ 'client': {
+ 'clientName': 'ANDROID_TESTSUITE',
+ 'clientVersion': '1.9',
+ 'androidSdkVersion': '29',
+ 'osName': 'Android',
+ 'osVersion': '10',
+ 'platform': 'MOBILE',
+ },
+ },
+ },
+ 'headers': {
+ 'User-Agent': ('com.google.android.youtube/'
+ '{json[context][client][clientVersion]}'
+ ' (Linux; U; {json[context][client][osName]}'
+ ' {json[context][client][osVersion]};'
+ ' {json[context][client][gl]}) gzip'),
+ 'X-YouTube-Client-Name': '{_id}',
+ 'X-YouTube-Client-Version': '{json[context][client][clientVersion]}',
+ },
+ 'params': {
+ 'key': 'AIzaSyA8eiZmM1FaDVjRy-df2KTyQ_vz_yYM39w',
+ },
+ },
+ 'android': {
+ '_id': 3,
+ 'json': {
+ 'params': '2AMBCgIQBg',
+ 'context': {
+ 'client': {
+ 'clientName': 'ANDROID',
+ 'clientVersion': '17.31.35',
+ 'androidSdkVersion': '30',
+ 'osName': 'Android',
+ 'osVersion': '11',
+ 'platform': 'MOBILE',
+ },
+ },
+ },
+ 'headers': {
+ 'User-Agent': ('com.google.android.youtube/'
+ '{json[context][client][clientVersion]}'
+ ' (Linux; U; {json[context][client][osName]}'
+ ' {json[context][client][osVersion]};'
+ ' {json[context][client][gl]}) gzip'),
+ 'X-YouTube-Client-Name': '{_id}',
+ 'X-YouTube-Client-Version': '{json[context][client][clientVersion]}',
+ },
+ 'params': {
+ 'key': 'AIzaSyA8eiZmM1FaDVjRy-df2KTyQ_vz_yYM39w',
+ },
+ },
+ # Only for videos that allow embedding
+ # Limited to 720p on some videos
+ 'android_embedded': {
+ '_id': 55,
+ 'json': {
+ 'params': '2AMBCgIQBg',
+ 'context': {
+ 'client': {
+ 'clientName': 'ANDROID_EMBEDDED_PLAYER',
+ 'clientVersion': '17.36.4',
+ 'clientScreen': 'EMBED',
+ 'androidSdkVersion': '29',
+ 'osName': 'Android',
+ 'osVersion': '10',
+ 'platform': 'MOBILE',
+ },
+ },
+ 'thirdParty': {
+ 'embedUrl': 'https://www.youtube.com/embed/{json[videoId]}',
+ },
+ },
+ 'headers': {
+ 'User-Agent': ('com.google.android.youtube/'
+ '{json[context][client][clientVersion]}'
+ ' (Linux; U; {json[context][client][osName]}'
+ ' {json[context][client][osVersion]};'
+ ' {json[context][client][gl]}) gzip'),
+ 'X-YouTube-Client-Name': '{_id}',
+ 'X-YouTube-Client-Version': '{json[context][client][clientVersion]}',
+ },
+ 'params': {
+ 'key': 'AIzaSyCjc_pVEDi4qsv5MtC2dMXzpIaDoRFLsxw',
+ },
+ },
+ # 4k with HDR
+ # Some videos block this client, may also require embedding enabled
+ # Limited subtitle availability
+ 'android_youtube_tv': {
+ '_id': 29,
+ '_query_subtitles': True,
+ 'json': {
+ 'params': '2AMBCgIQBg',
+ 'context': {
+ 'client': {
+ 'clientName': 'ANDROID_UNPLUGGED',
+ 'clientVersion': '6.36',
+ 'androidSdkVersion': '29',
+ 'osName': 'Android',
+ 'osVersion': '10',
+ 'platform': 'MOBILE',
+ },
+ },
+ },
+ 'headers': {
+ 'User-Agent': ('com.google.android.apps.youtube.unplugged/'
+ '{json[context][client][clientVersion]}'
+ ' (Linux; U; {json[context][client][osName]}'
+ ' {json[context][client][osVersion]};'
+ ' {json[context][client][gl]}) gzip'),
+ 'X-YouTube-Client-Name': '{_id}',
+ 'X-YouTube-Client-Version': '{json[context][client][clientVersion]}',
+ },
+ 'params': {
+ 'key': 'AIzaSyA8eiZmM1FaDVjRy-df2KTyQ_vz_yYM39w',
+ },
+ },
+ 'ios': {
+ '_id': 5,
+ 'json': {
+ 'context': {
+ 'client': {
+ 'clientName': 'IOS',
+ 'clientVersion': '17.33.2',
+ 'deviceModel': 'iPhone14,3',
+ 'osName': 'iOS',
+ 'osVersion': '15_6',
+ 'platform': 'MOBILE',
+ },
+ },
+ },
+ 'headers': {
+ 'User-Agent': ('com.google.ios.youtube/'
+ '{json[context][client][clientVersion]}'
+ ' ({json[context][client][deviceModel]};'
+ ' U; CPU {json[context][client][osName]}'
+ ' {json[context][client][osVersion]}'
+ ' like Mac OS X)'),
+ 'X-YouTube-Client-Name': '{_id}',
+ 'X-YouTube-Client-Version': '{json[context][client][clientVersion]}',
+ },
+ 'params': {
+ 'key': 'AIzaSyB-63vPrdThhKuerbB2N_l7Kwwcxj6yUAc',
+ },
+ },
+ # Used to requests captions for clients that don't provide them
+ # Requires handling of nsig to overcome throttling (TODO)
+ 'smarttv_embedded': {
+ '_id': 85,
+ 'json': {
+ 'params': '2AMBCgIQBg',
+ 'context': {
+ 'client': {
+ 'clientName': 'TVHTML5_SIMPLY_EMBEDDED_PLAYER',
+ 'clientScreen': 'WATCH',
+ 'clientVersion': '2.0',
+ },
+ },
+ 'thirdParty': {
+ 'embedUrl': 'https://www.youtube.com',
+ },
+ },
+ # Headers from a 2022 Samsung Tizen 6.5 based Smart TV
+ 'headers': {
+ 'User-Agent': ('Mozilla/5.0 (SMART-TV; LINUX; Tizen 6.5)'
+ ' AppleWebKit/537.36 (KHTML, like Gecko)'
+ ' 85.0.4183.93/6.5 TV Safari/537.36'),
+ },
+ 'params': {
+ 'key': 'AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8',
+ },
+ },
+ # Used for misc api requests by default
+ # Requires handling of nsig to overcome throttling (TODO)
+ 'web': {
+ '_id': 1,
+ 'json': {
+ 'context': {
+ 'client': {
+ 'clientName': 'WEB',
+ 'clientVersion': '2.20220801.00.00',
+ },
+ },
+ },
+ # Headers for a "Galaxy S20 Ultra" from Chrome dev tools device
+ # emulation
+ 'headers': {
+ 'User-Agent': ('Mozilla/5.0 (Linux; Android 10; SM-G981B)'
+ ' AppleWebKit/537.36 (KHTML, like Gecko)'
+ ' Chrome/80.0.3987.162 Mobile Safari/537.36'),
+ 'Referer': 'https://www.youtube.com/watch?v={json[videoId]}'
+ },
+ 'params': {
+ 'key': 'AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8',
+ },
+ },
+ '_common': {
+ '_access_token': None,
+ 'json': {
+ 'contentCheckOk': True,
+ 'context': {
+ 'client': {
+ 'gl': None,
+ 'hl': None,
+ },
+ },
+ 'playbackContext': {
+ 'contentPlaybackContext': {
+ 'html5Preference': 'HTML5_PREF_WANTS',
+ },
+ },
+ 'racyCheckOk': True,
+ 'thirdParty': {},
+ 'user': {
+ 'lockedSafetyMode': False
+ },
+ 'videoId': None,
+ },
+ 'headers': {
+ 'Origin': 'https://www.youtube.com',
+ 'Referer': 'https://www.youtube.com/watch?v={json[videoId]}',
+ 'Accept-Encoding': 'gzip, deflate',
+ 'Accept-Charset': 'ISO-8859-1,utf-8;q=0.7,*;q=0.7',
+ 'Accept': '*/*',
+ 'Accept-Language': 'en-US,en;q=0.5',
+ 'Authorization': 'Bearer {_access_token}',
+ },
+ 'params': {
+ 'key': None,
+ 'prettyPrint': 'false'
+ },
+ },
+ }
+
+ def __init__(self, context):
+ super(YouTubeRequestClient, self).__init__(
+ context=context, exc_type=YouTubeException
+ )
+
+ @staticmethod
+ def json_traverse(json_data, path):
+ if not json_data or not path:
+ return None
+
+ result = json_data
+ for keys in path:
+ is_dict = isinstance(result, dict)
+ if not is_dict and not isinstance(result, list):
+ return None
+
+ if not isinstance(keys, (list, tuple)):
+ keys = [keys]
+ for key in keys:
+ if is_dict:
+ if key not in result:
+ continue
+ elif not isinstance(key, int) or len(result) <= key:
+ continue
+ result = result[key]
+ break
+ else:
+ return None
+
+ if result == json_data:
+ return None
+ return result
+
+ def build_client(self, client_name, auth_header=False):
+ def _merge_dicts(item1, item2, _=Ellipsis):
+ if not isinstance(item1, dict) or not isinstance(item2, dict):
+ return item1 if item2 is _ else item2
+ new = {}
+ keys = set(item1)
+ keys.update(item2)
+ for key in keys:
+ value = _merge_dicts(item1.get(key, _), item2.get(key, _))
+ if value is _:
+ continue
+ if isinstance(value, str) and '{' in value:
+ _format['{0}.{1}'.format(id(new), key)] = (new, key, value)
+ new[key] = value
+ return new or _
+ _format = {}
+
+ client = (self.CLIENTS.get(client_name) or self.CLIENTS['web']).copy()
+ client = _merge_dicts(self.CLIENTS['_common'], client)
+
+ client['json']['videoId'] = self.video_id
+ if auth_header and self._access_token:
+ client['_access_token'] = self._access_token
+ client['params'] = None
+ elif 'Authorization' in client['headers']:
+ del client['headers']['Authorization']
+
+ for values, key, value in _format.values():
+ if key in values:
+ values[key] = value.format(**client)
+
+ return client
diff --git a/resources/lib/youtube_plugin/youtube/client/youtube.py b/resources/lib/youtube_plugin/youtube/client/youtube.py
index ee64358aa..03a959ad3 100644
--- a/resources/lib/youtube_plugin/youtube/client/youtube.py
+++ b/resources/lib/youtube_plugin/youtube/client/youtube.py
@@ -24,13 +24,13 @@
class YouTube(LoginClient):
- def __init__(self, config=None, language='en-US', region='US', items_per_page=50, access_token='', access_token_tv=''):
- if config is None:
- config = {}
- LoginClient.__init__(self, config=config, language=language, region=region, access_token=access_token,
- access_token_tv=access_token_tv)
+ def __init__(self, **kwargs):
+ if not kwargs.get('config'):
+ kwargs['config'] = {}
+ if 'items_per_page' in kwargs:
+ self._max_results = kwargs.pop('items_per_page')
- self._max_results = items_per_page
+ super(YouTube, self).__init__(**kwargs)
def get_max_results(self):
return self._max_results
@@ -86,8 +86,8 @@ def update_watch_history(self, video_id, url):
if self._access_token:
params['access_token'] = self._access_token
- self._request(url, params=params, headers=headers,
- error_msg='Failed to update watch history')
+ self.request(url, params=params, headers=headers,
+ error_msg='Failed to update watch history')
def get_video_streams(self, context, video_id):
video_info = VideoInfo(context, access_token=self._access_token_tv,
@@ -819,7 +819,7 @@ def _perform(_page_token, _offset, _result):
responses = []
def fetch_xml(_url, _responses):
- _response = self._request(_url, headers=headers)
+ _response = self.request(_url, headers=headers)
if _response:
_responses.append(_response)
@@ -1059,7 +1059,7 @@ def perform_v3_request(self, method='GET', headers=None, path=None,
log_params = None
_context.log_debug('[data] v3 request: |{0}| path: |{1}| params: |{2}| post_data: |{3}|'.format(method, path, log_params, post_data))
- result = self._request(_url, method=method, headers=_headers, json=post_data, params=_params)
+ result = self.request(_url, method=method, headers=_headers, json=post_data, params=_params)
if result is None:
return {}
@@ -1111,7 +1111,7 @@ def perform_v1_tv_request(self, method='GET', headers=None, path=None,
log_params = None
_context.log_debug('[data] v1 request: |{0}| path: |{1}| params: |{2}| post_data: |{3}|'.format(method, path, log_params, post_data))
- result = self._request(_url, method=method, headers=_headers, json=post_data, params=_params)
+ result = self.request(_url, method=method, headers=_headers, json=post_data, params=_params)
if result is None:
return {}
diff --git a/resources/lib/youtube_plugin/youtube/helper/video_info.py b/resources/lib/youtube_plugin/youtube/helper/video_info.py
index f57a5cd2d..32f52eb54 100644
--- a/resources/lib/youtube_plugin/youtube/helper/video_info.py
+++ b/resources/lib/youtube_plugin/youtube/helper/video_info.py
@@ -24,9 +24,9 @@
urljoin,
)
-import requests
import xbmcvfs
+from ..client.request_client import YouTubeRequestClient
from ...kodion.utils import is_httpd_live, make_dirs, DataCache
from ..youtube_exceptions import YouTubeException
from .subtitles import Subtitles
@@ -34,7 +34,7 @@
from .signature.cipher import Cipher
-class VideoInfo(object):
+class VideoInfo(YouTubeRequestClient):
FORMAT = {
# === Non-DASH ===
'5': {'container': 'flv',
@@ -584,256 +584,12 @@ class VideoInfo(object):
'video': {'height': 0, 'encoding': ''}}
}
- CLIENTS = {
- # 4k no VP9 HDR
- # Limited subtitle availability
- 'android_testsuite': {
- '_id': 30,
- '_query_subtitles': True,
- 'json': {
- 'params': '2AMBCgIQBg',
- 'context': {
- 'client': {
- 'clientName': 'ANDROID_TESTSUITE',
- 'clientVersion': '1.9',
- 'androidSdkVersion': '29',
- 'osName': 'Android',
- 'osVersion': '10',
- 'platform': 'MOBILE',
- },
- },
- },
- 'headers': {
- 'User-Agent': ('com.google.android.youtube/'
- '{json[context][client][clientVersion]}'
- ' (Linux; U; {json[context][client][osName]}'
- ' {json[context][client][osVersion]};'
- ' {json[context][client][gl]}) gzip'),
- 'X-YouTube-Client-Name': '{_id}',
- 'X-YouTube-Client-Version': '{json[context][client][clientVersion]}',
- },
- 'params': {
- 'key': 'AIzaSyA8eiZmM1FaDVjRy-df2KTyQ_vz_yYM39w',
- },
- },
- 'android': {
- '_id': 3,
- 'json': {
- 'params': '2AMBCgIQBg',
- 'context': {
- 'client': {
- 'clientName': 'ANDROID',
- 'clientVersion': '17.31.35',
- 'androidSdkVersion': '30',
- 'osName': 'Android',
- 'osVersion': '11',
- 'platform': 'MOBILE',
- },
- },
- },
- 'headers': {
- 'User-Agent': ('com.google.android.youtube/'
- '{json[context][client][clientVersion]}'
- ' (Linux; U; {json[context][client][osName]}'
- ' {json[context][client][osVersion]};'
- ' {json[context][client][gl]}) gzip'),
- 'X-YouTube-Client-Name': '{_id}',
- 'X-YouTube-Client-Version': '{json[context][client][clientVersion]}',
- },
- 'params': {
- 'key': 'AIzaSyA8eiZmM1FaDVjRy-df2KTyQ_vz_yYM39w',
- },
- },
- # Only for videos that allow embedding
- # Limited to 720p on some videos
- 'android_embedded': {
- '_id': 55,
- 'json': {
- 'params': '2AMBCgIQBg',
- 'context': {
- 'client': {
- 'clientName': 'ANDROID_EMBEDDED_PLAYER',
- 'clientVersion': '17.36.4',
- 'clientScreen': 'EMBED',
- 'androidSdkVersion': '29',
- 'osName': 'Android',
- 'osVersion': '10',
- 'platform': 'MOBILE',
- },
- },
- 'thirdParty': {
- 'embedUrl': 'https://www.youtube.com/embed/{json[videoId]}',
- },
- },
- 'headers': {
- 'User-Agent': ('com.google.android.youtube/'
- '{json[context][client][clientVersion]}'
- ' (Linux; U; {json[context][client][osName]}'
- ' {json[context][client][osVersion]};'
- ' {json[context][client][gl]}) gzip'),
- 'X-YouTube-Client-Name': '{_id}',
- 'X-YouTube-Client-Version': '{json[context][client][clientVersion]}',
- },
- 'params': {
- 'key': 'AIzaSyCjc_pVEDi4qsv5MtC2dMXzpIaDoRFLsxw',
- },
- },
- # 4k with HDR
- # Some videos block this client, may also require embedding enabled
- # Limited subtitle availability
- 'android_youtube_tv': {
- '_id': 29,
- '_query_subtitles': True,
- 'json': {
- 'params': '2AMBCgIQBg',
- 'context': {
- 'client': {
- 'clientName': 'ANDROID_UNPLUGGED',
- 'clientVersion': '6.36',
- 'androidSdkVersion': '29',
- 'osName': 'Android',
- 'osVersion': '10',
- 'platform': 'MOBILE',
- },
- },
- },
- 'headers': {
- 'User-Agent': ('com.google.android.apps.youtube.unplugged/'
- '{json[context][client][clientVersion]}'
- ' (Linux; U; {json[context][client][osName]}'
- ' {json[context][client][osVersion]};'
- ' {json[context][client][gl]}) gzip'),
- 'X-YouTube-Client-Name': '{_id}',
- 'X-YouTube-Client-Version': '{json[context][client][clientVersion]}',
- },
- 'params': {
- 'key': 'AIzaSyA8eiZmM1FaDVjRy-df2KTyQ_vz_yYM39w',
- },
- },
- 'ios': {
- '_id': 5,
- 'json': {
- 'context': {
- 'client': {
- 'clientName': 'IOS',
- 'clientVersion': '17.33.2',
- 'deviceModel': 'iPhone14,3',
- 'osName': 'iOS',
- 'osVersion': '15_6',
- 'platform': 'MOBILE',
- },
- },
- },
- 'headers': {
- 'User-Agent': ('com.google.ios.youtube/'
- '{json[context][client][clientVersion]}'
- ' ({json[context][client][deviceModel]};'
- ' U; CPU {json[context][client][osName]}'
- ' {json[context][client][osVersion]}'
- ' like Mac OS X)'),
- 'X-YouTube-Client-Name': '{_id}',
- 'X-YouTube-Client-Version': '{json[context][client][clientVersion]}',
- },
- 'params': {
- 'key': 'AIzaSyB-63vPrdThhKuerbB2N_l7Kwwcxj6yUAc',
- },
- },
- # Used to requests captions for clients that don't provide them
- # Requires handling of nsig to overcome throttling (TODO)
- 'smarttv_embedded': {
- '_id': 85,
- 'json': {
- 'params': '2AMBCgIQBg',
- 'context': {
- 'client': {
- 'clientName': 'TVHTML5_SIMPLY_EMBEDDED_PLAYER',
- 'clientScreen': 'WATCH',
- 'clientVersion': '2.0',
- },
- },
- 'thirdParty': {
- 'embedUrl': 'https://www.youtube.com',
- },
- },
- # Headers from a 2022 Samsung Tizen 6.5 based Smart TV
- 'headers': {
- 'User-Agent': ('Mozilla/5.0 (SMART-TV; LINUX; Tizen 6.5)'
- ' AppleWebKit/537.36 (KHTML, like Gecko)'
- ' 85.0.4183.93/6.5 TV Safari/537.36'),
- },
- 'params': {
- 'key': 'AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8',
- },
- },
- # Used for misc api requests by default
- # Requires handling of nsig to overcome throttling (TODO)
- 'web': {
- '_id': 1,
- 'json': {
- 'context': {
- 'client': {
- 'clientName': 'WEB',
- 'clientVersion': '2.20220801.00.00',
- },
- },
- },
- # Headers for a "Galaxy S20 Ultra" from Chrome dev tools device
- # emulation
- 'headers': {
- 'User-Agent': ('Mozilla/5.0 (Linux; Android 10; SM-G981B)'
- ' AppleWebKit/537.36 (KHTML, like Gecko)'
- ' Chrome/80.0.3987.162 Mobile Safari/537.36'),
- 'Referer': 'https://www.youtube.com/watch?v={json[videoId]}'
- },
- 'params': {
- 'key': 'AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8',
- },
- },
- '_common': {
- '_access_token': None,
- 'json': {
- 'contentCheckOk': True,
- 'context': {
- 'client': {
- 'gl': None,
- 'hl': None,
- },
- },
- 'playbackContext': {
- 'contentPlaybackContext': {
- 'html5Preference': 'HTML5_PREF_WANTS',
- },
- },
- 'racyCheckOk': True,
- 'thirdParty': {},
- 'user': {
- 'lockedSafetyMode': False
- },
- 'videoId': None,
- },
- 'headers': {
- 'Origin': 'https://www.youtube.com',
- 'Referer': 'https://www.youtube.com/watch?v={json[videoId]}',
- 'Accept-Encoding': 'gzip, deflate',
- 'Accept-Charset': 'ISO-8859-1,utf-8;q=0.7,*;q=0.7',
- 'Accept': '*/*',
- 'Accept-Language': 'en-US,en;q=0.5',
- 'Authorization': 'Bearer {_access_token}',
- },
- 'params': {
- 'key': None,
- 'prettyPrint': 'false'
- },
- },
- }
-
def __init__(self, context, access_token='', language='en-US'):
settings = context.get_settings()
self.video_id = None
self._context = context
self._data_cache = self._context.get_data_cache()
- self._verify = settings.verify_ssl()
self._language = (settings.get_string('youtube.language', language)
.replace('-', '_'))
self._language_base = self._language[0:2]
@@ -887,6 +643,8 @@ def __init__(self, context, access_token='', language='en-US'):
'gl': settings.get_string('youtube.region', 'US'),
}
+ super(VideoInfo, self).__init__(context=context)
+
@staticmethod
def _generate_cpn():
# https://github.com/rg3/youtube-dl/blob/master/youtube_dl/extractor/youtube.py#L1381
@@ -902,73 +660,15 @@ def load_stream_infos(self, video_id):
self.video_id = video_id
return self._get_video_info()
- def _build_client(self, client_name, auth_header=False):
- def _merge_dicts(item1, item2, _=Ellipsis):
- if not isinstance(item1, dict) or not isinstance(item2, dict):
- return item1 if item2 is _ else item2
- new = {}
- keys = set(item1)
- keys.update(item2)
- for key in keys:
- value = _merge_dicts(item1.get(key, _), item2.get(key, _))
- if value is _:
- continue
- if isinstance(value, str) and '{' in value:
- _format['{0}.{1}'.format(id(new), key)] = (new, key, value)
- new[key] = value
- return new or _
- _format = {}
-
- client = (self.CLIENTS.get(client_name) or self.CLIENTS['web']).copy()
- client = _merge_dicts(self.CLIENTS['_common'], client)
-
- client['json']['videoId'] = self.video_id
- if auth_header and self._access_token:
- client['_access_token'] = self._access_token
- client['params'] = None
- elif 'Authorization' in client['headers']:
- del client['headers']['Authorization']
-
- for values, key, value in _format.values():
- if key in values:
- values[key] = value.format(**client)
-
- return client
-
- def _request(self, url, method='GET',
- cookies=None, data=None, headers=None, json=None, params=None,
- error_msg=None, raise_error=False, timeout=(3.05, 27), **_):
- try:
- result = requests.request(method, url,
- verify=self._verify,
- allow_redirects=True,
- timeout=timeout,
- cookies=cookies,
- data=data,
- headers=headers,
- json=json,
- params=params)
- result.raise_for_status()
- except requests.exceptions.RequestException as error:
- response = error.response and error.response.text
- self._context.log_debug('Response: {0}'.format(response))
- self._context.log_error('{0}\n{1}'.format(
- error_msg or 'Request failed', traceback.format_exc()
- ))
- if raise_error:
- raise YouTubeException(error_msg)
- return None
- return result
-
def _get_player_page(self, client='web', embed=False):
- client = self._build_client(client)
+ client = self.build_client(client)
if embed:
url = 'https://www.youtube.com/embed/{0}'.format(self.video_id)
else:
url = 'https://www.youtube.com/watch?v={0}'.format(self.video_id)
cookies = {'CONSENT': 'YES+cb.20210615-14-p0.en+FX+294'}
- result = self._request(
+ result = self.request(
url, cookies=cookies, headers=client['headers'],
error_msg=('Failed to get player html for video_id: {0}'
.format(self.video_id))
@@ -1045,8 +745,8 @@ def _get_player_js(self):
if cached_js:
return cached_js
- client = self._build_client('web')
- result = self._request(
+ client = self.build_client('web')
+ result = self.request(
js_url, headers=client['headers'],
error_msg=('Failed to get player js for video_id: {0}'
.format(self.video_id))
@@ -1093,10 +793,10 @@ def _load_hls_manifest(self, url, live_type=None, meta_info=None,
if 'Authorization' in headers:
del headers['Authorization']
else:
- headers = self._build_client('web')['headers']
+ headers = self.build_client('web')['headers']
curl_headers = self._make_curl_headers(headers, cookies=None)
- result = self._request(
+ result = self.request(
url, headers=headers,
error_msg=('Failed to get manifest for video_id: {0}'
.format(self.video_id))
@@ -1120,7 +820,7 @@ def _load_hls_manifest(self, url, live_type=None, meta_info=None,
yt_format = self.FORMAT['9995']
elif live_type == 'isa_hls':
yt_format = self.FORMAT['9996']
-
+
if yt_format:
stream = {'url': url,
'meta': meta_info,
@@ -1161,7 +861,7 @@ def _create_stream_list(self, streams, meta_info=None, headers=None, playback_st
if 'Authorization' in headers:
del headers['Authorization']
else:
- headers = self._build_client('web')['headers']
+ headers = self.build_client('web')['headers']
curl_headers = self._make_curl_headers(headers, cookies=None)
if meta_info is None:
@@ -1341,9 +1041,9 @@ def _get_video_info(self):
playability_status = status = reason = None
for _ in range(2):
for client_name in self._prioritised_clients:
- client = self._build_client(client_name, auth_header)
+ client = self.build_client(client_name, auth_header)
- result = self._request(
+ result = self.request(
video_info_url, 'POST',
error_msg=(
'Player response failed for video_id: {0},'
@@ -1377,7 +1077,7 @@ def _get_video_info(self):
# This is used to check for error like:
# "The following content is not available on this app."
# Text will vary depending on Accept-Language and client hl
- # Youtube support url is checked instead
+ # YouTube support url is checked instead
url = self._get_error_details(
playability_status,
details=(
@@ -1444,11 +1144,11 @@ def _get_video_info(self):
if captions:
captions['headers'] = client['headers']
elif client.get('_query_subtitles'):
- result = self._request(
+ result = self.request(
video_info_url, 'POST',
error_msg=('Caption request failed to get player response for'
'video_id: {0}'.format(self.video_id)),
- **self._build_client('smarttv_embedded', True)
+ **self.build_client('smarttv_embedded', True)
)
response = result.json()
diff --git a/resources/lib/youtube_plugin/youtube/provider.py b/resources/lib/youtube_plugin/youtube/provider.py
index 34bdd8433..6af39ead8 100644
--- a/resources/lib/youtube_plugin/youtube/provider.py
+++ b/resources/lib/youtube_plugin/youtube/provider.py
@@ -357,7 +357,8 @@ def get_client(self, context):
refresh_tokens = refresh_tokens.split('|')
context.log_debug('Access token count: |%d| Refresh token count: |%d|' % (len(access_tokens), len(refresh_tokens)))
- client = YouTube(language=language,
+ client = YouTube(context=context,
+ language=language,
region=region,
items_per_page=items_per_page,
config=dev_keys if dev_keys else youtube_config)
From e2c4b62d5c6cfb16ea01e2108f1a9c37b4f138fa Mon Sep 17 00:00:00 2001
From: MoojMidge <56883549+MoojMidge@users.noreply.github.com>
Date: Wed, 1 Nov 2023 21:29:11 +1100
Subject: [PATCH 022/141] Use super() rather than hardcoded parent class
---
resources/lib/youtube_plugin/kodion/impl/abstract_settings.py | 2 +-
resources/lib/youtube_plugin/kodion/impl/xbmc/xbmc_context.py | 2 +-
.../lib/youtube_plugin/kodion/impl/xbmc/xbmc_context_ui.py | 2 +-
resources/lib/youtube_plugin/kodion/impl/xbmc/xbmc_player.py | 2 +-
.../lib/youtube_plugin/kodion/impl/xbmc/xbmc_playlist.py | 2 +-
.../youtube_plugin/kodion/impl/xbmc/xbmc_plugin_settings.py | 2 +-
.../youtube_plugin/kodion/impl/xbmc/xbmc_progress_dialog.py | 2 +-
.../kodion/impl/xbmc/xbmc_progress_dialog_bg.py | 2 +-
resources/lib/youtube_plugin/kodion/impl/xbmc/xbmc_runner.py | 2 +-
resources/lib/youtube_plugin/kodion/items/audio_item.py | 2 +-
resources/lib/youtube_plugin/kodion/items/directory_item.py | 2 +-
resources/lib/youtube_plugin/kodion/items/favorites_item.py | 2 +-
resources/lib/youtube_plugin/kodion/items/image_item.py | 2 +-
resources/lib/youtube_plugin/kodion/items/new_search_item.py | 2 +-
resources/lib/youtube_plugin/kodion/items/next_page_item.py | 2 +-
.../lib/youtube_plugin/kodion/items/search_history_item.py | 2 +-
resources/lib/youtube_plugin/kodion/items/search_item.py | 2 +-
resources/lib/youtube_plugin/kodion/items/uri_item.py | 2 +-
resources/lib/youtube_plugin/kodion/items/video_item.py | 2 +-
resources/lib/youtube_plugin/kodion/items/watch_later_item.py | 2 +-
resources/lib/youtube_plugin/kodion/json_store/api_keys.py | 2 +-
.../lib/youtube_plugin/kodion/json_store/login_tokens.py | 2 +-
resources/lib/youtube_plugin/kodion/utils/data_cache.py | 2 +-
resources/lib/youtube_plugin/kodion/utils/favorite_list.py | 2 +-
resources/lib/youtube_plugin/kodion/utils/function_cache.py | 2 +-
resources/lib/youtube_plugin/kodion/utils/http_server.py | 2 +-
resources/lib/youtube_plugin/kodion/utils/playback_history.py | 2 +-
resources/lib/youtube_plugin/kodion/utils/search_history.py | 2 +-
resources/lib/youtube_plugin/kodion/utils/watch_later_list.py | 2 +-
resources/lib/youtube_plugin/youtube/helper/url_resolver.py | 4 ++--
resources/lib/youtube_plugin/youtube/provider.py | 2 +-
31 files changed, 32 insertions(+), 32 deletions(-)
diff --git a/resources/lib/youtube_plugin/kodion/impl/abstract_settings.py b/resources/lib/youtube_plugin/kodion/impl/abstract_settings.py
index 2dd651f39..61e42fa56 100644
--- a/resources/lib/youtube_plugin/kodion/impl/abstract_settings.py
+++ b/resources/lib/youtube_plugin/kodion/impl/abstract_settings.py
@@ -16,7 +16,7 @@
class AbstractSettings(object):
def __init__(self):
- object.__init__(self)
+ super(AbstractSettings, self).__init__()
def get_string(self, setting_id, default_value=None):
raise NotImplementedError()
diff --git a/resources/lib/youtube_plugin/kodion/impl/xbmc/xbmc_context.py b/resources/lib/youtube_plugin/kodion/impl/xbmc/xbmc_context.py
index 3fc817fb1..3844277b4 100644
--- a/resources/lib/youtube_plugin/kodion/impl/xbmc/xbmc_context.py
+++ b/resources/lib/youtube_plugin/kodion/impl/xbmc/xbmc_context.py
@@ -35,7 +35,7 @@
class XbmcContext(AbstractContext):
def __init__(self, path='/', params=None, plugin_name='', plugin_id='', override=True):
- AbstractContext.__init__(self, path, params, plugin_name, plugin_id)
+ super(XbmcContext, self).__init__(path, params, plugin_name, plugin_id)
if plugin_id:
self._addon = xbmcaddon.Addon(id=plugin_id)
diff --git a/resources/lib/youtube_plugin/kodion/impl/xbmc/xbmc_context_ui.py b/resources/lib/youtube_plugin/kodion/impl/xbmc/xbmc_context_ui.py
index 4f1e50aee..618fc241d 100644
--- a/resources/lib/youtube_plugin/kodion/impl/xbmc/xbmc_context_ui.py
+++ b/resources/lib/youtube_plugin/kodion/impl/xbmc/xbmc_context_ui.py
@@ -20,7 +20,7 @@
class XbmcContextUI(AbstractContextUI):
def __init__(self, xbmc_addon, context):
- AbstractContextUI.__init__(self)
+ super(XbmcContextUI, self).__init__()
self._xbmc_addon = xbmc_addon
diff --git a/resources/lib/youtube_plugin/kodion/impl/xbmc/xbmc_player.py b/resources/lib/youtube_plugin/kodion/impl/xbmc/xbmc_player.py
index 36268e494..4b44cfaa7 100644
--- a/resources/lib/youtube_plugin/kodion/impl/xbmc/xbmc_player.py
+++ b/resources/lib/youtube_plugin/kodion/impl/xbmc/xbmc_player.py
@@ -14,7 +14,7 @@
class XbmcPlayer(AbstractPlayer):
def __init__(self, player_type, context):
- AbstractPlayer.__init__(self)
+ super(XbmcPlayer, self).__init__()
self._player_type = player_type
if player_type == 'audio':
diff --git a/resources/lib/youtube_plugin/kodion/impl/xbmc/xbmc_playlist.py b/resources/lib/youtube_plugin/kodion/impl/xbmc/xbmc_playlist.py
index b21130c86..e7c6edc12 100644
--- a/resources/lib/youtube_plugin/kodion/impl/xbmc/xbmc_playlist.py
+++ b/resources/lib/youtube_plugin/kodion/impl/xbmc/xbmc_playlist.py
@@ -17,7 +17,7 @@
class XbmcPlaylist(AbstractPlaylist):
def __init__(self, playlist_type, context):
- AbstractPlaylist.__init__(self)
+ super(XbmcPlaylist, self).__init__()
self._context = context
self._playlist = None
diff --git a/resources/lib/youtube_plugin/kodion/impl/xbmc/xbmc_plugin_settings.py b/resources/lib/youtube_plugin/kodion/impl/xbmc/xbmc_plugin_settings.py
index 3970d9c00..6e5632690 100644
--- a/resources/lib/youtube_plugin/kodion/impl/xbmc/xbmc_plugin_settings.py
+++ b/resources/lib/youtube_plugin/kodion/impl/xbmc/xbmc_plugin_settings.py
@@ -13,7 +13,7 @@
class XbmcPluginSettings(AbstractSettings):
def __init__(self, xbmc_addon):
- AbstractSettings.__init__(self)
+ super(XbmcPluginSettings, self).__init__()
self._xbmc_addon = xbmc_addon
diff --git a/resources/lib/youtube_plugin/kodion/impl/xbmc/xbmc_progress_dialog.py b/resources/lib/youtube_plugin/kodion/impl/xbmc/xbmc_progress_dialog.py
index 5aa6535d4..a1f061b99 100644
--- a/resources/lib/youtube_plugin/kodion/impl/xbmc/xbmc_progress_dialog.py
+++ b/resources/lib/youtube_plugin/kodion/impl/xbmc/xbmc_progress_dialog.py
@@ -14,7 +14,7 @@
class XbmcProgressDialog(AbstractProgressDialog):
def __init__(self, heading, text):
- AbstractProgressDialog.__init__(self, 100)
+ super(XbmcProgressDialog, self).__init__(100)
self._dialog = xbmcgui.DialogProgress()
self._dialog.create(heading, text)
diff --git a/resources/lib/youtube_plugin/kodion/impl/xbmc/xbmc_progress_dialog_bg.py b/resources/lib/youtube_plugin/kodion/impl/xbmc/xbmc_progress_dialog_bg.py
index be9b4d0ae..222cde7f7 100644
--- a/resources/lib/youtube_plugin/kodion/impl/xbmc/xbmc_progress_dialog_bg.py
+++ b/resources/lib/youtube_plugin/kodion/impl/xbmc/xbmc_progress_dialog_bg.py
@@ -14,7 +14,7 @@
class XbmcProgressDialogBG(AbstractProgressDialog):
def __init__(self, heading, text):
- AbstractProgressDialog.__init__(self, 100)
+ super(XbmcProgressDialogBG, self).__init__(100)
self._dialog = xbmcgui.DialogProgressBG()
self._dialog.create(heading, text)
diff --git a/resources/lib/youtube_plugin/kodion/impl/xbmc/xbmc_runner.py b/resources/lib/youtube_plugin/kodion/impl/xbmc/xbmc_runner.py
index 454ab1d11..0719e5de3 100644
--- a/resources/lib/youtube_plugin/kodion/impl/xbmc/xbmc_runner.py
+++ b/resources/lib/youtube_plugin/kodion/impl/xbmc/xbmc_runner.py
@@ -21,7 +21,7 @@
class XbmcRunner(AbstractProviderRunner):
def __init__(self):
- AbstractProviderRunner.__init__(self)
+ super(XbmcRunner, self).__init__()
self.handle = None
self.settings = None
diff --git a/resources/lib/youtube_plugin/kodion/items/audio_item.py b/resources/lib/youtube_plugin/kodion/items/audio_item.py
index 0d4b012ff..bd0c674e5 100644
--- a/resources/lib/youtube_plugin/kodion/items/audio_item.py
+++ b/resources/lib/youtube_plugin/kodion/items/audio_item.py
@@ -15,7 +15,7 @@
class AudioItem(BaseItem):
def __init__(self, name, uri, image='', fanart=''):
- BaseItem.__init__(self, name, uri, image, fanart)
+ super(AudioItem, self).__init__(name, uri, image, fanart)
self._duration = None
self._track_number = None
self._year = None
diff --git a/resources/lib/youtube_plugin/kodion/items/directory_item.py b/resources/lib/youtube_plugin/kodion/items/directory_item.py
index 96072485b..14b1702f8 100644
--- a/resources/lib/youtube_plugin/kodion/items/directory_item.py
+++ b/resources/lib/youtube_plugin/kodion/items/directory_item.py
@@ -13,7 +13,7 @@
class DirectoryItem(BaseItem):
def __init__(self, name, uri, image='', fanart=''):
- BaseItem.__init__(self, name, uri, image, fanart)
+ super(DirectoryItem, self).__init__(name, uri, image, fanart)
self._plot = self.get_name()
self._is_action = False
self._channel_subscription_id = None
diff --git a/resources/lib/youtube_plugin/kodion/items/favorites_item.py b/resources/lib/youtube_plugin/kodion/items/favorites_item.py
index 462c89d5c..acb939436 100644
--- a/resources/lib/youtube_plugin/kodion/items/favorites_item.py
+++ b/resources/lib/youtube_plugin/kodion/items/favorites_item.py
@@ -21,7 +21,7 @@ def __init__(self, context, alt_name=None, image=None, fanart=None):
if image is None:
image = context.create_resource_path('media/favorites.png')
- DirectoryItem.__init__(self, name, context.create_uri([constants.paths.FAVORITES, 'list']), image=image)
+ super(FavoritesItem, self).__init__(name, context.create_uri([constants.paths.FAVORITES, 'list']), image=image)
if fanart:
self.set_fanart(fanart)
else:
diff --git a/resources/lib/youtube_plugin/kodion/items/image_item.py b/resources/lib/youtube_plugin/kodion/items/image_item.py
index 3909a48d6..2a1217135 100644
--- a/resources/lib/youtube_plugin/kodion/items/image_item.py
+++ b/resources/lib/youtube_plugin/kodion/items/image_item.py
@@ -13,7 +13,7 @@
class ImageItem(BaseItem):
def __init__(self, name, uri, image='', fanart=''):
- BaseItem.__init__(self, name, uri, image, fanart)
+ super(ImageItem, self).__init__(name, uri, image, fanart)
self._title = None
def set_title(self, title):
diff --git a/resources/lib/youtube_plugin/kodion/items/new_search_item.py b/resources/lib/youtube_plugin/kodion/items/new_search_item.py
index 8661f300f..5499e598c 100644
--- a/resources/lib/youtube_plugin/kodion/items/new_search_item.py
+++ b/resources/lib/youtube_plugin/kodion/items/new_search_item.py
@@ -31,7 +31,7 @@ def __init__(self, context, alt_name=None, image=None, fanart=None, incognito=Fa
if location:
item_params.update({'location': location})
- DirectoryItem.__init__(self, name, context.create_uri([constants.paths.SEARCH, 'input'], params=item_params), image=image)
+ super(NewSearchItem, self).__init__(name, context.create_uri([constants.paths.SEARCH, 'input'], params=item_params), image=image)
if fanart:
self.set_fanart(fanart)
else:
diff --git a/resources/lib/youtube_plugin/kodion/items/next_page_item.py b/resources/lib/youtube_plugin/kodion/items/next_page_item.py
index 5b54b7868..4aa83a91b 100644
--- a/resources/lib/youtube_plugin/kodion/items/next_page_item.py
+++ b/resources/lib/youtube_plugin/kodion/items/next_page_item.py
@@ -21,7 +21,7 @@ def __init__(self, context, current_page=1, image=None, fanart=None):
if name.find('%d') != -1:
name %= current_page + 1
- DirectoryItem.__init__(self, name, context.create_uri(context.get_path(), new_params), image=image)
+ super(NextPageItem, self).__init__(name, context.create_uri(context.get_path(), new_params), image=image)
if fanart:
self.set_fanart(fanart)
else:
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 28242c436..10b8c7646 100644
--- a/resources/lib/youtube_plugin/kodion/items/search_history_item.py
+++ b/resources/lib/youtube_plugin/kodion/items/search_history_item.py
@@ -21,7 +21,7 @@ def __init__(self, context, query, image=None, fanart=None, location=False):
if location:
params['location'] = location
- DirectoryItem.__init__(self, query, context.create_uri([constants.paths.SEARCH, 'query'], params=params), image=image)
+ super(SearchHistoryItem, self).__init__(query, context.create_uri([constants.paths.SEARCH, 'query'], params=params), image=image)
if fanart:
self.set_fanart(fanart)
else:
diff --git a/resources/lib/youtube_plugin/kodion/items/search_item.py b/resources/lib/youtube_plugin/kodion/items/search_item.py
index f04e3ed6e..51e292dd2 100644
--- a/resources/lib/youtube_plugin/kodion/items/search_item.py
+++ b/resources/lib/youtube_plugin/kodion/items/search_item.py
@@ -23,7 +23,7 @@ def __init__(self, context, alt_name=None, image=None, fanart=None, location=Fal
params = {'location': location} if location else {}
- DirectoryItem.__init__(self, name, context.create_uri([constants.paths.SEARCH, 'list'], params=params), image=image)
+ super(SearchItem, self).__init__(name, context.create_uri([constants.paths.SEARCH, 'list'], params=params), image=image)
if fanart:
self.set_fanart(fanart)
else:
diff --git a/resources/lib/youtube_plugin/kodion/items/uri_item.py b/resources/lib/youtube_plugin/kodion/items/uri_item.py
index 9ba74cc9b..a3a80b702 100644
--- a/resources/lib/youtube_plugin/kodion/items/uri_item.py
+++ b/resources/lib/youtube_plugin/kodion/items/uri_item.py
@@ -13,4 +13,4 @@
class UriItem(BaseItem):
def __init__(self, uri):
- BaseItem.__init__(self, name='', uri=uri)
+ super(UriItem, self).__init__(name='', uri=uri)
diff --git a/resources/lib/youtube_plugin/kodion/items/video_item.py b/resources/lib/youtube_plugin/kodion/items/video_item.py
index 35a819be9..595bf90f6 100644
--- a/resources/lib/youtube_plugin/kodion/items/video_item.py
+++ b/resources/lib/youtube_plugin/kodion/items/video_item.py
@@ -20,7 +20,7 @@
class VideoItem(BaseItem):
def __init__(self, name, uri, image='', fanart=''):
- BaseItem.__init__(self, name, uri, image, fanart)
+ super(VideoItem, self).__init__(name, uri, image, fanart)
self._genre = None
self._aired = None
self._aired_utc = None
diff --git a/resources/lib/youtube_plugin/kodion/items/watch_later_item.py b/resources/lib/youtube_plugin/kodion/items/watch_later_item.py
index b896fdecb..0a816c277 100644
--- a/resources/lib/youtube_plugin/kodion/items/watch_later_item.py
+++ b/resources/lib/youtube_plugin/kodion/items/watch_later_item.py
@@ -21,7 +21,7 @@ def __init__(self, context, alt_name=None, image=None, fanart=None):
if image is None:
image = context.create_resource_path('media/watch_later.png')
- DirectoryItem.__init__(self, name, context.create_uri([constants.paths.WATCH_LATER, 'list']), image=image)
+ super(WatchLaterItem, self).__init__(name, context.create_uri([constants.paths.WATCH_LATER, 'list']), image=image)
if fanart:
self.set_fanart(fanart)
else:
diff --git a/resources/lib/youtube_plugin/kodion/json_store/api_keys.py b/resources/lib/youtube_plugin/kodion/json_store/api_keys.py
index 0faf0aa37..e5a4b8533 100644
--- a/resources/lib/youtube_plugin/kodion/json_store/api_keys.py
+++ b/resources/lib/youtube_plugin/kodion/json_store/api_keys.py
@@ -12,7 +12,7 @@
class APIKeyStore(JSONStore):
def __init__(self):
- JSONStore.__init__(self, 'api_keys.json')
+ super(APIKeyStore, self).__init__('api_keys.json')
def set_defaults(self):
data = self.get_data()
diff --git a/resources/lib/youtube_plugin/kodion/json_store/login_tokens.py b/resources/lib/youtube_plugin/kodion/json_store/login_tokens.py
index 58d586f9c..3f691e1ca 100644
--- a/resources/lib/youtube_plugin/kodion/json_store/login_tokens.py
+++ b/resources/lib/youtube_plugin/kodion/json_store/login_tokens.py
@@ -14,7 +14,7 @@
# noinspection PyTypeChecker
class LoginTokenStore(JSONStore):
def __init__(self):
- JSONStore.__init__(self, 'access_manager.json')
+ super(LoginTokenStore, self).__init__('access_manager.json')
def set_defaults(self):
data = self.get_data()
diff --git a/resources/lib/youtube_plugin/kodion/utils/data_cache.py b/resources/lib/youtube_plugin/kodion/utils/data_cache.py
index abee69e65..2e6fd89ec 100644
--- a/resources/lib/youtube_plugin/kodion/utils/data_cache.py
+++ b/resources/lib/youtube_plugin/kodion/utils/data_cache.py
@@ -27,7 +27,7 @@ class DataCache(Storage):
def __init__(self, filename, max_file_size_mb=5):
max_file_size_kb = max_file_size_mb * 1024
- Storage.__init__(self, filename, max_file_size_kb=max_file_size_kb)
+ super(DataCache, self).__init__(filename, max_file_size_kb=max_file_size_kb)
def is_empty(self):
return self._is_empty()
diff --git a/resources/lib/youtube_plugin/kodion/utils/favorite_list.py b/resources/lib/youtube_plugin/kodion/utils/favorite_list.py
index c75c2755c..a4031b918 100644
--- a/resources/lib/youtube_plugin/kodion/utils/favorite_list.py
+++ b/resources/lib/youtube_plugin/kodion/utils/favorite_list.py
@@ -14,7 +14,7 @@
class FavoriteList(Storage):
def __init__(self, filename):
- Storage.__init__(self, filename)
+ super(FavoriteList, self).__init__(filename)
def clear(self):
self._clear()
diff --git a/resources/lib/youtube_plugin/kodion/utils/function_cache.py b/resources/lib/youtube_plugin/kodion/utils/function_cache.py
index cdd4ed4e3..7218a8ba8 100644
--- a/resources/lib/youtube_plugin/kodion/utils/function_cache.py
+++ b/resources/lib/youtube_plugin/kodion/utils/function_cache.py
@@ -23,7 +23,7 @@ class FunctionCache(Storage):
def __init__(self, filename, max_file_size_mb=5):
max_file_size_kb = max_file_size_mb * 1024
- Storage.__init__(self, filename, max_file_size_kb=max_file_size_kb)
+ super(FunctionCache, self).__init__(filename, max_file_size_kb=max_file_size_kb)
self._enabled = True
diff --git a/resources/lib/youtube_plugin/kodion/utils/http_server.py b/resources/lib/youtube_plugin/kodion/utils/http_server.py
index 909ab0034..076255718 100644
--- a/resources/lib/youtube_plugin/kodion/utils/http_server.py
+++ b/resources/lib/youtube_plugin/kodion/utils/http_server.py
@@ -42,7 +42,7 @@ def __init__(self, request, client_address, server):
self.base_path = xbmc.translatePath('special://temp/%s' % self.addon_id).decode('utf-8')
except AttributeError:
self.base_path = xbmc.translatePath('special://temp/%s' % self.addon_id)
- BaseHTTPServer.BaseHTTPRequestHandler.__init__(self, request, client_address, server)
+ super(YouTubeRequestHandler, self).__init__(request, client_address, server)
def connection_allowed(self):
client_ip = self.client_address[0]
diff --git a/resources/lib/youtube_plugin/kodion/utils/playback_history.py b/resources/lib/youtube_plugin/kodion/utils/playback_history.py
index 723f3b160..db54100bf 100644
--- a/resources/lib/youtube_plugin/kodion/utils/playback_history.py
+++ b/resources/lib/youtube_plugin/kodion/utils/playback_history.py
@@ -16,7 +16,7 @@
class PlaybackHistory(Storage):
def __init__(self, filename):
- Storage.__init__(self, filename)
+ super(PlaybackHistory, self).__init__(filename)
def is_empty(self):
return self._is_empty()
diff --git a/resources/lib/youtube_plugin/kodion/utils/search_history.py b/resources/lib/youtube_plugin/kodion/utils/search_history.py
index 726fcc6a9..f94d7bfb0 100644
--- a/resources/lib/youtube_plugin/kodion/utils/search_history.py
+++ b/resources/lib/youtube_plugin/kodion/utils/search_history.py
@@ -16,7 +16,7 @@
class SearchHistory(Storage):
def __init__(self, filename, max_items=10):
- Storage.__init__(self, filename, max_item_count=max_items)
+ super(SearchHistory, self).__init__(filename, max_item_count=max_items)
def is_empty(self):
return self._is_empty()
diff --git a/resources/lib/youtube_plugin/kodion/utils/watch_later_list.py b/resources/lib/youtube_plugin/kodion/utils/watch_later_list.py
index 38fc222cb..04e21cada 100644
--- a/resources/lib/youtube_plugin/kodion/utils/watch_later_list.py
+++ b/resources/lib/youtube_plugin/kodion/utils/watch_later_list.py
@@ -16,7 +16,7 @@
class WatchLaterList(Storage):
def __init__(self, filename):
- Storage.__init__(self, filename)
+ super(WatchLaterList, self).__init__(filename)
def clear(self):
self._clear()
diff --git a/resources/lib/youtube_plugin/youtube/helper/url_resolver.py b/resources/lib/youtube_plugin/youtube/helper/url_resolver.py
index 1b4ea4ff0..fe6750c34 100644
--- a/resources/lib/youtube_plugin/youtube/helper/url_resolver.py
+++ b/resources/lib/youtube_plugin/youtube/helper/url_resolver.py
@@ -35,7 +35,7 @@ class YouTubeResolver(AbstractResolver):
RE_USER_NAME = re.compile(r'http(s)?://(www.)?youtube.com/(?P[a-zA-Z0-9]+)$')
def __init__(self):
- AbstractResolver.__init__(self)
+ super(YouTubeResolver, self).__init__()
def supports_url(self, url, url_components):
if url_components.hostname == 'www.youtube.com' or url_components.hostname == 'youtube.com':
@@ -91,7 +91,7 @@ def _load_page(_url):
class CommonResolver(AbstractResolver, list):
def __init__(self):
- AbstractResolver.__init__(self)
+ super(CommonResolver, self).__init__()
def supports_url(self, url, url_components):
return True
diff --git a/resources/lib/youtube_plugin/youtube/provider.py b/resources/lib/youtube_plugin/youtube/provider.py
index 6af39ead8..d2a4c7a85 100644
--- a/resources/lib/youtube_plugin/youtube/provider.py
+++ b/resources/lib/youtube_plugin/youtube/provider.py
@@ -203,7 +203,7 @@ class Provider(kodion.AbstractProvider):
}
def __init__(self):
- kodion.AbstractProvider.__init__(self)
+ super(Provider, self).__init__()
self._resource_manager = None
self._client = None
From f131501b8e71baf5bd8cbbba0fb29962733f9d75 Mon Sep 17 00:00:00 2001
From: MoojMidge <56883549+MoojMidge@users.noreply.github.com>
Date: Wed, 1 Nov 2023 21:40:12 +1100
Subject: [PATCH 023/141] Locator as sub-class of BaseRequestsClass
---
resources/lib/youtube_plugin/kodion/utils/ip_api.py | 8 +++++---
1 file changed, 5 insertions(+), 3 deletions(-)
diff --git a/resources/lib/youtube_plugin/kodion/utils/ip_api.py b/resources/lib/youtube_plugin/kodion/utils/ip_api.py
index f6873e253..6f5761d77 100644
--- a/resources/lib/youtube_plugin/kodion/utils/ip_api.py
+++ b/resources/lib/youtube_plugin/kodion/utils/ip_api.py
@@ -7,22 +7,24 @@
See LICENSES/GPL-2.0-only for more information.
"""
-import requests
+from .requests import BaseRequestsClass
-class Locator(object):
+class Locator(BaseRequestsClass):
def __init__(self, context):
self._base_url = 'http://ip-api.com'
self._response = {}
self._context = context
+ super(Locator, self).__init__(context=context)
+
def response(self):
return self._response
def locate_requester(self):
request_url = '/'.join([self._base_url, 'json'])
- response = requests.get(request_url)
+ response = self.request(request_url)
self._response = response.json()
def success(self):
From 978312d69c6fcfa2632e939e2dd87ee85fac42d2 Mon Sep 17 00:00:00 2001
From: MoojMidge <56883549+MoojMidge@users.noreply.github.com>
Date: Thu, 2 Nov 2023 15:30:44 +1100
Subject: [PATCH 024/141] Re-org to prevent issues with circular imports
- Move http_server, ip_api, and requests to kodion.network module from
kodion.utils
- Split kodion.impl to
- kodion.context
- kodion.player
- kodion.plugin
- kodion.settings
- kodion.ui
- Remove context parameter from BaseRequestsClass, requests now imports
Settings directly and can be imported by other modules in kodion
- Update BaseRequestsClass.request to use same params as parent class
---
resources/lib/youtube_authentication.py | 2 +-
.../lib/youtube_plugin/kodion/__init__.py | 2 +-
.../youtube_plugin/kodion/context/__init__.py | 13 ++
.../{impl => context}/abstract_context.py | 0
.../{impl => context}/xbmc/xbmc_context.py | 9 +-
.../youtube_plugin/kodion/impl/__init__.py | 17 ---
.../youtube_plugin/kodion/network/__init__.py | 21 +++
.../kodion/{utils => network}/http_server.py | 61 ++++----
.../kodion/{utils => network}/ip_api.py | 16 +--
.../kodion/{utils => network}/requests.py | 57 +++++---
.../youtube_plugin/kodion/player/__init__.py | 14 ++
.../{impl => player}/abstract_player.py | 0
.../{impl => player}/abstract_playlist.py | 0
.../{impl => player}/xbmc/xbmc_player.py | 0
.../{impl => player}/xbmc/xbmc_playlist.py | 2 +-
.../kodion/{impl/xbmc => plugin}/__init__.py | 8 +-
.../abstract_provider_runner.py | 2 +-
.../{impl => plugin}/xbmc/xbmc_runner.py | 5 +-
resources/lib/youtube_plugin/kodion/runner.py | 6 +-
.../lib/youtube_plugin/kodion/service.py | 5 +-
.../kodion/settings/__init__.py | 13 ++
.../{impl => settings}/abstract_settings.py | 0
.../xbmc/xbmc_plugin_settings.py | 0
.../lib/youtube_plugin/kodion/ui/__init__.py | 13 ++
.../{impl => ui}/abstract_context_ui.py | 0
.../{impl => ui}/abstract_progress_dialog.py | 0
.../kodion/{impl => ui}/xbmc/info_labels.py | 0
.../{impl => ui}/xbmc/xbmc_context_ui.py | 2 +-
.../kodion/{impl => ui}/xbmc/xbmc_items.py | 0
.../{impl => ui}/xbmc/xbmc_progress_dialog.py | 0
.../xbmc/xbmc_progress_dialog_bg.py | 0
.../youtube_plugin/kodion/utils/__init__.py | 30 +++-
.../youtube_plugin/kodion/utils/monitor.py | 3 +-
.../youtube/client/login_client.py | 25 ++--
.../youtube/client/request_client.py | 8 +-
.../lib/youtube_plugin/youtube/helper/tv.py | 11 +-
.../youtube_plugin/youtube/helper/utils.py | 14 +-
.../youtube/helper/video_info.py | 5 +-
.../youtube_plugin/youtube/helper/yt_play.py | 2 +-
.../youtube/helper/yt_setup_wizard.py | 7 +-
.../lib/youtube_plugin/youtube/provider.py | 130 +++++++++---------
resources/lib/youtube_registration.py | 2 +-
resources/lib/youtube_requests.py | 2 +-
resources/lib/youtube_resolver.py | 2 +-
44 files changed, 301 insertions(+), 208 deletions(-)
create mode 100644 resources/lib/youtube_plugin/kodion/context/__init__.py
rename resources/lib/youtube_plugin/kodion/{impl => context}/abstract_context.py (100%)
rename resources/lib/youtube_plugin/kodion/{impl => context}/xbmc/xbmc_context.py (98%)
delete mode 100644 resources/lib/youtube_plugin/kodion/impl/__init__.py
create mode 100644 resources/lib/youtube_plugin/kodion/network/__init__.py
rename resources/lib/youtube_plugin/kodion/{utils => network}/http_server.py (90%)
rename resources/lib/youtube_plugin/kodion/{utils => network}/ip_api.py (68%)
rename resources/lib/youtube_plugin/kodion/{utils => network}/requests.py (64%)
create mode 100644 resources/lib/youtube_plugin/kodion/player/__init__.py
rename resources/lib/youtube_plugin/kodion/{impl => player}/abstract_player.py (100%)
rename resources/lib/youtube_plugin/kodion/{impl => player}/abstract_playlist.py (100%)
rename resources/lib/youtube_plugin/kodion/{impl => player}/xbmc/xbmc_player.py (100%)
rename resources/lib/youtube_plugin/kodion/{impl => player}/xbmc/xbmc_playlist.py (98%)
rename resources/lib/youtube_plugin/kodion/{impl/xbmc => plugin}/__init__.py (50%)
rename resources/lib/youtube_plugin/kodion/{impl => plugin}/abstract_provider_runner.py (89%)
rename resources/lib/youtube_plugin/kodion/{impl => plugin}/xbmc/xbmc_runner.py (98%)
create mode 100644 resources/lib/youtube_plugin/kodion/settings/__init__.py
rename resources/lib/youtube_plugin/kodion/{impl => settings}/abstract_settings.py (100%)
rename resources/lib/youtube_plugin/kodion/{impl => settings}/xbmc/xbmc_plugin_settings.py (100%)
create mode 100644 resources/lib/youtube_plugin/kodion/ui/__init__.py
rename resources/lib/youtube_plugin/kodion/{impl => ui}/abstract_context_ui.py (100%)
rename resources/lib/youtube_plugin/kodion/{impl => ui}/abstract_progress_dialog.py (100%)
rename resources/lib/youtube_plugin/kodion/{impl => ui}/xbmc/info_labels.py (100%)
rename resources/lib/youtube_plugin/kodion/{impl => ui}/xbmc/xbmc_context_ui.py (100%)
rename resources/lib/youtube_plugin/kodion/{impl => ui}/xbmc/xbmc_items.py (100%)
rename resources/lib/youtube_plugin/kodion/{impl => ui}/xbmc/xbmc_progress_dialog.py (100%)
rename resources/lib/youtube_plugin/kodion/{impl => ui}/xbmc/xbmc_progress_dialog_bg.py (100%)
diff --git a/resources/lib/youtube_authentication.py b/resources/lib/youtube_authentication.py
index e3f373f35..08abf56c9 100644
--- a/resources/lib/youtube_authentication.py
+++ b/resources/lib/youtube_authentication.py
@@ -8,7 +8,7 @@
"""
from youtube_plugin.youtube.provider import Provider
-from youtube_plugin.kodion.impl import Context
+from youtube_plugin.kodion.context import Context
from youtube_plugin.youtube.helper import yt_login
# noinspection PyUnresolvedReferences
diff --git a/resources/lib/youtube_plugin/kodion/__init__.py b/resources/lib/youtube_plugin/kodion/__init__.py
index be1e324a8..869364a9b 100644
--- a/resources/lib/youtube_plugin/kodion/__init__.py
+++ b/resources/lib/youtube_plugin/kodion/__init__.py
@@ -18,7 +18,7 @@
from .abstract_provider import AbstractProvider
# import specialized implementation into the kodion namespace
-from .impl import Context
+from .context import Context
from . import logger
diff --git a/resources/lib/youtube_plugin/kodion/context/__init__.py b/resources/lib/youtube_plugin/kodion/context/__init__.py
new file mode 100644
index 000000000..9b2b1d889
--- /dev/null
+++ b/resources/lib/youtube_plugin/kodion/context/__init__.py
@@ -0,0 +1,13 @@
+# -*- coding: utf-8 -*-
+"""
+
+ Copyright (C) 2023-present plugin.video.youtube
+
+ SPDX-License-Identifier: GPL-2.0-only
+ See LICENSES/GPL-2.0-only for more information.
+"""
+
+from .xbmc.xbmc_context import XbmcContext as Context
+
+
+__all__ = ('Context', )
\ No newline at end of file
diff --git a/resources/lib/youtube_plugin/kodion/impl/abstract_context.py b/resources/lib/youtube_plugin/kodion/context/abstract_context.py
similarity index 100%
rename from resources/lib/youtube_plugin/kodion/impl/abstract_context.py
rename to resources/lib/youtube_plugin/kodion/context/abstract_context.py
diff --git a/resources/lib/youtube_plugin/kodion/impl/xbmc/xbmc_context.py b/resources/lib/youtube_plugin/kodion/context/xbmc/xbmc_context.py
similarity index 98%
rename from resources/lib/youtube_plugin/kodion/impl/xbmc/xbmc_context.py
rename to resources/lib/youtube_plugin/kodion/context/xbmc/xbmc_context.py
index 3844277b4..7f77c785a 100644
--- a/resources/lib/youtube_plugin/kodion/impl/xbmc/xbmc_context.py
+++ b/resources/lib/youtube_plugin/kodion/context/xbmc/xbmc_context.py
@@ -21,12 +21,13 @@
import xbmcvfs
from ..abstract_context import AbstractContext
-from .xbmc_plugin_settings import XbmcPluginSettings
-from .xbmc_context_ui import XbmcContextUI
-from .xbmc_playlist import XbmcPlaylist
-from .xbmc_player import XbmcPlayer
+from ...player.xbmc.xbmc_playlist import XbmcPlaylist
+from ...player.xbmc.xbmc_player import XbmcPlayer
+from ...settings.xbmc.xbmc_plugin_settings import XbmcPluginSettings
+from ...ui.xbmc.xbmc_context_ui import XbmcContextUI
from ... import utils
+
try:
xbmc.translatePath = xbmcvfs.translatePath
except AttributeError:
diff --git a/resources/lib/youtube_plugin/kodion/impl/__init__.py b/resources/lib/youtube_plugin/kodion/impl/__init__.py
deleted file mode 100644
index ddb3e0b93..000000000
--- a/resources/lib/youtube_plugin/kodion/impl/__init__.py
+++ /dev/null
@@ -1,17 +0,0 @@
-# -*- coding: utf-8 -*-
-"""
-
- Copyright (C) 2014-2016 bromix (plugin.video.youtube)
- Copyright (C) 2016-2018 plugin.video.youtube
-
- SPDX-License-Identifier: GPL-2.0-only
- See LICENSES/GPL-2.0-only for more information.
-"""
-
-from .xbmc.xbmc_plugin_settings import XbmcPluginSettings as Settings
-from .xbmc.xbmc_context import XbmcContext as Context
-from .xbmc.xbmc_context_ui import XbmcContextUI as ContextUI
-from .xbmc.xbmc_runner import XbmcRunner as Runner
-
-
-__all__ = ['Settings', 'Context', 'ContextUI', 'Runner']
diff --git a/resources/lib/youtube_plugin/kodion/network/__init__.py b/resources/lib/youtube_plugin/kodion/network/__init__.py
new file mode 100644
index 000000000..8e1c3dccf
--- /dev/null
+++ b/resources/lib/youtube_plugin/kodion/network/__init__.py
@@ -0,0 +1,21 @@
+# -*- coding: utf-8 -*-
+"""
+
+ Copyright (C) 2023-present plugin.video.youtube
+
+ SPDX-License-Identifier: GPL-2.0-only
+ See LICENSES/GPL-2.0-only for more information.
+"""
+
+from .http_server import get_client_ip_address, get_http_server, is_httpd_live
+from .ip_api import Locator
+from .requests import BaseRequestsClass
+
+
+__all__ = (
+ 'get_client_ip_address',
+ 'get_http_server',
+ 'is_httpd_live',
+ 'BaseRequestsClass',
+ 'Locator',
+)
diff --git a/resources/lib/youtube_plugin/kodion/utils/http_server.py b/resources/lib/youtube_plugin/kodion/network/http_server.py
similarity index 90%
rename from resources/lib/youtube_plugin/kodion/utils/http_server.py
rename to resources/lib/youtube_plugin/kodion/network/http_server.py
index 076255718..591810d84 100644
--- a/resources/lib/youtube_plugin/kodion/utils/http_server.py
+++ b/resources/lib/youtube_plugin/kodion/network/http_server.py
@@ -17,32 +17,36 @@
from urllib.parse import urlparse
import xbmc
-import xbmcaddon
import xbmcgui
import xbmcvfs
+from xbmcaddon import Addon
+
+from ..logger import log_debug
+from ..settings import Settings
-from .. import logger
try:
xbmc.translatePath = xbmcvfs.translatePath
except AttributeError:
pass
-class YouTubeRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
+_addon_id = 'plugin.video.youtube'
+_settings = Settings(Addon(id=_addon_id))
+
+class YouTubeProxyRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
def __init__(self, request, client_address, server):
- self.addon_id = 'plugin.video.youtube'
- addon = xbmcaddon.Addon(self.addon_id)
+ addon = Addon(_addon_id)
whitelist_ips = addon.getSetting('kodion.http.ip.whitelist')
whitelist_ips = ''.join(whitelist_ips.split())
self.whitelist_ips = whitelist_ips.split(',')
self.local_ranges = ('10.', '172.16.', '192.168.', '127.0.0.1', 'localhost', '::1')
self.chunk_size = 1024 * 64
try:
- self.base_path = xbmc.translatePath('special://temp/%s' % self.addon_id).decode('utf-8')
+ self.base_path = xbmc.translatePath('special://temp/%s' % _addon_id).decode('utf-8')
except AttributeError:
- self.base_path = xbmc.translatePath('special://temp/%s' % self.addon_id)
- super(YouTubeRequestHandler, self).__init__(request, client_address, server)
+ self.base_path = xbmc.translatePath('special://temp/%s' % _addon_id)
+ super(YouTubeProxyRequestHandler, self).__init__(request, client_address, server)
def connection_allowed(self):
client_ip = self.client_address[0]
@@ -54,14 +58,14 @@ def connection_allowed(self):
log_lines.append('Whitelisted: |%s|' % str(conn_allowed))
if not conn_allowed:
- logger.log_debug('HTTPServer: Connection from |%s| not allowed' % client_ip)
+ log_debug('HTTPServer: Connection from |%s| not allowed' % client_ip)
elif self.path != '/ping':
- logger.log_debug(' '.join(log_lines))
+ log_debug(' '.join(log_lines))
return conn_allowed
# noinspection PyPep8Naming
def do_GET(self):
- addon = xbmcaddon.Addon('plugin.video.youtube')
+ addon = Addon('plugin.video.youtube')
mpd_proxy_enabled = addon.getSetting('kodion.mpd.videos') == 'true' and addon.getSetting('kodion.video.quality.isa') == 'true'
api_config_enabled = addon.getSetting('youtube.api.config.page') == 'true'
@@ -77,14 +81,14 @@ def do_GET(self):
self.wfile.write(client_json.encode('utf-8'))
if stripped_path != '/ping':
- logger.log_debug('HTTPServer: GET Request uri path |{proxy_path}|'.format(proxy_path=self.path))
+ log_debug('HTTPServer: GET Request uri path |{proxy_path}|'.format(proxy_path=self.path))
if not self.connection_allowed():
self.send_error(403)
elif mpd_proxy_enabled and self.path.endswith('.mpd'):
file_path = os.path.join(self.base_path, self.path.strip('/').strip('\\'))
file_chunk = True
- logger.log_debug('HTTPServer: Request file path |{file_path}|'.format(file_path=file_path.encode('utf-8')))
+ log_debug('HTTPServer: Request file path |{file_path}|'.format(file_path=file_path.encode('utf-8')))
try:
with open(file_path, 'rb') as f:
self.send_response(200)
@@ -108,7 +112,7 @@ def do_GET(self):
for chunk in self.get_chunks(html):
self.wfile.write(chunk)
elif api_config_enabled and stripped_path.startswith('/api_submit'):
- addon = xbmcaddon.Addon('plugin.video.youtube')
+ addon = Addon('plugin.video.youtube')
i18n = addon.getLocalizedString
xbmc.executebuiltin('Dialog.Close(addonsettings,true)')
old_api_key = addon.getSetting('youtube.api.key')
@@ -160,12 +164,12 @@ def do_GET(self):
# noinspection PyPep8Naming
def do_HEAD(self):
- logger.log_debug('HTTPServer: HEAD Request uri path |{proxy_path}|'.format(proxy_path=self.path))
+ log_debug('HTTPServer: HEAD Request uri path |{proxy_path}|'.format(proxy_path=self.path))
if not self.connection_allowed():
self.send_error(403)
else:
- addon = xbmcaddon.Addon('plugin.video.youtube')
+ addon = Addon('plugin.video.youtube')
mpd_proxy_enabled = addon.getSetting('kodion.mpd.videos') == 'true' and addon.getSetting('kodion.video.quality.isa') == 'true'
if mpd_proxy_enabled and self.path.endswith('.mpd'):
file_path = os.path.join(self.base_path, self.path.strip('/').strip('\\'))
@@ -182,7 +186,7 @@ def do_HEAD(self):
# noinspection PyPep8Naming
def do_POST(self):
- logger.log_debug('HTTPServer: Request uri path |{proxy_path}|'.format(proxy_path=self.path))
+ log_debug('HTTPServer: Request uri path |{proxy_path}|'.format(proxy_path=self.path))
if not self.connection_allowed():
self.send_error(403)
@@ -220,7 +224,7 @@ def do_POST(self):
match = re.search(r'^Authorized-Format-Types:\s*(?P.+?)\r*$', response_header, re.MULTILINE)
if match:
authorized_types = match.group('authorized_types').split(',')
- logger.log_debug('HTTPServer: Found authorized formats |{authorized_fmts}|'.format(authorized_fmts=authorized_types))
+ log_debug('HTTPServer: Found authorized formats |{authorized_fmts}|'.format(authorized_fmts=authorized_types))
fmt_to_px = {'SD': (1280 * 528) - 1, 'HD720': 1280 * 720, 'HD': 7680 * 4320}
if 'HD' in authorized_types:
@@ -259,7 +263,7 @@ def get_chunks(self, data):
@staticmethod
def api_config_page():
- addon = xbmcaddon.Addon('plugin.video.youtube')
+ addon = Addon('plugin.video.youtube')
i18n = addon.getLocalizedString
api_key = addon.getSetting('youtube.api.key')
api_id = addon.getSetting('youtube.api.id')
@@ -273,7 +277,7 @@ def api_config_page():
@staticmethod
def api_submit_page(updated_keys, enabled, footer):
- addon = xbmcaddon.Addon('plugin.video.youtube')
+ addon = Addon('plugin.video.youtube')
i18n = addon.getLocalizedString
html = Pages().api_submit.get('html')
css = Pages().api_submit.get('css')
@@ -454,16 +458,15 @@ class Pages(object):
def get_http_server(address=None, port=None):
- addon_id = 'plugin.video.youtube'
- addon = xbmcaddon.Addon(addon_id)
+ addon = Addon(_addon_id)
address = address if address else addon.getSetting('kodion.http.listen')
address = address if re.match(r'^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$', address) else '0.0.0.0'
port = int(port) if port else 50152
try:
- server = BaseHTTPServer.HTTPServer((address, port), YouTubeRequestHandler)
+ server = BaseHTTPServer.HTTPServer((address, port), YouTubeProxyRequestHandler)
return server
except socket.error as e:
- logger.log_debug('HTTPServer: Failed to start |{address}:{port}| |{response}|'.format(address=address, port=port, response=str(e)))
+ log_debug('HTTPServer: Failed to start |{address}:{port}| |{response}|'.format(address=address, port=port, response=str(e)))
xbmcgui.Dialog().notification(addon.getAddonInfo('name'), str(e),
addon.getAddonInfo('icon'),
5000, False)
@@ -471,8 +474,7 @@ def get_http_server(address=None, port=None):
def is_httpd_live(address=None, port=None):
- addon_id = 'plugin.video.youtube'
- addon = xbmcaddon.Addon(addon_id)
+ addon = Addon(_addon_id)
address = address if address else addon.getSetting('kodion.http.listen')
address = '127.0.0.1' if address == '0.0.0.0' else address
address = address if re.match(r'^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$', address) else '127.0.0.1'
@@ -482,16 +484,15 @@ def is_httpd_live(address=None, port=None):
response = requests.get(url)
result = response.status_code == 204
if not result:
- logger.log_debug('HTTPServer: Ping |{address}:{port}| |{response}|'.format(address=address, port=port, response=response.status_code))
+ log_debug('HTTPServer: Ping |{address}:{port}| |{response}|'.format(address=address, port=port, response=response.status_code))
return result
except:
- logger.log_debug('HTTPServer: Ping |{address}:{port}| |{response}|'.format(address=address, port=port, response='failed'))
+ log_debug('HTTPServer: Ping |{address}:{port}| |{response}|'.format(address=address, port=port, response='failed'))
return False
def get_client_ip_address(address=None, port=None):
- addon_id = 'plugin.video.youtube'
- addon = xbmcaddon.Addon(addon_id)
+ addon = Addon(_addon_id)
address = address if address else addon.getSetting('kodion.http.listen')
address = '127.0.0.1' if address == '0.0.0.0' else address
address = address if re.match(r'^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$', address) else '127.0.0.1'
diff --git a/resources/lib/youtube_plugin/kodion/utils/ip_api.py b/resources/lib/youtube_plugin/kodion/network/ip_api.py
similarity index 68%
rename from resources/lib/youtube_plugin/kodion/utils/ip_api.py
rename to resources/lib/youtube_plugin/kodion/network/ip_api.py
index 6f5761d77..79892c6fe 100644
--- a/resources/lib/youtube_plugin/kodion/utils/ip_api.py
+++ b/resources/lib/youtube_plugin/kodion/network/ip_api.py
@@ -8,16 +8,16 @@
"""
from .requests import BaseRequestsClass
+from .. import logger
class Locator(BaseRequestsClass):
- def __init__(self, context):
+ def __init__(self):
self._base_url = 'http://ip-api.com'
self._response = {}
- self._context = context
- super(Locator, self).__init__(context=context)
+ super(Locator, self).__init__()
def response(self):
return self._response
@@ -30,9 +30,9 @@ def locate_requester(self):
def success(self):
successful = self.response().get('status', 'fail') == 'success'
if successful:
- self._context.log_debug('Location request was successful')
+ logger.log_debug('Location request was successful')
else:
- self._context.log_error(self.response().get('message', 'Location request failed with no error message'))
+ logger.log_error(self.response().get('message', 'Location request failed with no error message'))
return successful
def coordinates(self):
@@ -42,7 +42,7 @@ def coordinates(self):
lat = self._response.get('lat')
lon = self._response.get('lon')
if lat is None or lon is None:
- self._context.log_error('No coordinates returned')
+ logger.log_error('No coordinates returned')
return None
- self._context.log_debug('Coordinates found')
- return lat, lon
+ logger.log_debug('Coordinates found')
+ return {'lat': lat, 'lon': lon}
diff --git a/resources/lib/youtube_plugin/kodion/utils/requests.py b/resources/lib/youtube_plugin/kodion/network/requests.py
similarity index 64%
rename from resources/lib/youtube_plugin/kodion/utils/requests.py
rename to resources/lib/youtube_plugin/kodion/network/requests.py
index 64594907e..ad83e1f44 100644
--- a/resources/lib/youtube_plugin/kodion/utils/requests.py
+++ b/resources/lib/youtube_plugin/kodion/network/requests.py
@@ -15,6 +15,14 @@
from requests.adapters import HTTPAdapter, Retry
from requests.exceptions import RequestException
+from ..logger import log_error
+from ..settings import Settings
+
+from xbmcaddon import Addon
+
+
+_settings = Settings(Addon(id='plugin.video.youtube'))
+
class BaseRequestsClass(object):
http_adapter = HTTPAdapter(
@@ -28,10 +36,9 @@ class BaseRequestsClass(object):
)
)
- def __init__(self, context, exc_type=RequestException):
- self._context = context
- self._verify = self._context.get_settings().verify_ssl()
- self._timeout = self._context.get_settings().get_timeout()
+ def __init__(self, exc_type=RequestException):
+ self._verify = _settings.verify_ssl()
+ self._timeout = _settings.get_timeout()
self._default_exc = exc_type
self._session = Session()
@@ -46,20 +53,38 @@ def __exit__(self, exc_type, exc_value, traceback):
self._session.close()
def request(self, url, method='GET',
- cookies=None, data=None, headers=None, json=None, params=None,
+ params=None, data=None, headers=None, cookies=None, files=None,
+ auth=None, timeout=None, allow_redirects=None, proxies=None,
+ hooks=None, stream=None, verify=None, cert=None, json=None,
+ # Custom event hook implementation
+ # See _login_json_hook and _login_error_hook in login_client.py
+ # for example usage
response_hook=None, error_hook=None,
error_title=None, error_info=None, raise_exc=False, **_):
+ if timeout is None:
+ timeout = self._timeout
+ if verify is None:
+ verify = self._verify
+ if allow_redirects is None:
+ allow_redirects = True
+
response = None
try:
response = self._session.request(method, url,
- verify=self._verify,
- allow_redirects=True,
- timeout=self._timeout,
- cookies=cookies,
+ params=params,
data=data,
headers=headers,
- json=json,
- params=params)
+ cookies=cookies,
+ files=files,
+ auth=auth,
+ timeout=timeout,
+ allow_redirects=allow_redirects,
+ proxies=proxies,
+ hooks=hooks,
+ stream=stream,
+ verify=verify,
+ cert=cert,
+ json=json,)
if response_hook:
response = response_hook(response)
else:
@@ -106,15 +131,15 @@ def request(self, url, method='GET',
)
)
- self._context.log_error('\n'.join([part for part in [
+ log_error('\n'.join([part for part in [
error_title, error_info, response_text, stack_trace, exc_tb
] if part]))
if raise_exc:
if isinstance(raise_exc, BaseException):
- raise raise_exc from exc
- if not callable(raise_exc):
- raise self._default_exc(error_title) from exc
- raise raise_exc(error_title) from exc
+ raise raise_exc(exc)
+ if callable(raise_exc):
+ raise raise_exc(error_title)(exc)
+ raise self._default_exc(error_title)(exc)
return response
diff --git a/resources/lib/youtube_plugin/kodion/player/__init__.py b/resources/lib/youtube_plugin/kodion/player/__init__.py
new file mode 100644
index 000000000..705b491c8
--- /dev/null
+++ b/resources/lib/youtube_plugin/kodion/player/__init__.py
@@ -0,0 +1,14 @@
+# -*- coding: utf-8 -*-
+"""
+
+ Copyright (C) 2023-present plugin.video.youtube
+
+ SPDX-License-Identifier: GPL-2.0-only
+ See LICENSES/GPL-2.0-only for more information.
+"""
+
+from .xbmc.xbmc_player import XbmcPlayer as Player
+from .xbmc.xbmc_playlist import XbmcPlaylist as Playlist
+
+
+__all__ = ('Player', 'Playlist', )
\ No newline at end of file
diff --git a/resources/lib/youtube_plugin/kodion/impl/abstract_player.py b/resources/lib/youtube_plugin/kodion/player/abstract_player.py
similarity index 100%
rename from resources/lib/youtube_plugin/kodion/impl/abstract_player.py
rename to resources/lib/youtube_plugin/kodion/player/abstract_player.py
diff --git a/resources/lib/youtube_plugin/kodion/impl/abstract_playlist.py b/resources/lib/youtube_plugin/kodion/player/abstract_playlist.py
similarity index 100%
rename from resources/lib/youtube_plugin/kodion/impl/abstract_playlist.py
rename to resources/lib/youtube_plugin/kodion/player/abstract_playlist.py
diff --git a/resources/lib/youtube_plugin/kodion/impl/xbmc/xbmc_player.py b/resources/lib/youtube_plugin/kodion/player/xbmc/xbmc_player.py
similarity index 100%
rename from resources/lib/youtube_plugin/kodion/impl/xbmc/xbmc_player.py
rename to resources/lib/youtube_plugin/kodion/player/xbmc/xbmc_player.py
diff --git a/resources/lib/youtube_plugin/kodion/impl/xbmc/xbmc_playlist.py b/resources/lib/youtube_plugin/kodion/player/xbmc/xbmc_playlist.py
similarity index 98%
rename from resources/lib/youtube_plugin/kodion/impl/xbmc/xbmc_playlist.py
rename to resources/lib/youtube_plugin/kodion/player/xbmc/xbmc_playlist.py
index e7c6edc12..7eb8a5a1e 100644
--- a/resources/lib/youtube_plugin/kodion/impl/xbmc/xbmc_playlist.py
+++ b/resources/lib/youtube_plugin/kodion/player/xbmc/xbmc_playlist.py
@@ -12,7 +12,7 @@
import xbmc
from ..abstract_playlist import AbstractPlaylist
-from . import xbmc_items
+from ...ui.xbmc import xbmc_items
class XbmcPlaylist(AbstractPlaylist):
diff --git a/resources/lib/youtube_plugin/kodion/impl/xbmc/__init__.py b/resources/lib/youtube_plugin/kodion/plugin/__init__.py
similarity index 50%
rename from resources/lib/youtube_plugin/kodion/impl/xbmc/__init__.py
rename to resources/lib/youtube_plugin/kodion/plugin/__init__.py
index 86fe5e5c0..37fe21cbe 100644
--- a/resources/lib/youtube_plugin/kodion/impl/xbmc/__init__.py
+++ b/resources/lib/youtube_plugin/kodion/plugin/__init__.py
@@ -1,11 +1,13 @@
# -*- coding: utf-8 -*-
"""
- Copyright (C) 2014-2016 bromix (plugin.video.youtube)
- Copyright (C) 2016-2018 plugin.video.youtube
+ Copyright (C) 2023-present plugin.video.youtube
SPDX-License-Identifier: GPL-2.0-only
See LICENSES/GPL-2.0-only for more information.
"""
-__all__ = []
+from .xbmc.xbmc_runner import XbmcRunner as Runner
+
+
+__all__ = ('Runner', )
diff --git a/resources/lib/youtube_plugin/kodion/impl/abstract_provider_runner.py b/resources/lib/youtube_plugin/kodion/plugin/abstract_provider_runner.py
similarity index 89%
rename from resources/lib/youtube_plugin/kodion/impl/abstract_provider_runner.py
rename to resources/lib/youtube_plugin/kodion/plugin/abstract_provider_runner.py
index 515cf4544..d1aed3959 100644
--- a/resources/lib/youtube_plugin/kodion/impl/abstract_provider_runner.py
+++ b/resources/lib/youtube_plugin/kodion/plugin/abstract_provider_runner.py
@@ -13,5 +13,5 @@ class AbstractProviderRunner(object):
def __init__(self):
pass
- def run(self, provider, context=None):
+ def run(self, provider, context):
raise NotImplementedError()
diff --git a/resources/lib/youtube_plugin/kodion/impl/xbmc/xbmc_runner.py b/resources/lib/youtube_plugin/kodion/plugin/xbmc/xbmc_runner.py
similarity index 98%
rename from resources/lib/youtube_plugin/kodion/impl/xbmc/xbmc_runner.py
rename to resources/lib/youtube_plugin/kodion/plugin/xbmc/xbmc_runner.py
index 0719e5de3..b44a8fed8 100644
--- a/resources/lib/youtube_plugin/kodion/impl/xbmc/xbmc_runner.py
+++ b/resources/lib/youtube_plugin/kodion/plugin/xbmc/xbmc_runner.py
@@ -15,8 +15,7 @@
from ...exceptions import KodionException
from ...items import AudioItem, DirectoryItem, ImageItem, UriItem, VideoItem
from ... import AbstractProvider
-from . import info_labels
-from . import xbmc_items
+from ...ui.xbmc import info_labels, xbmc_items
class XbmcRunner(AbstractProviderRunner):
@@ -25,7 +24,7 @@ def __init__(self):
self.handle = None
self.settings = None
- def run(self, provider, context=None):
+ def run(self, provider, context):
self.handle = context.get_handle()
diff --git a/resources/lib/youtube_plugin/kodion/runner.py b/resources/lib/youtube_plugin/kodion/runner.py
index 32104852c..19067c112 100644
--- a/resources/lib/youtube_plugin/kodion/runner.py
+++ b/resources/lib/youtube_plugin/kodion/runner.py
@@ -11,10 +11,10 @@
import copy
import timeit
-from .impl import Runner
-from .impl import Context
-
from . import debug
+from .context import Context
+from .plugin import Runner
+
__all__ = ['run']
diff --git a/resources/lib/youtube_plugin/kodion/service.py b/resources/lib/youtube_plugin/kodion/service.py
index a1e3fefc0..163f1375c 100644
--- a/resources/lib/youtube_plugin/kodion/service.py
+++ b/resources/lib/youtube_plugin/kodion/service.py
@@ -11,10 +11,9 @@
from datetime import datetime
import time
-from .impl import Context
+from .context import Context
+from .utils import YouTubeMonitor, YouTubePlayer
from ..youtube.provider import Provider
-from .utils import YouTubeMonitor
-from .utils import YouTubePlayer
def strptime(stamp, stamp_fmt):
diff --git a/resources/lib/youtube_plugin/kodion/settings/__init__.py b/resources/lib/youtube_plugin/kodion/settings/__init__.py
new file mode 100644
index 000000000..61a1c1394
--- /dev/null
+++ b/resources/lib/youtube_plugin/kodion/settings/__init__.py
@@ -0,0 +1,13 @@
+# -*- coding: utf-8 -*-
+"""
+
+ Copyright (C) 2023-present plugin.video.youtube
+
+ SPDX-License-Identifier: GPL-2.0-only
+ See LICENSES/GPL-2.0-only for more information.
+"""
+
+from .xbmc.xbmc_plugin_settings import XbmcPluginSettings as Settings
+
+
+__all__ = ('Settings', )
diff --git a/resources/lib/youtube_plugin/kodion/impl/abstract_settings.py b/resources/lib/youtube_plugin/kodion/settings/abstract_settings.py
similarity index 100%
rename from resources/lib/youtube_plugin/kodion/impl/abstract_settings.py
rename to resources/lib/youtube_plugin/kodion/settings/abstract_settings.py
diff --git a/resources/lib/youtube_plugin/kodion/impl/xbmc/xbmc_plugin_settings.py b/resources/lib/youtube_plugin/kodion/settings/xbmc/xbmc_plugin_settings.py
similarity index 100%
rename from resources/lib/youtube_plugin/kodion/impl/xbmc/xbmc_plugin_settings.py
rename to resources/lib/youtube_plugin/kodion/settings/xbmc/xbmc_plugin_settings.py
diff --git a/resources/lib/youtube_plugin/kodion/ui/__init__.py b/resources/lib/youtube_plugin/kodion/ui/__init__.py
new file mode 100644
index 000000000..20669bc59
--- /dev/null
+++ b/resources/lib/youtube_plugin/kodion/ui/__init__.py
@@ -0,0 +1,13 @@
+# -*- coding: utf-8 -*-
+"""
+
+ Copyright (C) 2023-present plugin.video.youtube
+
+ SPDX-License-Identifier: GPL-2.0-only
+ See LICENSES/GPL-2.0-only for more information.
+"""
+
+from .xbmc.xbmc_context_ui import XbmcContextUI as ContextUI
+
+
+__all__ = ('ContextUI', )
diff --git a/resources/lib/youtube_plugin/kodion/impl/abstract_context_ui.py b/resources/lib/youtube_plugin/kodion/ui/abstract_context_ui.py
similarity index 100%
rename from resources/lib/youtube_plugin/kodion/impl/abstract_context_ui.py
rename to resources/lib/youtube_plugin/kodion/ui/abstract_context_ui.py
diff --git a/resources/lib/youtube_plugin/kodion/impl/abstract_progress_dialog.py b/resources/lib/youtube_plugin/kodion/ui/abstract_progress_dialog.py
similarity index 100%
rename from resources/lib/youtube_plugin/kodion/impl/abstract_progress_dialog.py
rename to resources/lib/youtube_plugin/kodion/ui/abstract_progress_dialog.py
diff --git a/resources/lib/youtube_plugin/kodion/impl/xbmc/info_labels.py b/resources/lib/youtube_plugin/kodion/ui/xbmc/info_labels.py
similarity index 100%
rename from resources/lib/youtube_plugin/kodion/impl/xbmc/info_labels.py
rename to resources/lib/youtube_plugin/kodion/ui/xbmc/info_labels.py
diff --git a/resources/lib/youtube_plugin/kodion/impl/xbmc/xbmc_context_ui.py b/resources/lib/youtube_plugin/kodion/ui/xbmc/xbmc_context_ui.py
similarity index 100%
rename from resources/lib/youtube_plugin/kodion/impl/xbmc/xbmc_context_ui.py
rename to resources/lib/youtube_plugin/kodion/ui/xbmc/xbmc_context_ui.py
index 618fc241d..b8cdb0185 100644
--- a/resources/lib/youtube_plugin/kodion/impl/xbmc/xbmc_context_ui.py
+++ b/resources/lib/youtube_plugin/kodion/ui/xbmc/xbmc_context_ui.py
@@ -11,9 +11,9 @@
import xbmc
import xbmcgui
-from ..abstract_context_ui import AbstractContextUI
from .xbmc_progress_dialog import XbmcProgressDialog
from .xbmc_progress_dialog_bg import XbmcProgressDialogBG
+from ..abstract_context_ui import AbstractContextUI
from ... import constants
from ... import utils
diff --git a/resources/lib/youtube_plugin/kodion/impl/xbmc/xbmc_items.py b/resources/lib/youtube_plugin/kodion/ui/xbmc/xbmc_items.py
similarity index 100%
rename from resources/lib/youtube_plugin/kodion/impl/xbmc/xbmc_items.py
rename to resources/lib/youtube_plugin/kodion/ui/xbmc/xbmc_items.py
diff --git a/resources/lib/youtube_plugin/kodion/impl/xbmc/xbmc_progress_dialog.py b/resources/lib/youtube_plugin/kodion/ui/xbmc/xbmc_progress_dialog.py
similarity index 100%
rename from resources/lib/youtube_plugin/kodion/impl/xbmc/xbmc_progress_dialog.py
rename to resources/lib/youtube_plugin/kodion/ui/xbmc/xbmc_progress_dialog.py
diff --git a/resources/lib/youtube_plugin/kodion/impl/xbmc/xbmc_progress_dialog_bg.py b/resources/lib/youtube_plugin/kodion/ui/xbmc/xbmc_progress_dialog_bg.py
similarity index 100%
rename from resources/lib/youtube_plugin/kodion/impl/xbmc/xbmc_progress_dialog_bg.py
rename to resources/lib/youtube_plugin/kodion/ui/xbmc/xbmc_progress_dialog_bg.py
diff --git a/resources/lib/youtube_plugin/kodion/utils/__init__.py b/resources/lib/youtube_plugin/kodion/utils/__init__.py
index 5365ec8f6..f74045045 100644
--- a/resources/lib/youtube_plugin/kodion/utils/__init__.py
+++ b/resources/lib/youtube_plugin/kodion/utils/__init__.py
@@ -26,17 +26,33 @@
from .watch_later_list import WatchLaterList
from .function_cache import FunctionCache
from .access_manager import AccessManager
-from .http_server import get_http_server, is_httpd_live, get_client_ip_address
from .monitor import YouTubeMonitor
from .player import YouTubePlayer
from .playback_history import PlaybackHistory
from .data_cache import DataCache
from .system_version import SystemVersion
-from . import ip_api
-__all__ = ['SearchHistory', 'FavoriteList', 'WatchLaterList', 'FunctionCache', 'AccessManager',
- 'strip_html_from_text', 'create_path', 'create_uri_path', 'find_best_fit', 'to_unicode', 'to_utf8',
- 'datetime_parser', 'select_stream', 'get_http_server', 'is_httpd_live', 'YouTubeMonitor',
- 'make_dirs', 'loose_version', 'ip_api', 'PlaybackHistory', 'DataCache', 'get_client_ip_address',
- 'SystemVersion', 'find_video_id', 'YouTubePlayer']
+__all__ = [
+ 'create_path',
+ 'create_uri_path',
+ 'datetime_parser',
+ 'find_best_fit',
+ 'find_video_id',
+ 'loose_version',
+ 'make_dirs',
+ 'select_stream',
+ 'strip_html_from_text',
+ 'to_unicode',
+ 'to_utf8',
+ 'AccessManager',
+ 'DataCache',
+ 'FavoriteList',
+ 'FunctionCache',
+ 'PlaybackHistory',
+ 'SearchHistory',
+ 'SystemVersion',
+ 'WatchLaterList',
+ 'YouTubeMonitor',
+ 'YouTubePlayer'
+]
diff --git a/resources/lib/youtube_plugin/kodion/utils/monitor.py b/resources/lib/youtube_plugin/kodion/utils/monitor.py
index 3c86eda88..363109250 100644
--- a/resources/lib/youtube_plugin/kodion/utils/monitor.py
+++ b/resources/lib/youtube_plugin/kodion/utils/monitor.py
@@ -17,9 +17,10 @@
import xbmcaddon
import xbmcvfs
-from ..utils import get_http_server, is_httpd_live
+from ..network import get_http_server, is_httpd_live
from .. import logger
+
try:
xbmc.translatePath = xbmcvfs.translatePath
except AttributeError:
diff --git a/resources/lib/youtube_plugin/youtube/client/login_client.py b/resources/lib/youtube_plugin/youtube/client/login_client.py
index 0524027e9..f713e16ea 100644
--- a/resources/lib/youtube_plugin/youtube/client/login_client.py
+++ b/resources/lib/youtube_plugin/youtube/client/login_client.py
@@ -13,18 +13,19 @@
from requests.exceptions import InvalidJSONError
-from .request_client import YouTubeRequestClient
-from ...youtube.youtube_exceptions import (
- InvalidGrant,
- LoginException,
- YouTubeException,
-)
from .__config__ import (
api,
developer_keys,
keys_changed,
youtube_tv,
)
+from .request_client import YouTubeRequestClient
+from ...kodion.logger import log_debug
+from ...youtube.youtube_exceptions import (
+ InvalidGrant,
+ LoginException,
+ YouTubeException,
+)
class LoginClient(YouTubeRequestClient):
@@ -46,10 +47,8 @@ class LoginClient(YouTubeRequestClient):
'developer': developer_keys
}
- def __init__(self, context, config=None, language='en-US', region='',
+ def __init__(self, config=None, language='en-US', region='',
access_token='', access_token_tv=''):
- self._context = context
-
self._config = self.CONFIGS['main'] if config is None else config
self._config_tv = self.CONFIGS['youtube-tv']
# the default language is always en_US (like YouTube on the WEB)
@@ -64,7 +63,7 @@ def __init__(self, context, config=None, language='en-US', region='',
self._log_error_callback = None
- super(LoginClient, self).__init__(context=context)
+ super(LoginClient, self).__init__()
@staticmethod
def _login_json_hook(response):
@@ -151,7 +150,7 @@ def refresh_token(self, refresh_token, client_id='', client_secret=''):
' client_id: |', client_id[:5], '...|',
' client_secret: |', client_secret[:5], '...|)'
])
- self._context.log_debug('Refresh token for ' + client_summary)
+ log_debug('Refresh token for ' + client_summary)
json_data = self.request('https://www.googleapis.com/oauth2/v4/token',
method='POST', data=post_data, headers=headers,
@@ -192,7 +191,7 @@ def request_access_token(self, code, client_id='', client_secret=''):
' client_id: |', client_id[:5], '...|',
' client_secret: |', client_secret[:5], '...|)'
])
- self._context.log_debug('Requesting access token for ' + client_summary)
+ log_debug('Requesting access token for ' + client_summary)
json_data = self.request('https://www.googleapis.com/oauth2/v4/token',
method='POST', data=post_data, headers=headers,
@@ -223,7 +222,7 @@ def request_device_and_user_code(self, client_id=''):
'(config_type: |', config_type, '|',
' client_id: |', client_id[:5], '...|)',
])
- self._context.log_debug('Requesting device and user code for ' + client_summary)
+ log_debug('Requesting device and user code for ' + client_summary)
json_data = self.request('https://accounts.google.com/o/oauth2/device/code',
method='POST', data=post_data, headers=headers,
diff --git a/resources/lib/youtube_plugin/youtube/client/request_client.py b/resources/lib/youtube_plugin/youtube/client/request_client.py
index 7b2c482fd..0bfb2771d 100644
--- a/resources/lib/youtube_plugin/youtube/client/request_client.py
+++ b/resources/lib/youtube_plugin/youtube/client/request_client.py
@@ -7,7 +7,7 @@
See LICENSES/GPL-2.0-only for more information.
"""
-from ...kodion.utils.requests import BaseRequestsClass
+from ...kodion.network import BaseRequestsClass
from ...youtube.youtube_exceptions import YouTubeException
@@ -255,10 +255,8 @@ class YouTubeRequestClient(BaseRequestsClass):
},
}
- def __init__(self, context):
- super(YouTubeRequestClient, self).__init__(
- context=context, exc_type=YouTubeException
- )
+ def __init__(self):
+ super(YouTubeRequestClient, self).__init__(exc_type=YouTubeException)
@staticmethod
def json_traverse(json_data, path):
diff --git a/resources/lib/youtube_plugin/youtube/helper/tv.py b/resources/lib/youtube_plugin/youtube/helper/tv.py
index c9818f924..94a9795af 100644
--- a/resources/lib/youtube_plugin/youtube/helper/tv.py
+++ b/resources/lib/youtube_plugin/youtube/helper/tv.py
@@ -8,9 +8,8 @@
See LICENSES/GPL-2.0-only for more information.
"""
-from ... import kodion
+from ...kodion.items import DirectoryItem, NextPageItem, VideoItem
from ...youtube.helper import utils
-from ...kodion.items.video_item import VideoItem
def my_subscriptions_to_items(provider, context, json_data, do_filter=False):
@@ -65,7 +64,7 @@ def my_subscriptions_to_items(provider, context, json_data, do_filter=False):
new_context = context.clone(new_params=new_params)
current_page = int(new_context.get_param('page', 1))
- next_page_item = kodion.items.NextPageItem(new_context, current_page, fanart=provider.get_fanart(new_context))
+ next_page_item = NextPageItem(new_context, current_page, fanart=provider.get_fanart(new_context))
result.append(next_page_item)
return result
@@ -111,7 +110,7 @@ def tv_videos_to_items(provider, context, json_data):
new_context = context.clone(new_params=new_params)
current_page = int(new_context.get_param('page', 1))
- next_page_item = kodion.items.NextPageItem(new_context, current_page, fanart=provider.get_fanart(new_context))
+ next_page_item = NextPageItem(new_context, current_page, fanart=provider.get_fanart(new_context))
result.append(next_page_item)
return result
@@ -140,7 +139,7 @@ def saved_playlists_to_items(provider, context, json_data):
else:
item_uri = context.create_uri(['playlist', playlist_id], item_params)
- playlist_item = kodion.items.DirectoryItem(title, item_uri, image=image)
+ playlist_item = DirectoryItem(title, item_uri, image=image)
playlist_item.set_fanart(provider.get_fanart(context))
result.append(playlist_item)
playlist_id_dict[playlist_id] = playlist_item
@@ -162,7 +161,7 @@ def saved_playlists_to_items(provider, context, json_data):
new_context = context.clone(new_params=new_params)
current_page = int(new_context.get_param('page', 1))
- next_page_item = kodion.items.NextPageItem(new_context, current_page, fanart=provider.get_fanart(new_context))
+ next_page_item = NextPageItem(new_context, current_page, fanart=provider.get_fanart(new_context))
result.append(next_page_item)
return result
diff --git a/resources/lib/youtube_plugin/youtube/helper/utils.py b/resources/lib/youtube_plugin/youtube/helper/utils.py
index 30ca5f284..78392b4af 100644
--- a/resources/lib/youtube_plugin/youtube/helper/utils.py
+++ b/resources/lib/youtube_plugin/youtube/helper/utils.py
@@ -11,8 +11,8 @@
import re
import time
-from ... import kodion
from ...kodion import utils
+from ...kodion.items import DirectoryItem
from ...youtube.helper import yt_context_menu
try:
@@ -44,8 +44,8 @@ def get_thumb_timestamp(minutes=15):
def make_comment_item(context, provider, snippet, uri, total_replies=0):
- author = '[B]{}[/B]'.format(kodion.utils.to_str(snippet['authorDisplayName']))
- body = kodion.utils.to_str(snippet['textOriginal'])
+ author = '[B]{}[/B]'.format(utils.to_str(snippet['authorDisplayName']))
+ body = utils.to_str(snippet['textOriginal'])
label_props = None
plot_props = None
@@ -86,7 +86,7 @@ def make_comment_item(context, provider, snippet, uri, total_replies=0):
else:
plot = '{author}{edited}[CR][CR]{body}'.format(author=author, edited=edited, body=body)
- comment_item = kodion.items.DirectoryItem(label, uri)
+ comment_item = DirectoryItem(label, uri)
comment_item.set_plot(plot)
comment_item.set_date_from_datetime(utils.datetime_parser.parse(snippet['publishedAt']))
if not uri:
@@ -346,7 +346,7 @@ def update_video_infos(provider, context, video_id_dict,
# plot
channel_name = snippet.get('channelTitle', '')
- description = kodion.utils.strip_html_from_text(snippet['description'])
+ description = utils.strip_html_from_text(snippet['description'])
if show_channel_name and channel_name:
description = '%s[CR][CR]%s' % (ui.uppercase(ui.bold(channel_name)), description)
video_item.set_studio(channel_name)
@@ -438,7 +438,7 @@ def update_video_infos(provider, context, video_id_dict,
# got to [CHANNEL], only if we are not directly in the channel provide a jump to the channel
if (channel_id and channel_name and
- kodion.utils.create_path('channel', channel_id) != context.get_path()):
+ utils.create_path('channel', channel_id) != context.get_path()):
video_item.set_channel_id(channel_id)
yt_context_menu.append_go_to_channel(context_menu, provider, context, channel_id, channel_name)
@@ -581,7 +581,7 @@ def update_play_info(provider, context, video_id, video_item, video_stream, use_
# plot
channel_name = snippet.get('channelTitle', '')
- description = kodion.utils.strip_html_from_text(snippet['description'])
+ description = utils.strip_html_from_text(snippet['description'])
if channel_name and settings.get_bool('youtube.view.description.show_channel_name', True):
description = '%s[CR][CR]%s' % (ui.uppercase(ui.bold(channel_name)), description)
video_item.set_studio(channel_name)
diff --git a/resources/lib/youtube_plugin/youtube/helper/video_info.py b/resources/lib/youtube_plugin/youtube/helper/video_info.py
index 32f52eb54..914c3ff21 100644
--- a/resources/lib/youtube_plugin/youtube/helper/video_info.py
+++ b/resources/lib/youtube_plugin/youtube/helper/video_info.py
@@ -27,7 +27,8 @@
import xbmcvfs
from ..client.request_client import YouTubeRequestClient
-from ...kodion.utils import is_httpd_live, make_dirs, DataCache
+from ...kodion.network import is_httpd_live
+from ...kodion.utils import make_dirs, DataCache
from ..youtube_exceptions import YouTubeException
from .subtitles import Subtitles
from .ratebypass import ratebypass
@@ -643,7 +644,7 @@ def __init__(self, context, access_token='', language='en-US'):
'gl': settings.get_string('youtube.region', 'US'),
}
- super(VideoInfo, self).__init__(context=context)
+ super(VideoInfo, self).__init__()
@staticmethod
def _generate_cpn():
diff --git a/resources/lib/youtube_plugin/youtube/helper/yt_play.py b/resources/lib/youtube_plugin/youtube/helper/yt_play.py
index e4e2ec096..eceed96c4 100644
--- a/resources/lib/youtube_plugin/youtube/helper/yt_play.py
+++ b/resources/lib/youtube_plugin/youtube/helper/yt_play.py
@@ -18,7 +18,7 @@
from ... import kodion
from ...kodion import constants
from ...kodion.items import VideoItem
-from ...kodion.impl.xbmc.xbmc_items import to_playback_item
+from ...kodion.ui.xbmc.xbmc_items import to_playback_item
from ...youtube.youtube_exceptions import YouTubeException
from ...youtube.helper import utils, v3
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 559cba4d2..c6fe1d609 100644
--- a/resources/lib/youtube_plugin/youtube/helper/yt_setup_wizard.py
+++ b/resources/lib/youtube_plugin/youtube/helper/yt_setup_wizard.py
@@ -8,7 +8,7 @@
See LICENSES/GPL-2.0-only for more information.
"""
-from ...kodion.utils import ip_api
+from ...kodion.network import Locator
DEFAULT_LANGUAGES = {'items': [{'snippet': {'name': 'Afrikaans', 'hl': 'af'}, 'id': 'af'}, {'snippet': {'name': 'Azerbaijani', 'hl': 'az'}, 'id': 'az'}, {'snippet': {'name': 'Indonesian', 'hl': 'id'}, 'id': 'id'}, {'snippet': {'name': 'Malay', 'hl': 'ms'}, 'id': 'ms'},
@@ -106,15 +106,14 @@ def _process_language(provider, context):
def _process_geo_location(provider, context):
- settings = context.get_settings()
if not context.get_ui().on_yes_no_input(context.get_name(), context.localize(provider.LOCAL_MAP['youtube.perform.geolocation'])):
return
- locator = ip_api.Locator(context)
+ locator = Locator()
locator.locate_requester()
coordinates = locator.coordinates()
if coordinates:
- settings.set_location('{lat},{lon}'.format(lat=coordinates[0], lon=coordinates[1]))
+ context.get_settings().set_location('{0[lat]},{0[lon]}'.format(coordinates))
def process(provider, context):
diff --git a/resources/lib/youtube_plugin/youtube/provider.py b/resources/lib/youtube_plugin/youtube/provider.py
index d2a4c7a85..09d47d879 100644
--- a/resources/lib/youtube_plugin/youtube/provider.py
+++ b/resources/lib/youtube_plugin/youtube/provider.py
@@ -15,17 +15,6 @@
import socket
from base64 import b64decode
-from ..youtube.helper import yt_subscriptions
-from .. import kodion
-from ..kodion.utils import (
- find_video_id,
- get_client_ip_address,
- is_httpd_live,
- strip_html_from_text,
- FunctionCache,
-)
-from ..kodion.items import DirectoryItem
-from ..youtube.client import YouTube
from .helper import (
v3,
yt_context_menu,
@@ -41,6 +30,16 @@
UrlToItemConverter,
)
from .youtube_exceptions import InvalidGrant, LoginException
+from ..kodion import (
+ constants,
+ AbstractProvider,
+ RegisterProviderPath,
+)
+from ..youtube.client import YouTube
+from ..kodion.items import DirectoryItem, NewSearchItem, SearchItem
+from ..kodion.network import get_client_ip_address, is_httpd_live
+from ..kodion.utils import find_video_id, strip_html_from_text, FunctionCache
+from ..youtube.helper import yt_subscriptions
import xbmc
import xbmcaddon
@@ -55,7 +54,7 @@
pass
-class Provider(kodion.AbstractProvider):
+class Provider(AbstractProvider):
LOCAL_MAP = {'youtube.search': 30102,
'youtube.next_page': 30106,
'youtube.watch_later': 30107,
@@ -357,8 +356,7 @@ def get_client(self, context):
refresh_tokens = refresh_tokens.split('|')
context.log_debug('Access token count: |%d| Refresh token count: |%d|' % (len(access_tokens), len(refresh_tokens)))
- client = YouTube(context=context,
- language=language,
+ client = YouTube(language=language,
region=region,
items_per_page=items_per_page,
config=dev_keys if dev_keys else youtube_config)
@@ -424,7 +422,7 @@ def get_fanart(context):
return context.create_resource_path('media', 'fanart.jpg')
# noinspection PyUnusedLocal
- @kodion.RegisterProviderPath('^/uri2addon/$')
+ @RegisterProviderPath('^/uri2addon/$')
def on_uri2addon(self, context, re_match):
uri = context.get_param('uri', '')
if not uri:
@@ -440,9 +438,9 @@ def on_uri2addon(self, context, re_match):
return False
- @kodion.RegisterProviderPath('^/playlist/(?P[^/]+)/$')
+ @RegisterProviderPath('^/playlist/(?P[^/]+)/$')
def _on_playlist(self, context, re_match):
- self.set_content_type(context, kodion.constants.content_type.VIDEOS)
+ self.set_content_type(context, constants.content_type.VIDEOS)
result = []
@@ -464,9 +462,9 @@ def _on_playlist(self, context, re_match):
playlist_id:
"""
- @kodion.RegisterProviderPath('^/channel/(?P[^/]+)/playlist/(?P[^/]+)/$')
+ @RegisterProviderPath('^/channel/(?P[^/]+)/playlist/(?P[^/]+)/$')
def _on_channel_playlist(self, context, re_match):
- self.set_content_type(context, kodion.constants.content_type.VIDEOS)
+ self.set_content_type(context, constants.content_type.VIDEOS)
client = self.get_client(context)
result = []
@@ -487,9 +485,9 @@ def _on_channel_playlist(self, context, re_match):
channel_id:
"""
- @kodion.RegisterProviderPath('^/channel/(?P[^/]+)/playlists/$')
+ @RegisterProviderPath('^/channel/(?P[^/]+)/playlists/$')
def _on_channel_playlists(self, context, re_match):
- self.set_content_type(context, kodion.constants.content_type.FILES)
+ self.set_content_type(context, constants.content_type.FILES)
result = []
channel_id = re_match.group('channel_id')
@@ -528,9 +526,9 @@ def _on_channel_playlists(self, context, re_match):
channel_id:
"""
- @kodion.RegisterProviderPath('^/channel/(?P[^/]+)/live/$')
+ @RegisterProviderPath('^/channel/(?P[^/]+)/live/$')
def _on_channel_live(self, context, re_match):
- self.set_content_type(context, kodion.constants.content_type.VIDEOS)
+ self.set_content_type(context, constants.content_type.VIDEOS)
result = []
channel_id = re_match.group('channel_id')
@@ -551,7 +549,7 @@ def _on_channel_live(self, context, re_match):
channel_id:
"""
- @kodion.RegisterProviderPath('^/(?P(channel|user))/(?P[^/]+)/$')
+ @RegisterProviderPath('^/(?P(channel|user))/(?P[^/]+)/$')
def _on_channel(self, context, re_match):
listitem_channel_id = context.get_ui().get_info_label('Container.ListItem(0).Property(channel_id)')
@@ -565,7 +563,7 @@ def _on_channel(self, context, re_match):
if method == 'channel' and not channel_id:
return False
- self.set_content_type(context, kodion.constants.content_type.VIDEOS)
+ self.set_content_type(context, constants.content_type.VIDEOS)
resource_manager = self.get_resource_manager(context)
@@ -623,9 +621,9 @@ def _on_channel(self, context, re_match):
search_live_id = mine_id if mine_id else channel_id
if not hide_search:
- search_item = kodion.items.NewSearchItem(context, alt_name=context.get_ui().bold(context.localize(self.LOCAL_MAP['youtube.search'])),
- image=context.create_resource_path('media', 'search.png'),
- fanart=self.get_fanart(context), channel_id=search_live_id, incognito=incognito, addon_id=addon_id)
+ search_item = NewSearchItem(context, alt_name=context.get_ui().bold(context.localize(self.LOCAL_MAP['youtube.search'])),
+ image=context.create_resource_path('media', 'search.png'),
+ fanart=self.get_fanart(context), channel_id=search_live_id, incognito=incognito, addon_id=addon_id)
search_item.set_fanart(self.get_fanart(context))
result.append(search_item)
@@ -651,15 +649,15 @@ def _on_channel(self, context, re_match):
return result
# noinspection PyUnusedLocal
- @kodion.RegisterProviderPath('^/location/mine/$')
+ @RegisterProviderPath('^/location/mine/$')
def _on_my_location(self, context, re_match):
- self.set_content_type(context, kodion.constants.content_type.FILES)
+ self.set_content_type(context, constants.content_type.FILES)
settings = context.get_settings()
result = []
# search
- search_item = kodion.items.SearchItem(context, image=context.create_resource_path('media', 'search.png'),
+ search_item = SearchItem(context, image=context.create_resource_path('media', 'search.png'),
fanart=self.get_fanart(context), location=True)
result.append(search_item)
@@ -702,7 +700,7 @@ def _on_my_location(self, context, re_match):
"""
# noinspection PyUnusedLocal
- @kodion.RegisterProviderPath('^/play/$')
+ @RegisterProviderPath('^/play/$')
def on_play(self, context, re_match):
listitem_path = context.get_ui().get_info_label('Container.ListItem(0).FileNameAndPath')
@@ -779,25 +777,25 @@ def on_play(self, context, re_match):
return yt_play.play_channel_live(self, context)
return False
- @kodion.RegisterProviderPath('^/video/(?P[^/]+)/$')
+ @RegisterProviderPath('^/video/(?P[^/]+)/$')
def _on_video_x(self, context, re_match):
method = re_match.group('method')
return yt_video.process(method, self, context, re_match)
- @kodion.RegisterProviderPath('^/playlist/(?P[^/]+)/(?P[^/]+)/$')
+ @RegisterProviderPath('^/playlist/(?P[^/]+)/(?P[^/]+)/$')
def _on_playlist_x(self, context, re_match):
method = re_match.group('method')
category = re_match.group('category')
return yt_playlist.process(method, category, self, context)
- @kodion.RegisterProviderPath('^/subscriptions/(?P[^/]+)/$')
+ @RegisterProviderPath('^/subscriptions/(?P[^/]+)/$')
def _on_subscriptions(self, context, re_match):
method = re_match.group('method')
resource_manager = self.get_resource_manager(context)
subscriptions = yt_subscriptions.process(method, self, context)
if method == 'list':
- self.set_content_type(context, kodion.constants.content_type.FILES)
+ self.set_content_type(context, constants.content_type.FILES)
channel_ids = []
for subscription in subscriptions:
channel_ids.append(subscription.get_channel_id())
@@ -808,20 +806,20 @@ def _on_subscriptions(self, context, re_match):
return subscriptions
- @kodion.RegisterProviderPath('^/special/(?P[^/]+)/$')
+ @RegisterProviderPath('^/special/(?P[^/]+)/$')
def _on_yt_specials(self, context, re_match):
category = re_match.group('category')
return yt_specials.process(category, self, context)
# noinspection PyUnusedLocal
- @kodion.RegisterProviderPath('^/history/clear/$')
+ @RegisterProviderPath('^/history/clear/$')
def _on_yt_clear_history(self, context, re_match):
if context.get_ui().on_yes_no_input(context.get_name(), context.localize(self.LOCAL_MAP['youtube.clear_history_confirmation'])):
json_data = self.get_client(context).clear_watch_history()
if 'error' not in json_data:
context.get_ui().show_notification(context.localize(self.LOCAL_MAP['youtube.succeeded']))
- @kodion.RegisterProviderPath('^/users/(?P[^/]+)/$')
+ @RegisterProviderPath('^/users/(?P[^/]+)/$')
def _on_users(self, context, re_match):
action = re_match.group('action')
refresh = context.get_param('refresh', 'true').lower() == 'true'
@@ -972,7 +970,7 @@ def switch_to_user(_user):
return True
- @kodion.RegisterProviderPath('^/sign/(?P[^/]+)/$')
+ @RegisterProviderPath('^/sign/(?P[^/]+)/$')
def _on_sign(self, context, re_match):
sign_out_confirmed = context.get_param('confirmed', '').lower() == 'true'
mode = re_match.group('mode')
@@ -989,7 +987,7 @@ def _on_sign(self, context, re_match):
yt_login.process(mode, self, context)
return False
- @kodion.RegisterProviderPath('^/search/$')
+ @RegisterProviderPath('^/search/$')
def endpoint_search(self, context, re_match):
query = context.get_param('q', '')
if not query:
@@ -1031,9 +1029,9 @@ def on_search(self, search_text, context, re_match):
context.set_param('q', search_text)
if search_type == 'video':
- self.set_content_type(context, kodion.constants.content_type.VIDEOS)
+ self.set_content_type(context, constants.content_type.VIDEOS)
else:
- self.set_content_type(context, kodion.constants.content_type.FILES)
+ self.set_content_type(context, constants.content_type.FILES)
if page == 1 and search_type == 'video' and not event_type and not hide_folders:
if not channel_id and not location:
@@ -1075,7 +1073,7 @@ def on_search(self, search_text, context, re_match):
result.extend(v3.response_to_items(self, context, json_data))
return result
- @kodion.RegisterProviderPath('^/config/(?P[^/]+)/$')
+ @RegisterProviderPath('^/config/(?P[^/]+)/$')
def configure_addon(self, context, re_match):
switch = re_match.group('switch')
settings = context.get_settings()
@@ -1122,7 +1120,7 @@ def configure_addon(self, context, re_match):
return False
# noinspection PyUnusedLocal
- @kodion.RegisterProviderPath('^/my_subscriptions/filter/$')
+ @RegisterProviderPath('^/my_subscriptions/filter/$')
def manage_my_subscription_filter(self, context, re_match):
params = context.get_params()
action = params.get('action')
@@ -1160,7 +1158,7 @@ def manage_my_subscription_filter(self, context, re_match):
context.get_ui().show_notification(message=message)
context.get_ui().refresh_container()
- @kodion.RegisterProviderPath('^/maintain/(?P[^/]+)/(?P[^/]+)/$')
+ @RegisterProviderPath('^/maintain/(?P[^/]+)/(?P[^/]+)/$')
def maintenance_actions(self, context, re_match):
maint_type = re_match.group('maint_type')
action = re_match.group('action')
@@ -1251,7 +1249,7 @@ def maintenance_actions(self, context, re_match):
context.get_ui().show_notification(context.localize(self.LOCAL_MAP['youtube.requires.krypton']))
# noinspection PyUnusedLocal
- @kodion.RegisterProviderPath('^/api/update/$')
+ @RegisterProviderPath('^/api/update/$')
def api_key_update(self, context, re_match):
settings = context.get_settings()
params = context.get_params()
@@ -1301,7 +1299,7 @@ def api_key_update(self, context, re_match):
context.log_debug('Failed to enable personal API keys. Missing: %s' % ', '.join(log_list))
# noinspection PyUnusedLocal
- @kodion.RegisterProviderPath('^/show_client_ip/$')
+ @RegisterProviderPath('^/show_client_ip/$')
def show_client_ip(self, context, re_match):
port = context.get_settings().httpd_port()
@@ -1315,7 +1313,7 @@ def show_client_ip(self, context, re_match):
context.get_ui().show_notification(context.localize(self.LOCAL_MAP['youtube.httpd.not.running']))
# noinspection PyUnusedLocal
- @kodion.RegisterProviderPath('^/playback_history/$')
+ @RegisterProviderPath('^/playback_history/$')
def on_playback_history(self, context, re_match):
params = context.get_params()
video_id = params.get('video_id')
@@ -1359,7 +1357,7 @@ def on_root(self, context, re_match):
settings = context.get_settings()
_ = self.get_client(context) # required for self.is_logged_in()
- self.set_content_type(context, kodion.constants.content_type.FILES)
+ self.set_content_type(context, constants.content_type.FILES)
result = []
@@ -1420,22 +1418,20 @@ def on_root(self, context, re_match):
# search
if settings.get_bool('youtube.folder.search.show', True):
- search_item = kodion.items.SearchItem(context, image=context.create_resource_path('media', 'search.png'),
- fanart=self.get_fanart(context))
+ search_item = SearchItem(context, image=context.create_resource_path('media', 'search.png'),
+ fanart=self.get_fanart(context))
result.append(search_item)
if settings.get_bool('youtube.folder.quick_search.show', True):
- quick_search_item = kodion.items.NewSearchItem(context,
- alt_name=context.localize(self.LOCAL_MAP['youtube.quick.search']),
- fanart=self.get_fanart(context))
+ quick_search_item = NewSearchItem(context, alt_name=context.localize(self.LOCAL_MAP['youtube.quick.search']),
+ fanart=self.get_fanart(context))
result.append(quick_search_item)
if settings.get_bool('youtube.folder.quick_search_incognito.show', True):
- quick_search_incognito_item = kodion.items.NewSearchItem(context,
- alt_name=context.localize(self.LOCAL_MAP['youtube.quick.search.incognito']),
- image=context.create_resource_path('media', 'search.png'),
- fanart=self.get_fanart(context),
- incognito=True)
+ quick_search_incognito_item = NewSearchItem(context, alt_name=context.localize(self.LOCAL_MAP['youtube.quick.search.incognito']),
+ image=context.create_resource_path('media', 'search.png'),
+ fanart=self.get_fanart(context),
+ incognito=True)
result.append(quick_search_incognito_item)
# my location
@@ -1594,13 +1590,13 @@ def on_root(self, context, re_match):
@staticmethod
def set_content_type(context, content_type):
context.set_content_type(content_type)
- if content_type == kodion.constants.content_type.VIDEOS:
- context.add_sort_method(kodion.constants.sort_method.UNSORTED,
- kodion.constants.sort_method.VIDEO_RUNTIME,
- kodion.constants.sort_method.DATEADDED,
- kodion.constants.sort_method.TRACKNUM,
- kodion.constants.sort_method.VIDEO_TITLE,
- kodion.constants.sort_method.DATE)
+ if content_type == constants.content_type.VIDEOS:
+ context.add_sort_method(constants.sort_method.UNSORTED,
+ constants.sort_method.VIDEO_RUNTIME,
+ constants.sort_method.DATEADDED,
+ constants.sort_method.TRACKNUM,
+ constants.sort_method.VIDEO_TITLE,
+ constants.sort_method.DATE)
def handle_exception(self, context, exception_to_handle):
if isinstance(exception_to_handle, (InvalidGrant, LoginException)):
diff --git a/resources/lib/youtube_registration.py b/resources/lib/youtube_registration.py
index ab64f9ad4..429420ba0 100644
--- a/resources/lib/youtube_registration.py
+++ b/resources/lib/youtube_registration.py
@@ -9,7 +9,7 @@
from base64 import b64encode
from youtube_plugin.kodion.json_store import APIKeyStore
-from youtube_plugin.kodion.impl import Context
+from youtube_plugin.kodion.context import Context
def register_api_keys(addon_id, api_key, client_id, client_secret):
diff --git a/resources/lib/youtube_requests.py b/resources/lib/youtube_requests.py
index 513546a64..7f427348e 100644
--- a/resources/lib/youtube_requests.py
+++ b/resources/lib/youtube_requests.py
@@ -10,7 +10,7 @@
import re
from youtube_plugin.youtube.provider import Provider
-from youtube_plugin.kodion.impl import Context
+from youtube_plugin.kodion.context import Context
def __get_core_components(addon_id=None):
diff --git a/resources/lib/youtube_resolver.py b/resources/lib/youtube_resolver.py
index 28eb03256..69d5d411c 100644
--- a/resources/lib/youtube_resolver.py
+++ b/resources/lib/youtube_resolver.py
@@ -10,7 +10,7 @@
import re
from youtube_plugin.youtube.provider import Provider
-from youtube_plugin.kodion.impl import Context
+from youtube_plugin.kodion.context import Context
def _get_core_components(addon_id=None):
From a0d7c1b90ea4d98cbddba032cdfd648d7784a9cb Mon Sep 17 00:00:00 2001
From: MoojMidge <56883549+MoojMidge@users.noreply.github.com>
Date: Thu, 2 Nov 2023 23:35:34 +1100
Subject: [PATCH 025/141] Fix completed live streams playing at max 720p
- Fix #530
- Partially fix #540
- MPEG-DASH and HLS manifests are not provided for completed live streams
---
.../youtube/helper/video_info.py | 45 +++++++++----------
1 file changed, 22 insertions(+), 23 deletions(-)
diff --git a/resources/lib/youtube_plugin/youtube/helper/video_info.py b/resources/lib/youtube_plugin/youtube/helper/video_info.py
index 914c3ff21..4f66dac61 100644
--- a/resources/lib/youtube_plugin/youtube/helper/video_info.py
+++ b/resources/lib/youtube_plugin/youtube/helper/video_info.py
@@ -1264,26 +1264,32 @@ def _get_video_info(self):
self._player_js = self._get_player_js()
self._cipher = Cipher(self._context, javascript=self._player_js)
- manifest_url = None
- if is_live:
- live_type = _settings.get_live_stream_type()
- if live_type == 'isa_mpd':
- manifest_url = streaming_data.get('dashManifestUrl', '')
- else:
- stream_list.extend(self._load_hls_manifest(
- streaming_data.get('hlsManifestUrl'),
- live_type, meta_info, client['headers'], playback_stats
- ))
- elif httpd_is_live and adaptive_fmts:
+ manifest_url = main_stream = None
+ live_type = is_live and _settings.get_live_stream_type()
+
+ if live_type == 'isa_mpd' and 'dashManifestUrl' in streaming_data:
+ manifest_url = streaming_data['dashManifestUrl']
+ elif 'hlsManifestUrl' in streaming_data:
+ stream_list.extend(self._load_hls_manifest(
+ streaming_data['hlsManifestUrl'],
+ live_type, meta_info, client['headers'], playback_stats
+ ))
+ else:
+ live_type = None
+
+ # extract adaptive streams and create MPEG-DASH manifest
+ if not manifest_url and httpd_is_live and adaptive_fmts:
video_data, audio_data = self._process_stream_data(
adaptive_fmts, default_lang['code']
)
manifest_url, main_stream = self._generate_mpd_manifest(
video_data, audio_data, license_info.get('url')
)
- stream_list.extend(self._load_hls_manifest(
- streaming_data.get('hlsManifestUrl'),
- None, meta_info, client['headers'], playback_stats
+
+ # extract non-adaptive streams
+ if all_fmts:
+ stream_list.extend(self._create_stream_list(
+ all_fmts, meta_info, client['headers'], playback_stats
))
if manifest_url:
@@ -1295,7 +1301,7 @@ def _get_video_info(self):
'playback_stats': playback_stats
}
- if is_live:
+ if live_type:
# MPD structure has segments with additional attributes
# and url has changed from using a query string to using url params
# This breaks the InputStream.Adaptive partial manifest update
@@ -1306,7 +1312,7 @@ def _get_video_info(self):
else:
video_stream['url'] = manifest_url + '/mpd_version/5'
details = self.FORMAT.get('9998')
- else:
+ elif main_stream:
details = self.FORMAT.get('9999').copy()
video_info = main_stream['video']
@@ -1336,13 +1342,6 @@ def _get_video_info(self):
video_stream.update(details)
stream_list.append(video_stream)
- # extract streams from map
- if all_fmts:
- stream_list.extend(self._create_stream_list(
- all_fmts, meta_info, client['headers'], playback_stats
- ))
-
- # last fallback
if not stream_list:
raise YouTubeException('No streams found')
From 6d1301eaa04a04727b0c983fec096d3b20b5bcdf Mon Sep 17 00:00:00 2001
From: MoojMidge <56883549+MoojMidge@users.noreply.github.com>
Date: Sat, 4 Nov 2023 15:13:16 +1100
Subject: [PATCH 026/141] Add setting methods to get/set API details
- Also add some basic validation
---
.../kodion/constants/const_settings.py | 3 +
.../kodion/settings/abstract_settings.py | 67 +++++++++++++++----
2 files changed, 57 insertions(+), 13 deletions(-)
diff --git a/resources/lib/youtube_plugin/kodion/constants/const_settings.py b/resources/lib/youtube_plugin/kodion/constants/const_settings.py
index 58ac464a6..1f13d7468 100644
--- a/resources/lib/youtube_plugin/kodion/constants/const_settings.py
+++ b/resources/lib/youtube_plugin/kodion/constants/const_settings.py
@@ -50,5 +50,8 @@
HTTPD_WHITELIST = 'kodion.http.ip.whitelist' # (string)
API_CONFIG_PAGE = 'youtube.api.config.page' # (bool)
+API_KEY = 'youtube.api.key' # (string)
+API_ID = 'youtube.api.id' # (string)
+API_SECRET = 'youtube.api.secret' # (string)
CLIENT_SELECTION = 'youtube.client.selection' # (int)
diff --git a/resources/lib/youtube_plugin/kodion/settings/abstract_settings.py b/resources/lib/youtube_plugin/kodion/settings/abstract_settings.py
index 61e42fa56..b4d975f45 100644
--- a/resources/lib/youtube_plugin/kodion/settings/abstract_settings.py
+++ b/resources/lib/youtube_plugin/kodion/settings/abstract_settings.py
@@ -136,7 +136,7 @@ def verify_ssl(self):
def get_timeout(self):
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)
+ return connect_timeout, read_timout
def allow_dev_keys(self):
return self.get_bool(SETTINGS.ALLOW_DEV_KEYS, False)
@@ -170,30 +170,71 @@ def use_mpd_live_streams(self):
return self.get_int(SETTINGS.LIVE_STREAMS + '.1', 0) == 3
return False
- def httpd_port(self):
- return self.get_int(SETTINGS.HTTPD_PORT, 50152)
+ def httpd_port(self, port=None):
+ default_port = 50152
+
+ if not port:
+ port = self.get_int(SETTINGS.HTTPD_PORT, default_port)
- def httpd_listen(self, default='0.0.0.0', for_request=False):
- ip_address = self.get_string(SETTINGS.HTTPD_LISTEN, default)
try:
- ip_address = ip_address.strip()
- except AttributeError:
- pass
+ port = int(port)
+ except ValueError:
+ return default_port
+ return port
+
+ def httpd_listen(self, for_request=False, ip_address=None):
+ default_address = '0.0.0.0'
+ default_octets = [0, 0, 0, 0,]
+
if not ip_address:
- ip_address = default
- if for_request and ip_address == default:
- ip_address = '127.0.0.1'
- return ip_address
+ ip_address = self.get_string(SETTINGS.HTTPD_LISTEN,
+ default_address)
+
+ try:
+ octets = [octet for octet in map(int, ip_address.split('.'))
+ if 0 <= octet <= 255]
+ if len(octets) != 4:
+ raise ValueError
+ except ValueError:
+ octets = default_octets
+
+ if for_request and octets == default_octets:
+ return '127.0.0.1'
+ return '.'.join(map(str, octets))
def set_httpd_listen(self, value):
return self.set_string(SETTINGS.HTTPD_LISTEN, value)
def httpd_whitelist(self):
- return self.get_string(SETTINGS.HTTPD_WHITELIST, '')
+ allow_list = self.get_string(SETTINGS.HTTPD_WHITELIST, '')
+ allow_list = ''.join(allow_list.split()).split(',')
+ allow_list = [
+ self.httpd_listen(for_request=True, ip_address=ip_address)
+ for ip_address in allow_list
+ ]
+ return allow_list
def api_config_page(self):
return self.get_bool(SETTINGS.API_CONFIG_PAGE, False)
+ def api_id(self, new_id=None):
+ if new_id is not None:
+ self.set_string(SETTINGS.API_ID, new_id)
+ return new_id
+ return self.get_string(SETTINGS.API_ID)
+
+ def api_key(self, new_key=None):
+ if new_key is not None:
+ self.set_string(SETTINGS.API_KEY, new_key)
+ return new_key
+ return self.get_string(SETTINGS.API_KEY)
+
+ def api_secret(self, new_secret=None):
+ if new_secret is not None:
+ self.set_string(SETTINGS.API_SECRET, new_secret)
+ return new_secret
+ return self.get_string(SETTINGS.API_SECRET)
+
def get_location(self):
location = self.get_string(SETTINGS.LOCATION, '').replace(' ', '').strip()
coords = location.split(',')
From c56234d88c7206964a7adf06a59aeaf77c61d175 Mon Sep 17 00:00:00 2001
From: MoojMidge <56883549+MoojMidge@users.noreply.github.com>
Date: Sat, 4 Nov 2023 17:04:29 +1100
Subject: [PATCH 027/141] Fix local resuming not working after d873bbd
- Also enable for non MPEG-DASH videos
- Also respect user choice from Kodi resume prompt
---
.../youtube_plugin/kodion/context/xbmc/xbmc_context.py | 9 +++++++--
resources/lib/youtube_plugin/youtube/helper/yt_play.py | 8 ++++----
2 files changed, 11 insertions(+), 6 deletions(-)
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 7f77c785a..17441ff2d 100644
--- a/resources/lib/youtube_plugin/kodion/context/xbmc/xbmc_context.py
+++ b/resources/lib/youtube_plugin/kodion/context/xbmc/xbmc_context.py
@@ -48,6 +48,8 @@ def __init__(self, path='/', params=None, plugin_name='', plugin_id='', override
sys parameters and re-build our clean uri.
Also we extract the path and parameters - man, that would be so simple with the normal url-parsing routines.
"""
+ num_args = len(sys.argv)
+
# first the path of the uri
if override:
self._uri = sys.argv[0]
@@ -55,18 +57,21 @@ def __init__(self, path='/', params=None, plugin_name='', plugin_id='', override
self._path = unquote(comps.path)
# after that try to get the params
- if len(sys.argv) > 2:
+ if num_args > 2:
params = sys.argv[2][1:]
if params:
self._uri = '?'.join([self._uri, params])
self._params = dict(parse_qsl(params))
+ if num_args > 3 and sys.argv[3].lower() == 'resume:true':
+ self._params['resume'] = True
+
self._ui = None
self._video_playlist = None
self._audio_playlist = None
self._video_player = None
self._audio_player = None
- self._plugin_handle = int(sys.argv[1]) if len(sys.argv) > 1 else None
+ self._plugin_handle = int(sys.argv[1]) if num_args > 1 else -1
self._plugin_id = plugin_id or self._addon.getAddonInfo('id')
self._plugin_name = plugin_name or self._addon.getAddonInfo('name')
self._version = self._addon.getAddonInfo('version')
diff --git a/resources/lib/youtube_plugin/youtube/helper/yt_play.py b/resources/lib/youtube_plugin/youtube/helper/yt_play.py
index eceed96c4..d735d400d 100644
--- a/resources/lib/youtube_plugin/youtube/helper/yt_play.py
+++ b/resources/lib/youtube_plugin/youtube/helper/yt_play.py
@@ -85,7 +85,6 @@ def play_video(provider, context):
use_remote_history = use_history and settings.use_remote_history()
use_play_data = use_history and settings.use_local_history()
-
video_item = utils.update_play_info(provider, context, video_id, video_item, video_stream,
use_play_data=use_play_data)
@@ -93,9 +92,10 @@ def play_video(provider, context):
play_count = 0
playback_stats = video_stream.get('playback_stats')
- if use_remote_history:
- if video_item.get_start_time() and video_item.use_mpd_video():
- seek_time = video_item.get_start_time()
+ if use_play_data:
+ seek = video_item.get_start_time()
+ if seek and context.get_param('resume'):
+ seek_time = start_time
play_count = video_item.get_play_count() if video_item.get_play_count() is not None else '0'
item = to_playback_item(context, video_item)
From 4362bbcdcefa76172db19ace2fd8d08d3ff0d371 Mon Sep 17 00:00:00 2001
From: MoojMidge <56883549+MoojMidge@users.noreply.github.com>
Date: Mon, 6 Nov 2023 23:24:47 +1100
Subject: [PATCH 028/141] Use correct types for context params and play_data
---
.../kodion/abstract_provider.py | 6 +-
.../kodion/context/abstract_context.py | 116 +++++++++++++++---
.../kodion/context/xbmc/xbmc_context.py | 9 +-
.../kodion/items/next_page_item.py | 2 +-
.../youtube_plugin/kodion/items/video_item.py | 4 +-
resources/lib/youtube_plugin/kodion/runner.py | 2 +-
.../kodion/ui/xbmc/xbmc_items.py | 8 +-
.../kodion/utils/playback_history.py | 27 ++--
.../lib/youtube_plugin/kodion/utils/player.py | 4 +-
.../lib/youtube_plugin/youtube/helper/tv.py | 12 +-
.../youtube/helper/url_to_item_converter.py | 2 +-
.../youtube_plugin/youtube/helper/utils.py | 8 +-
.../lib/youtube_plugin/youtube/helper/v3.py | 4 +-
.../youtube_plugin/youtube/helper/yt_play.py | 12 +-
.../youtube/helper/yt_specials.py | 26 ++--
.../youtube_plugin/youtube/helper/yt_video.py | 2 +-
.../lib/youtube_plugin/youtube/provider.py | 79 ++++++------
17 files changed, 208 insertions(+), 115 deletions(-)
diff --git a/resources/lib/youtube_plugin/kodion/abstract_provider.py b/resources/lib/youtube_plugin/kodion/abstract_provider.py
index edd9efc40..8c466e846 100644
--- a/resources/lib/youtube_plugin/kodion/abstract_provider.py
+++ b/resources/lib/youtube_plugin/kodion/abstract_provider.py
@@ -249,7 +249,7 @@ def _internal_search(self, context, re_match):
if not query:
return False
- incognito = str(context.get_param('incognito', False)).lower() == 'true'
+ incognito = context.get_param('incognito', False)
channel_id = context.get_param('channel_id', '')
query = to_utf8(query)
@@ -270,7 +270,7 @@ def _internal_search(self, context, re_match):
return self.on_search(query, context, re_match)
if command == 'query':
- incognito = str(context.get_param('incognito', False)).lower() == 'true'
+ incognito = context.get_param('incognito', False)
channel_id = context.get_param('channel_id', '')
query = params['q']
query = to_unicode(query)
@@ -287,7 +287,7 @@ def _internal_search(self, context, re_match):
context.set_content_type(constants.content_type.FILES)
result = []
- location = str(context.get_param('location', False)).lower() == 'true'
+ location = context.get_param('location', False)
# 'New Search...'
new_search_item = NewSearchItem(context, fanart=self.get_alternative_fanart(context), location=location)
diff --git a/resources/lib/youtube_plugin/kodion/context/abstract_context.py b/resources/lib/youtube_plugin/kodion/context/abstract_context.py
index 321087b66..8d3f56dd0 100644
--- a/resources/lib/youtube_plugin/kodion/context/abstract_context.py
+++ b/resources/lib/youtube_plugin/kodion/context/abstract_context.py
@@ -16,7 +16,6 @@
from ..utils import (
create_path,
create_uri_path,
- to_utf8,
AccessManager,
DataCache,
FavoriteList,
@@ -29,6 +28,63 @@
class AbstractContext(object):
+ _BOOL_PARAMS = {
+ 'ask_for_quality',
+ 'audio_only',
+ 'confirmed',
+ 'enable',
+ 'hide_folders',
+ 'hide_live',
+ 'hide_playlists',
+ 'hide_search',
+ 'incognito',
+ 'location',
+ 'logged_in',
+ 'play',
+ 'prompt_for_subtitles',
+ 'refresh',
+ 'refresh_container'
+ 'resume',
+ 'screensaver',
+ 'strm',
+ }
+ _INT_PARAMS = {
+ 'live',
+ 'offset',
+ 'page',
+ }
+ _FLOAT_PARAMS = {
+ 'seek',
+ }
+ _LIST_PARAMS = {
+ 'channel_ids',
+ 'playlist_ids',
+ }
+ _STRING_PARAMS = {
+ 'api_key',
+ 'action', # deprecated
+ 'addon_id',
+ 'channel_id',
+ 'channel_name',
+ 'client_id',
+ 'client_secret',
+ 'event_type',
+ 'item',
+ 'next_page_token',
+ 'page_token',
+ 'parent_id',
+ 'playlist', # deprecated
+ 'playlist_id',
+ 'playlist_name',
+ 'q',
+ 'rating',
+ 'search_type',
+ 'subscription_id',
+ 'videoid', # deprecated
+ 'video_id',
+ 'uri',
+ }
+
def __init__(self, path='/', params=None, plugin_name='', plugin_id=''):
if not params:
params = {}
@@ -55,6 +111,7 @@ def __init__(self, path='/', params=None, plugin_name='', plugin_id=''):
self._view_mode = None
# create valid uri
+ self.parse_params()
self._uri = self.create_uri(self._path, self._params)
def format_date_short(self, date_obj):
@@ -90,7 +147,7 @@ def get_data_cache(self):
if max_cache_size_mb <= 0:
max_cache_size_mb = 5
else:
- max_cache_size_mb = max_cache_size_mb / 2.0
+ max_cache_size_mb /= 2.0
self._data_cache = DataCache(os.path.join(self.get_cache_path(), 'data_cache'),
max_file_size_mb=max_cache_size_mb)
return self._data_cache
@@ -101,7 +158,7 @@ def get_function_cache(self):
if max_cache_size_mb <= 0:
max_cache_size_mb = 5
else:
- max_cache_size_mb = max_cache_size_mb / 2.0
+ max_cache_size_mb /= 2.0
self._function_cache = FunctionCache(os.path.join(self.get_cache_path(), 'cache'),
max_file_size_mb=max_cache_size_mb)
return self._function_cache
@@ -160,17 +217,7 @@ def create_uri(self, path='/', params=None):
uri = "%s://%s/" % ('plugin', str(self._plugin_id))
if params:
- # make a copy of the map
- uri_params = {}
- uri_params.update(params)
-
- # encode in utf-8
- for param in uri_params:
- if isinstance(params[param], int):
- params[param] = str(params[param])
-
- uri_params[param] = to_utf8(params[param])
- uri = '?'.join([uri, urlencode(uri_params)])
+ uri = '?'.join([uri, urlencode(params, encoding='utf-8')])
return uri
@@ -184,10 +231,47 @@ def get_params(self):
return self._params
def get_param(self, name, default=None):
- return self.get_params().get(name, default)
+ return self._params.get(name, default)
+
+ def parse_params(self, params=None):
+ if not params:
+ params = self._params
+ to_delete = []
+
+ for param, value in params.items():
+ try:
+ if param in self._BOOL_PARAMS:
+ parsed_value = str(value).lower() in ('true', '1')
+ elif param in self._INT_PARAMS:
+ parsed_value = int(value)
+ elif param in self._FLOAT_PARAMS:
+ parsed_value = float(value)
+ elif param in self._LIST_PARAMS:
+ parsed_value = [
+ val for val in value.split(',') if val
+ ]
+ elif param in self._STRING_PARAMS:
+ parsed_value = str(value)
+ else:
+ self.log_debug('Unknown parameter - |{0}: {1}|'.format(
+ param, value
+ ))
+ to_delete.append(param)
+ continue
+ except (TypeError, ValueError):
+ self.log_error('Invalid parameter value - |{0}: {1}|'.format(
+ param, value
+ ))
+ to_delete.append(param)
+ continue
+
+ self._params[param] = parsed_value
+
+ for param in to_delete:
+ del params[param]
def set_param(self, name, value):
- self._params[name] = value
+ self.parse_params({name: value})
def get_data_path(self):
"""
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 17441ff2d..e5bdd7ac4 100644
--- a/resources/lib/youtube_plugin/kodion/context/xbmc/xbmc_context.py
+++ b/resources/lib/youtube_plugin/kodion/context/xbmc/xbmc_context.py
@@ -53,15 +53,15 @@ def __init__(self, path='/', params=None, plugin_name='', plugin_id='', override
# first the path of the uri
if override:
self._uri = sys.argv[0]
- comps = urlparse(self._uri)
- self._path = unquote(comps.path)
+ parsed_url = urlparse(self._uri)
+ self._path = unquote(parsed_url.path)
# after that try to get the params
if num_args > 2:
params = sys.argv[2][1:]
if params:
self._uri = '?'.join([self._uri, params])
- self._params = dict(parse_qsl(params))
+ self.parse_params(dict(parse_qsl(params)))
if num_args > 3 and sys.argv[3].lower() == 'resume:true':
self._params['resume'] = True
@@ -332,7 +332,8 @@ def inputstream_adaptive_capabilities(self, capability=None):
version = self._ISA_CAPABILITIES.get(capability)
return version is True or version and isa_loose_version >= utils.loose_version(version)
- def inputstream_adaptive_auto_stream_selection(self):
+ @staticmethod
+ def inputstream_adaptive_auto_stream_selection():
try:
return xbmcaddon.Addon('inputstream.adaptive').getSetting('STREAMSELECTION') == '0'
except RuntimeError:
diff --git a/resources/lib/youtube_plugin/kodion/items/next_page_item.py b/resources/lib/youtube_plugin/kodion/items/next_page_item.py
index 4aa83a91b..364baa36c 100644
--- a/resources/lib/youtube_plugin/kodion/items/next_page_item.py
+++ b/resources/lib/youtube_plugin/kodion/items/next_page_item.py
@@ -16,7 +16,7 @@ class NextPageItem(DirectoryItem):
def __init__(self, context, current_page=1, image=None, fanart=None):
new_params = {}
new_params.update(context.get_params())
- new_params['page'] = str(current_page + 1)
+ new_params['page'] = current_page + 1
name = context.localize(constants.localize.NEXT_PAGE, 'Next Page')
if name.find('%d') != -1:
name %= current_page + 1
diff --git a/resources/lib/youtube_plugin/kodion/items/video_item.py b/resources/lib/youtube_plugin/kodion/items/video_item.py
index 595bf90f6..65e3579ef 100644
--- a/resources/lib/youtube_plugin/kodion/items/video_item.py
+++ b/resources/lib/youtube_plugin/kodion/items/video_item.py
@@ -286,13 +286,13 @@ def get_last_played(self):
return self._last_played
def set_start_percent(self, start_percent):
- self._start_percent = start_percent or ''
+ self._start_percent = start_percent or 0
def get_start_percent(self):
return self._start_percent
def set_start_time(self, start_time):
- self._start_time = start_time or ''
+ self._start_time = start_time or 0
def get_start_time(self):
return self._start_time
diff --git a/resources/lib/youtube_plugin/kodion/runner.py b/resources/lib/youtube_plugin/kodion/runner.py
index 19067c112..63ae7b367 100644
--- a/resources/lib/youtube_plugin/kodion/runner.py
+++ b/resources/lib/youtube_plugin/kodion/runner.py
@@ -55,7 +55,7 @@ def run(provider, context=None):
context.log_notice('Running: %s (%s) on %s with %s\n\tPath: %s\n\tParams: %s' %
(name, addon_version, version, python_version,
- context.get_path(), str(context_params)))
+ context.get_path(), context_params))
__RUNNER__.run(provider, context)
provider.tear_down(context)
diff --git a/resources/lib/youtube_plugin/kodion/ui/xbmc/xbmc_items.py b/resources/lib/youtube_plugin/kodion/ui/xbmc/xbmc_items.py
index a23ce255b..2cbbe6324 100644
--- a/resources/lib/youtube_plugin/kodion/ui/xbmc/xbmc_items.py
+++ b/resources/lib/youtube_plugin/kodion/ui/xbmc/xbmc_items.py
@@ -109,10 +109,10 @@ def to_play_item(context, play_item):
if not is_strm:
if play_item.get_play_count() == 0:
if play_item.get_start_percent():
- list_item.setProperty('StartPercent', play_item.get_start_percent())
+ list_item.setProperty('StartPercent', str(play_item.get_start_percent()))
if play_item.get_start_time():
- list_item.setProperty('StartOffset', play_item.get_start_time())
+ list_item.setProperty('StartOffset', str(play_item.get_start_time()))
if play_item.subtitles:
list_item.setSubtitles(play_item.subtitles)
@@ -165,10 +165,10 @@ def to_video_item(context, video_item):
if video_item.get_play_count() == 0:
if video_item.get_start_percent():
- item.setProperty('StartPercent', video_item.get_start_percent())
+ item.setProperty('StartPercent', str(video_item.get_start_percent()))
if video_item.get_start_time():
- item.setProperty('StartOffset', video_item.get_start_time())
+ item.setProperty('StartOffset', str(video_item.get_start_time()))
# This should work for all versions of XBMC/KODI.
if 'duration' in _info_labels:
diff --git a/resources/lib/youtube_plugin/kodion/utils/playback_history.py b/resources/lib/youtube_plugin/kodion/utils/playback_history.py
index db54100bf..e61779755 100644
--- a/resources/lib/youtube_plugin/kodion/utils/playback_history.py
+++ b/resources/lib/youtube_plugin/kodion/utils/playback_history.py
@@ -26,30 +26,37 @@ def _decode(obj):
return pickle.loads(obj)
self._open()
- placeholders = ','.join(['?' for _ in keys])
- keys = [str(item) for item in keys]
+ placeholders = ','.join(['?'] * len(keys))
query = 'SELECT * FROM %s WHERE key IN (%s)' % (self._table_name, placeholders)
- query_result = self._execute(False, query, keys)
+ query_result = self._execute(False, query, list(keys))
+ data_keys = ['play_count', 'total_time' 'played_time', 'played_percent']
result = {}
if query_result:
for item in query_result:
values = _decode(item[2]).split(',')
- result[str(item[0])] = {'play_count': values[0], 'total_time': values[1],
- 'played_time': values[2], 'played_percent': values[3],
- 'last_played': item[1]}
+ result[item[0]] = {
+ 'play_count': int(values[0]),
+ 'total_time': float(values[1]),
+ 'played_time': float(values[2]),
+ 'played_percent': int(values[3]),
+ 'last_played': item[1],
+ }
self._close()
return result
def get_item(self, key):
- key = str(key)
query_result = self._get(key)
result = {}
if query_result:
values = query_result[0].split(',')
- result[key] = {'play_count': values[0], 'total_time': values[1],
- 'played_time': values[2], 'played_percent': values[3],
- 'last_played': query_result[1]}
+ result[key] = {
+ 'play_count': int(values[0]),
+ 'total_time': float(values[1]),
+ 'played_time': float(values[2]),
+ 'played_percent': int(values[3]),
+ 'last_played': query_result[1],
+ }
return result
def clear(self):
diff --git a/resources/lib/youtube_plugin/kodion/utils/player.py b/resources/lib/youtube_plugin/kodion/utils/player.py
index 0bdccf1c1..0374d98da 100644
--- a/resources/lib/youtube_plugin/kodion/utils/player.py
+++ b/resources/lib/youtube_plugin/kodion/utils/player.py
@@ -71,8 +71,6 @@ def run(self):
if playback_stats is None:
playback_stats = {}
- play_count = str(play_count)
-
played_time = -1.0
state = 'playing'
@@ -266,7 +264,7 @@ def run(self):
is_logged_in = self.provider.is_logged_in()
if self.percent_complete >= settings.get_play_count_min_percent():
- play_count = '1'
+ play_count += 1
self.current_time = 0.0
if is_logged_in and report_url and use_remote_history:
client.update_watch_history(self.video_id, report_url
diff --git a/resources/lib/youtube_plugin/youtube/helper/tv.py b/resources/lib/youtube_plugin/youtube/helper/tv.py
index 94a9795af..a78b39dde 100644
--- a/resources/lib/youtube_plugin/youtube/helper/tv.py
+++ b/resources/lib/youtube_plugin/youtube/helper/tv.py
@@ -16,7 +16,7 @@ def my_subscriptions_to_items(provider, context, json_data, do_filter=False):
result = []
video_id_dict = {}
- incognito = str(context.get_param('incognito', False)).lower() == 'true'
+ incognito = context.get_param('incognito', False)
filter_list = []
black_list = False
@@ -63,7 +63,7 @@ def my_subscriptions_to_items(provider, context, json_data, do_filter=False):
new_context = context.clone(new_params=new_params)
- current_page = int(new_context.get_param('page', 1))
+ current_page = new_context.get_param('page', 1)
next_page_item = NextPageItem(new_context, current_page, fanart=provider.get_fanart(new_context))
result.append(next_page_item)
@@ -74,7 +74,7 @@ def tv_videos_to_items(provider, context, json_data):
result = []
video_id_dict = {}
- incognito = str(context.get_param('incognito', False)).lower() == 'true'
+ incognito = context.get_param('incognito', False)
items = json_data.get('items', [])
for item in items:
@@ -109,7 +109,7 @@ def tv_videos_to_items(provider, context, json_data):
new_context = context.clone(new_params=new_params)
- current_page = int(new_context.get_param('page', 1))
+ current_page = new_context.get_param('page', 1)
next_page_item = NextPageItem(new_context, current_page, fanart=provider.get_fanart(new_context))
result.append(next_page_item)
@@ -120,7 +120,7 @@ def saved_playlists_to_items(provider, context, json_data):
result = []
playlist_id_dict = {}
- incognito = str(context.get_param('incognito', False)).lower() == 'true'
+ incognito = context.get_param('incognito', False)
thumb_size = context.get_settings().use_thumbnail_size()
items = json_data.get('items', [])
@@ -160,7 +160,7 @@ def saved_playlists_to_items(provider, context, json_data):
new_context = context.clone(new_params=new_params)
- current_page = int(new_context.get_param('page', 1))
+ current_page = new_context.get_param('page', 1)
next_page_item = NextPageItem(new_context, current_page, fanart=provider.get_fanart(new_context))
result.append(next_page_item)
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 a8e3c2d69..068d853b0 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
@@ -121,7 +121,7 @@ def get_items(self, provider, context, title_required=True):
return result
def get_video_items(self, provider, context, title_required=True):
- incognito = str(context.get_param('incognito', False)).lower() == 'true'
+ incognito = context.get_param('incognito', False)
use_play_data = not incognito
if not self._video_items:
diff --git a/resources/lib/youtube_plugin/youtube/helper/utils.py b/resources/lib/youtube_plugin/youtube/helper/utils.py
index 78392b4af..70f6e688b 100644
--- a/resources/lib/youtube_plugin/youtube/helper/utils.py
+++ b/resources/lib/youtube_plugin/youtube/helper/utils.py
@@ -287,7 +287,7 @@ def update_video_infos(provider, context, video_id_dict,
# duration
if not video_item.live and play_data and 'total_time' in play_data:
- duration = float(play_data['total_time'] or 0)
+ duration = play_data['total_time']
else:
duration = yt_item.get('contentDetails', {}).get('duration')
if duration:
@@ -448,12 +448,12 @@ def update_video_infos(provider, context, video_id_dict,
yt_context_menu.append_subscribe_to_channel(context_menu, provider, context, channel_id, channel_name)
if not video_item.live and play_data:
- if play_data.get('play_count') is None or int(play_data.get('play_count')) == 0:
+ if not play_data.get('play_count'):
yt_context_menu.append_mark_watched(context_menu, provider, context, video_id)
else:
yt_context_menu.append_mark_unwatched(context_menu, provider, context, video_id)
- if int(play_data.get('played_percent', '0')) > 0 or float(play_data.get('played_time', '0.0')) > 0.0:
+ if play_data.get('played_percent', 0) > 0 or play_data.get('played_time', 0) > 0:
yt_context_menu.append_reset_resume_point(context_menu, provider, context, video_id)
# more...
@@ -557,7 +557,7 @@ def update_play_info(provider, context, video_id, video_item, video_stream, use_
# duration
if not video_item.live and play_data and 'total_time' in play_data:
- duration = float(play_data['total_time'] or 0)
+ duration = play_data['total_time']
else:
duration = yt_item.get('contentDetails', {}).get('duration')
if duration:
diff --git a/resources/lib/youtube_plugin/youtube/helper/v3.py b/resources/lib/youtube_plugin/youtube/helper/v3.py
index 186a60c33..3a5c1a7b3 100644
--- a/resources/lib/youtube_plugin/youtube/helper/v3.py
+++ b/resources/lib/youtube_plugin/youtube/helper/v3.py
@@ -31,7 +31,7 @@ def _process_list_response(provider, context, json_data):
context.log_warning('List of search result is empty')
return result
- incognito = str(context.get_param('incognito', False)).lower() == 'true'
+ incognito = context.get_param('incognito', False)
addon_id = context.get_param('addon_id', '')
for yt_item in yt_items:
@@ -325,7 +325,7 @@ def response_to_items(provider, context, json_data, sort=None, reverse_sort=Fals
new_context = context.clone(new_params=new_params)
- current_page = int(new_context.get_param('page', 1))
+ current_page = new_context.get_param('page', 1)
next_page_item = items.NextPageItem(new_context, current_page, fanart=provider.get_fanart(new_context))
result.append(next_page_item)
diff --git a/resources/lib/youtube_plugin/youtube/helper/yt_play.py b/resources/lib/youtube_plugin/youtube/helper/yt_play.py
index d735d400d..a2c126327 100644
--- a/resources/lib/youtube_plugin/youtube/helper/yt_play.py
+++ b/resources/lib/youtube_plugin/youtube/helper/yt_play.py
@@ -36,7 +36,7 @@ def play_video(provider, context):
context.get_ui().clear_home_window_property('ask_for_quality')
screensaver = False
- if context.get_param('screensaver', None) and str(context.get_param('screensaver')).lower() == 'true':
+ if context.get_param('screensaver'):
ask_for_quality = False
screensaver = True
@@ -80,7 +80,7 @@ def play_video(provider, context):
title = metadata.get('video', {}).get('title', '')
video_item = VideoItem(title, video_stream['url'])
- incognito = str(context.get_param('incognito', False)).lower() == 'true'
+ incognito = context.get_param('incognito', False)
use_history = not is_live and not screensaver and not incognito
use_remote_history = use_history and settings.use_remote_history()
use_play_data = use_history and settings.use_local_history()
@@ -96,7 +96,7 @@ def play_video(provider, context):
seek = video_item.get_start_time()
if seek and context.get_param('resume'):
seek_time = start_time
- play_count = video_item.get_play_count() if video_item.get_play_count() is not None else '0'
+ play_count = video_item.get_play_count() or 0
item = to_playback_item(context, video_item)
item.setPath(video_item.get_uri())
@@ -223,10 +223,10 @@ def _load_videos(_page_token='', _progress_dialog=None):
if progress_dialog:
progress_dialog.close()
- if (context.get_param('play', '') == '1') and (context.get_handle() == -1):
+ if context.get_param('play') and context.get_handle() == -1:
player.play(playlist_index=playlist_position)
return False
- if context.get_param('play', '') == '1':
+ if context.get_param('play'):
return videos[playlist_position]
return True
@@ -234,7 +234,7 @@ def _load_videos(_page_token='', _progress_dialog=None):
def play_channel_live(provider, context):
channel_id = context.get_param('channel_id')
- index = int(context.get_param('live')) - 1
+ index = context.get_param('live') - 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)
diff --git a/resources/lib/youtube_plugin/youtube/helper/yt_specials.py b/resources/lib/youtube_plugin/youtube/helper/yt_specials.py
index 8a9c0e114..5cd5935da 100644
--- a/resources/lib/youtube_plugin/youtube/helper/yt_specials.py
+++ b/resources/lib/youtube_plugin/youtube/helper/yt_specials.py
@@ -127,7 +127,7 @@ def _sort(x):
# TODO: cache result
page_token = context.get_param('page_token', '')
- location = str(context.get_param('location', False)).lower() == 'true'
+ location = context.get_param('location', False)
json_data = provider.get_client(context).get_live_events(event_type=event_type, page_token=page_token, location=location)
if not v3.handle_error(provider, context, json_data):
@@ -138,7 +138,7 @@ def _sort(x):
def _process_description_links(provider, context):
- incognito = str(context.get_param('incognito', False)).lower() == 'true'
+ incognito = context.get_param('incognito', False)
addon_id = context.get_param('addon_id', '')
def _extract_urls(_video_id):
@@ -250,17 +250,13 @@ def _display_playlists(_playlist_ids):
if video_id:
return _extract_urls(video_id)
- channel_ids = context.get_param('channel_ids', '')
+ channel_ids = context.get_param('channel_ids', [])
if channel_ids:
- channel_ids = channel_ids.split(',')
- if channel_ids:
- return _display_channels(channel_ids)
+ return _display_channels(channel_ids)
- playlist_ids = context.get_param('playlist_ids', '')
+ playlist_ids = context.get_param('playlist_ids', [])
if playlist_ids:
- playlist_ids = playlist_ids.split(',')
- if playlist_ids:
- return _display_playlists(playlist_ids)
+ return _display_playlists(playlist_ids)
context.log_error('Missing video_id or playlist_ids for description links')
@@ -272,7 +268,7 @@ def _process_saved_playlists_tv(provider, context):
result = []
next_page_token = context.get_param('next_page_token', '')
- offset = int(context.get_param('offset', 0))
+ offset = context.get_param('offset', 0)
json_data = provider.get_client(context).get_saved_playlists(page_token=next_page_token, offset=offset)
result.extend(tv.saved_playlists_to_items(provider, context, json_data))
@@ -284,7 +280,7 @@ def _process_watch_history_tv(provider, context):
result = []
next_page_token = context.get_param('next_page_token', '')
- offset = int(context.get_param('offset', 0))
+ offset = context.get_param('offset', 0)
json_data = provider.get_client(context).get_watch_history(page_token=next_page_token, offset=offset)
result.extend(tv.tv_videos_to_items(provider, context, json_data))
@@ -296,7 +292,7 @@ def _process_purchases_tv(provider, context):
result = []
next_page_token = context.get_param('next_page_token', '')
- offset = int(context.get_param('offset', 0))
+ offset = context.get_param('offset', 0)
json_data = provider.get_client(context).get_purchases(page_token=next_page_token, offset=offset)
result.extend(tv.tv_videos_to_items(provider, context, json_data))
@@ -308,7 +304,7 @@ def _process_new_uploaded_videos_tv(provider, context):
result = []
next_page_token = context.get_param('next_page_token', '')
- offset = int(context.get_param('offset', 0))
+ offset = context.get_param('offset', 0)
json_data = provider.get_client(context).get_my_subscriptions(page_token=next_page_token, offset=offset)
result.extend(tv.my_subscriptions_to_items(provider, context, json_data))
@@ -320,7 +316,7 @@ def _process_new_uploaded_videos_tv_filtered(provider, context):
result = []
next_page_token = context.get_param('next_page_token', '')
- offset = int(context.get_param('offset', 0))
+ offset = context.get_param('offset', 0)
json_data = provider.get_client(context).get_my_subscriptions(page_token=next_page_token, offset=offset)
result.extend(tv.my_subscriptions_to_items(provider, context, json_data, do_filter=True))
diff --git a/resources/lib/youtube_plugin/youtube/helper/yt_video.py b/resources/lib/youtube_plugin/youtube/helper/yt_video.py
index 16cece658..81728600e 100644
--- a/resources/lib/youtube_plugin/youtube/helper/yt_video.py
+++ b/resources/lib/youtube_plugin/youtube/helper/yt_video.py
@@ -67,7 +67,7 @@ def _process_rate_video(provider, context, re_match):
elif response.get('status_code') == 204:
# this will be set if we are in the 'Liked Video' playlist
- if context.get_param('refresh_container', '0') == '1':
+ if context.get_param('refresh_container'):
context.get_ui().refresh_container()
if result == 'none':
diff --git a/resources/lib/youtube_plugin/youtube/provider.py b/resources/lib/youtube_plugin/youtube/provider.py
index 09d47d879..21367aed2 100644
--- a/resources/lib/youtube_plugin/youtube/provider.py
+++ b/resources/lib/youtube_plugin/youtube/provider.py
@@ -496,7 +496,7 @@ def _on_channel_playlists(self, context, re_match):
resource_manager = self.get_resource_manager(context)
item_params = {}
- incognito = str(context.get_param('incognito', False)).lower() == 'true'
+ incognito = context.get_param('incognito', False)
addon_id = context.get_param('addon_id', '')
if incognito:
item_params.update({'incognito': incognito})
@@ -595,9 +595,9 @@ def _on_channel(self, context, re_match):
return False
channel_fanarts = resource_manager.get_fanarts([channel_id])
- page = int(context.get_param('page', 1))
+ page = context.get_param('page', 1)
page_token = context.get_param('page_token', '')
- incognito = str(context.get_param('incognito', False)).lower() == 'true'
+ incognito = context.get_param('incognito', False)
addon_id = context.get_param('addon_id', '')
item_params = {}
if incognito:
@@ -605,12 +605,12 @@ def _on_channel(self, context, re_match):
if addon_id:
item_params.update({'addon_id': addon_id})
- hide_folders = str(context.get_param('hide_folders', False)).lower() == 'true'
+ hide_folders = context.get_param('hide_folders', False)
if page == 1 and not hide_folders:
- hide_playlists = str(context.get_param('hide_playlists', False)).lower() == 'true'
- hide_search = str(context.get_param('hide_search', False)).lower() == 'true'
- hide_live = str(context.get_param('hide_live', False)).lower() == 'true'
+ hide_playlists = context.get_param('hide_playlists', False)
+ hide_search = context.get_param('hide_search', False)
+ hide_live = context.get_param('hide_live', False)
if not hide_playlists:
playlists_item = DirectoryItem(context.get_ui().bold(context.localize(self.LOCAL_MAP['youtube.playlists'])),
@@ -729,7 +729,7 @@ def on_play(self, context, re_match):
context.get_ui().clear_home_window_property('ask_for_quality')
if 'prompt_for_subtitles' in params:
- prompt_subtitles = params['prompt_for_subtitles'] == '1'
+ prompt_subtitles = params['prompt_for_subtitles']
del params['prompt_for_subtitles']
if prompt_subtitles and 'video_id' in params and 'playlist_id' not in params:
# redirect to builtin after setting home window property, so playback url matches playable listitems
@@ -738,7 +738,7 @@ def on_play(self, context, re_match):
redirect = True
elif 'audio_only' in params:
- audio_only = params['audio_only'] == '1'
+ audio_only = params['audio_only']
del params['audio_only']
if audio_only and 'video_id' in params and 'playlist_id' not in params:
# redirect to builtin after setting home window property, so playback url matches playable listitems
@@ -747,7 +747,7 @@ def on_play(self, context, re_match):
redirect = True
elif 'ask_for_quality' in params:
- ask_for_quality = params['ask_for_quality'] == '1'
+ ask_for_quality = params['ask_for_quality']
del params['ask_for_quality']
if ask_for_quality and 'video_id' in params and 'playlist_id' not in params:
# redirect to builtin after setting home window property, so playback url matches playable listitems
@@ -773,7 +773,7 @@ def on_play(self, context, re_match):
return yt_play.play_video(self, context)
if 'playlist_id' in params:
return yt_play.play_playlist(self, context)
- if 'channel_id' in params and 'live' in params and int(params['live']) > 0:
+ if 'channel_id' in params and 'live' in params and params['live'] > 0:
return yt_play.play_channel_live(self, context)
return False
@@ -822,7 +822,8 @@ def _on_yt_clear_history(self, context, re_match):
@RegisterProviderPath('^/users/(?P[^/]+)/$')
def _on_users(self, context, re_match):
action = re_match.group('action')
- refresh = context.get_param('refresh', 'true').lower() == 'true'
+ refresh = context.get_param('refresh')
+
access_manager = context.get_access_manager()
ui = context.get_ui()
@@ -972,7 +973,7 @@ def switch_to_user(_user):
@RegisterProviderPath('^/sign/(?P[^/]+)/$')
def _on_sign(self, context, re_match):
- sign_out_confirmed = context.get_param('confirmed', '').lower() == 'true'
+ sign_out_confirmed = context.get_param('confirmed')
mode = re_match.group('mode')
if (mode == 'in') and context.get_access_manager().has_refresh_token():
yt_login.process('out', self, context, sign_out_refresh=False)
@@ -1018,9 +1019,9 @@ def on_search(self, search_text, context, re_match):
channel_id = context.get_param('channel_id', '')
event_type = context.get_param('event_type', '')
- hide_folders = str(context.get_param('hide_folders', False)).lower() == 'true'
- location = str(context.get_param('location', False)).lower() == 'true'
- page = int(context.get_param('page', 1))
+ hide_folders = context.get_param('hide_folders', False)
+ location = context.get_param('location', False)
+ page = context.get_param('page', 1)
page_token = context.get_param('page_token', '')
search_type = context.get_param('search_type', 'video')
@@ -1256,7 +1257,7 @@ def api_key_update(self, context, re_match):
client_id = params.get('client_id')
client_secret = params.get('client_secret')
api_key = params.get('api_key')
- enable = params.get('enable', '').lower() == 'true'
+ enable = params.get('enable')
updated_list = []
log_list = []
@@ -1321,28 +1322,34 @@ def on_playback_history(self, context, re_match):
if not video_id or not action:
return True
playback_history = context.get_playback_history()
- items = playback_history.get_items([video_id])
- if not items or not items.get(video_id):
- item_dict = {'play_count': '0', 'total_time': '0.0',
- 'played_time': '0.0', 'played_percent': '0'}
- else:
- item_dict = items.get(video_id)
+ play_data = playback_history.get_items([video_id]).get(video_id)
+ if not play_data:
+ play_data = {
+ 'play_count': 0,
+ 'total_time': 0,
+ 'played_time': 0,
+ 'played_percent': 0
+ }
+
if action == 'mark_unwatched':
- if int(item_dict.get('play_count', 0)) > 0:
- item_dict['play_count'] = '0'
- item_dict['played_time'] = '0.0'
- item_dict['played_percent'] = '0'
+ if play_data.get('play_count', 0) > 0:
+ play_data['play_count'] = 0
+ play_data['played_time'] = 0
+ play_data['played_percent'] = 0
+
elif action == 'mark_watched':
- if int(item_dict.get('play_count', 0)) == 0:
- item_dict['play_count'] = '1'
+ if not play_data.get('play_count', 0):
+ play_data['play_count'] = 1
+
elif action == 'reset_resume':
- item_dict['played_time'] = '0.0'
- item_dict['played_percent'] = '0'
- item_dict['play_count'] = item_dict.get('play_count', '0')
- item_dict['total_time'] = item_dict.get('total_time', '0.0')
- item_dict['played_time'] = item_dict.get('played_time', '0.0')
- item_dict['played_percent'] = item_dict.get('played_percent', '0')
- playback_history.update(video_id, item_dict['play_count'], item_dict['total_time'], item_dict['played_time'], item_dict['played_percent'])
+ play_data['played_time'] = 0
+ play_data['played_percent'] = 0
+
+ playback_history.update(video_id,
+ play_data.get('play_count', 0),
+ play_data.get('total_time', 0),
+ play_data.get('played_time', 0),
+ play_data.get('played_percent', 0))
context.get_ui().refresh_container()
return True
From 1e2e5f074115024f7d91f83eab8a0fd260d20c85 Mon Sep 17 00:00:00 2001
From: MoojMidge <56883549+MoojMidge@users.noreply.github.com>
Date: Mon, 6 Nov 2023 23:37:21 +1100
Subject: [PATCH 029/141] Add lisitems to directory as complete listing
---
.../kodion/plugin/xbmc/xbmc_runner.py | 100 ++++++++++--------
1 file changed, 53 insertions(+), 47 deletions(-)
diff --git a/resources/lib/youtube_plugin/kodion/plugin/xbmc/xbmc_runner.py b/resources/lib/youtube_plugin/kodion/plugin/xbmc/xbmc_runner.py
index b44a8fed8..dddeca507 100644
--- a/resources/lib/youtube_plugin/kodion/plugin/xbmc/xbmc_runner.py
+++ b/resources/lib/youtube_plugin/kodion/plugin/xbmc/xbmc_runner.py
@@ -35,7 +35,7 @@ def run(self, provider, context):
context.log_error(ex.__str__())
xbmcgui.Dialog().ok("Exception in ContentProvider", ex.__str__())
xbmcplugin.endOfDirectory(self.handle, succeeded=False)
- return
+ return False
self.settings = context.get_settings()
@@ -45,35 +45,45 @@ def run(self, provider, context):
if isinstance(result, bool) and not result:
xbmcplugin.endOfDirectory(self.handle, succeeded=False)
- elif isinstance(result, (VideoItem, AudioItem, UriItem)):
- self._set_resolved_url(context, result)
- elif isinstance(result, DirectoryItem):
- self._add_directory(context, result)
+ return False
+
+ if isinstance(result, (VideoItem, AudioItem, UriItem)):
+ return self._set_resolved_url(context, result)
+
+ show_fanart = self.settings.show_fanart()
+
+ if isinstance(result, DirectoryItem):
+ item_count = 1
+ items = [self._add_directory(result, show_fanart)]
elif isinstance(result, list):
item_count = len(result)
- for item in result:
- if isinstance(item, DirectoryItem):
- self._add_directory(context, item, item_count)
- elif isinstance(item, VideoItem):
- self._add_video(context, item, item_count)
- elif isinstance(item, AudioItem):
- self._add_audio(context, item, item_count)
- elif isinstance(item, ImageItem):
- self._add_image(context, item, item_count)
-
- xbmcplugin.endOfDirectory(
- self.handle, succeeded=True,
- updateListing=options.get(AbstractProvider.RESULT_UPDATE_LISTING, False),
- cacheToDisc=options.get(AbstractProvider.RESULT_CACHE_TO_DISC, True))
+ items = [
+ self._add_directory(item, show_fanart) if isinstance(item, DirectoryItem)
+ else self._add_video(context, item) if isinstance(item, VideoItem)
+ else self._add_audio(context, item) if isinstance(item, AudioItem)
+ else self._add_image(item, show_fanart) if isinstance(item, ImageItem)
+ else None
+ for item in result
+ ]
else:
# handle exception
- pass
+ return False
+
+ succeeded = xbmcplugin.addDirectoryItems(
+ self.handle, items, item_count
+ )
+ xbmcplugin.endOfDirectory(
+ self.handle,
+ succeeded=succeeded,
+ updateListing=options.get(AbstractProvider.RESULT_UPDATE_LISTING, False),
+ cacheToDisc=options.get(AbstractProvider.RESULT_CACHE_TO_DISC, True)
+ )
+ return succeeded
def _set_resolved_url(self, context, base_item, succeeded=True):
item = xbmc_items.to_playback_item(context, base_item)
item.setPath(base_item.get_uri())
xbmcplugin.setResolvedUrl(self.handle, succeeded=succeeded, listitem=item)
-
"""
# just to be sure :)
if not isLiveStream:
@@ -85,8 +95,10 @@ def _set_resolved_url(self, context, base_item, succeeded=True):
break
tries-=1
"""
+ return succeeded
- def _add_directory(self, context, directory_item, item_count=0):
+ @staticmethod
+ def _add_directory(directory_item, show_fanart=False):
art = {'icon': 'DefaultFolder.png',
'thumb': directory_item.get_image()}
@@ -95,9 +107,10 @@ def _add_directory(self, context, directory_item, item_count=0):
info_tag = xbmc_items.ListItemInfoTag(item, tag_type='video')
# only set fanart is enabled
- if directory_item.get_fanart() and self.settings.show_fanart():
- art['fanart'] = directory_item.get_fanart()
-
+ if show_fanart:
+ fanart = directory_item.get_fanart()
+ if fanart:
+ art['fanart'] = fanart
item.setArt(art)
@@ -119,28 +132,26 @@ def _add_directory(self, context, directory_item, item_count=0):
if directory_item.get_channel_subscription_id(): # make channel_subscription_id property available for keymapping
item.setProperty('channel_subscription_id', directory_item.get_channel_subscription_id())
- xbmcplugin.addDirectoryItem(handle=self.handle,
- url=directory_item.get_uri(),
- listitem=item,
- isFolder=is_folder,
- totalItems=item_count)
+ return directory_item.get_uri(), item, is_folder
- def _add_video(self, context, video_item, item_count=0):
+ @staticmethod
+ def _add_video(context, video_item):
item = xbmc_items.to_video_item(context, video_item)
item.setPath(video_item.get_uri())
- xbmcplugin.addDirectoryItem(handle=self.handle,
- url=video_item.get_uri(),
- listitem=item,
- totalItems=item_count)
+ return video_item.get_uri(), item, False
- def _add_image(self, context, image_item, item_count):
+ @staticmethod
+ def _add_image(image_item, show_fanart=False):
art = {'icon': 'DefaultPicture.png',
'thumb': image_item.get_image()}
item = xbmcgui.ListItem(label=image_item.get_name(), offscreen=True)
- if image_item.get_fanart() and self.settings.show_fanart():
- art['fanart'] = image_item.get_fanart()
+ # only set fanart is enabled
+ if show_fanart:
+ fanart = image_item.get_fanart()
+ if fanart:
+ art['fanart'] = fanart
item.setArt(art)
@@ -150,15 +161,10 @@ def _add_image(self, context, image_item, item_count):
item.setInfo(type='picture', infoLabels=info_labels.create_from_item(image_item))
item.setPath(image_item.get_uri())
- xbmcplugin.addDirectoryItem(handle=self.handle,
- url=image_item.get_uri(),
- listitem=item,
- totalItems=item_count)
+ return image_item.get_uri(), item, False
- def _add_audio(self, context, audio_item, item_count):
+ @staticmethod
+ def _add_audio(context, audio_item):
item = xbmc_items.to_audio_item(context, audio_item)
item.setPath(audio_item.get_uri())
- xbmcplugin.addDirectoryItem(handle=self.handle,
- url=audio_item.get_uri(),
- listitem=item,
- totalItems=item_count)
+ return audio_item.get_uri(), item, False
From e210bb8a944c18afe9ce6ef026dc69d517dc8f01 Mon Sep 17 00:00:00 2001
From: MoojMidge <56883549+MoojMidge@users.noreply.github.com>
Date: Thu, 9 Nov 2023 00:12:48 +1100
Subject: [PATCH 030/141] Update sqlite based modules
- Faster and maybe more rebust
- Deduplicate code in subclasses, move to Storage
- Fix issue where timestamp was stored as datetime string
---
.../kodion/abstract_provider.py | 12 +-
.../kodion/context/abstract_context.py | 2 +-
.../youtube_plugin/kodion/utils/data_cache.py | 88 ++---
.../kodion/utils/favorite_list.py | 17 +-
.../kodion/utils/playback_history.py | 73 ++---
.../kodion/utils/search_history.py | 22 +-
.../youtube_plugin/kodion/utils/storage.py | 301 +++++++++++-------
.../kodion/utils/watch_later_list.py | 20 +-
.../youtube_plugin/youtube/client/youtube.py | 6 +-
.../youtube/helper/resource_manager.py | 6 +-
.../youtube/helper/video_info.py | 6 +-
.../lib/youtube_plugin/youtube/provider.py | 3 +-
12 files changed, 267 insertions(+), 289 deletions(-)
diff --git a/resources/lib/youtube_plugin/kodion/abstract_provider.py b/resources/lib/youtube_plugin/kodion/abstract_provider.py
index 8c466e846..0a21812eb 100644
--- a/resources/lib/youtube_plugin/kodion/abstract_provider.py
+++ b/resources/lib/youtube_plugin/kodion/abstract_provider.py
@@ -159,7 +159,7 @@ def _internal_favorite(context, re_match):
context.get_ui().refresh_container()
return None
if command == 'list':
- directory_items = context.get_favorite_list().list()
+ directory_items = context.get_favorite_list().get_items()
for directory_item in directory_items:
context_menu = [(context.localize(constants.localize.WATCH_LATER_REMOVE),
@@ -186,7 +186,7 @@ def _internal_watch_later(self, context, re_match):
context.get_ui().refresh_container()
return None
if command == 'list':
- video_items = context.get_watch_later_list().list()
+ video_items = context.get_watch_later_list().get_items()
for video_item in video_items:
context_menu = [(context.localize(constants.localize.WATCH_LATER_REMOVE),
@@ -207,6 +207,8 @@ def data_cache(self, context):
self._data_cache = context.get_data_cache()
def _internal_search(self, context, re_match):
+ context.add_sort_method(constants.sort_method.UNSORTED)
+
params = context.get_params()
command = re_match.group('command')
@@ -254,10 +256,10 @@ def _internal_search(self, context, re_match):
query = to_utf8(query)
try:
- self._data_cache.set('search_query', json.dumps({'query': quote(query)}))
+ encoded = json.dumps({'query': quote(query)})
except KeyError:
encoded = json.dumps({'query': quote(query.encode('utf8'))})
- self._data_cache.set('search_query', encoded)
+ self._data_cache.set_item('search_query', encoded)
if not incognito and not channel_id:
try:
@@ -293,7 +295,7 @@ def _internal_search(self, context, re_match):
new_search_item = NewSearchItem(context, fanart=self.get_alternative_fanart(context), location=location)
result.append(new_search_item)
- for search in search_history.list():
+ for search in search_history.get_items():
# little fallback for old history entries
if isinstance(search, DirectoryItem):
search = search.get_name()
diff --git a/resources/lib/youtube_plugin/kodion/context/abstract_context.py b/resources/lib/youtube_plugin/kodion/context/abstract_context.py
index 8d3f56dd0..fc2595f18 100644
--- a/resources/lib/youtube_plugin/kodion/context/abstract_context.py
+++ b/resources/lib/youtube_plugin/kodion/context/abstract_context.py
@@ -167,7 +167,7 @@ def get_search_history(self):
if not self._search_history:
max_search_history_items = self.get_settings().get_int(constants.setting.SEARCH_SIZE, 50)
self._search_history = SearchHistory(os.path.join(self.get_cache_path(), 'search'),
- max_search_history_items)
+ max_item_count=max_search_history_items)
return self._search_history
def get_favorite_list(self):
diff --git a/resources/lib/youtube_plugin/kodion/utils/data_cache.py b/resources/lib/youtube_plugin/kodion/utils/data_cache.py
index 2e6fd89ec..7e986beeb 100644
--- a/resources/lib/youtube_plugin/kodion/utils/data_cache.py
+++ b/resources/lib/youtube_plugin/kodion/utils/data_cache.py
@@ -9,13 +9,9 @@
"""
import json
-import pickle
-import sqlite3
-
-from datetime import datetime, timedelta
+from datetime import datetime
from .storage import Storage
-from .. import logger
class DataCache(Storage):
@@ -33,55 +29,35 @@ def is_empty(self):
return self._is_empty()
def get_items(self, seconds, content_ids):
- def _decode(obj):
- return pickle.loads(obj)
+ query_result = self._get_by_ids(content_ids, process=json.loads)
+ if not query_result:
+ return {}
current_time = datetime.now()
- placeholders = ','.join(['?' for _ in content_ids])
- keys = [str(item) for item in content_ids]
- query = 'SELECT * FROM %s WHERE key IN (%s)' % (self._table_name, placeholders)
-
- self._open()
-
- query_result = self._execute(False, query, keys)
- result = {}
- if query_result:
- for item in query_result:
- cached_time = item[1]
- if cached_time is None:
- logger.log_error('Data Cache [get_items]: cached_time is None while getting {content_id}'.format(
- content_id=item[0]
- ))
- cached_time = current_time
- # this is so stupid, but we have the function 'total_seconds' only starting with python 2.7
- diff_seconds = self.get_seconds_diff(cached_time)
- if diff_seconds <= seconds:
- result[str(item[0])] = json.loads(_decode(item[2]))
-
- self._close()
+ result = {
+ item[0]: item[2]
+ for item in query_result
+ if self.get_seconds_diff(item[1] or current_time) <= seconds
+ }
return result
def get_item(self, seconds, content_id):
content_id = str(content_id)
query_result = self._get(content_id)
- result = {}
- if query_result:
- current_time = datetime.now()
- cached_time = query_result[1]
- if cached_time is None:
- logger.log_error('Data Cache [get]: cached_time is None while getting {content_id}'.format(content_id=content_id))
- cached_time = current_time
- # this is so stupid, but we have the function 'total_seconds' only starting with python 2.7
- diff_seconds = self.get_seconds_diff(cached_time)
- if diff_seconds <= seconds:
- result[content_id] = json.loads(query_result[0])
+ if not query_result:
+ return {}
+
+ current_time = datetime.now()
+ if self.get_seconds_diff(query_result[1] or current_time) > seconds:
+ return {}
+ result = {content_id: json.loads(query_result[0])}
return result
- def set(self, content_id, item):
+ def set_item(self, content_id, item):
self._set(content_id, item)
- def set_all(self, items):
+ def set_items(self, items):
self._set_all(items)
def clear(self):
@@ -95,31 +71,3 @@ def update(self, content_id, item):
def _optimize_item_count(self):
pass
-
- def _set(self, content_id, item):
- def _encode(obj):
- return sqlite3.Binary(pickle.dumps(obj, protocol=pickle.HIGHEST_PROTOCOL))
-
- current_time = datetime.now() + timedelta(microseconds=1)
- query = 'REPLACE INTO %s (key,time,value) VALUES(?,?,?)' % self._table_name
-
- self._open()
- self._execute(True, query, values=[content_id, current_time, _encode(item)])
- self._close()
-
- def _set_all(self, items):
- def _encode(obj):
- return sqlite3.Binary(pickle.dumps(obj, protocol=pickle.HIGHEST_PROTOCOL))
-
- needs_commit = True
- current_time = datetime.now() + timedelta(microseconds=1)
-
- query = 'REPLACE INTO %s (key,time,value) VALUES(?,?,?)' % self._table_name
-
- self._open()
-
- for key, item in items.items():
- self._execute(needs_commit, query, values=[key, current_time, _encode(json.dumps(item))])
- needs_commit = False
-
- self._close()
diff --git a/resources/lib/youtube_plugin/kodion/utils/favorite_list.py b/resources/lib/youtube_plugin/kodion/utils/favorite_list.py
index a4031b918..d18a013f6 100644
--- a/resources/lib/youtube_plugin/kodion/utils/favorite_list.py
+++ b/resources/lib/youtube_plugin/kodion/utils/favorite_list.py
@@ -19,18 +19,13 @@ def __init__(self, filename):
def clear(self):
self._clear()
- def list(self):
- result = []
+ @staticmethod
+ def _sort_item(_item):
+ return _item[2].get_name().upper()
- for key in self._get_ids():
- data = self._get(key)
- item = items.from_json(data[0])
- result.append(item)
-
- def _sort(_item):
- return _item.get_name().upper()
-
- return sorted(result, key=_sort, reverse=False)
+ def get_items(self):
+ result = self._get_by_ids(process=items.from_json)
+ return sorted(result, key=self._sort_item, reverse=False)
def add(self, base_item):
item_json_data = items.to_json(base_item)
diff --git a/resources/lib/youtube_plugin/kodion/utils/playback_history.py b/resources/lib/youtube_plugin/kodion/utils/playback_history.py
index e61779755..3acdb6400 100644
--- a/resources/lib/youtube_plugin/kodion/utils/playback_history.py
+++ b/resources/lib/youtube_plugin/kodion/utils/playback_history.py
@@ -7,10 +7,6 @@
See LICENSES/GPL-2.0-only for more information.
"""
-import datetime
-import pickle
-import sqlite3
-
from .storage import Storage
@@ -21,42 +17,39 @@ def __init__(self, filename):
def is_empty(self):
return self._is_empty()
+ @staticmethod
+ def _process_item(item):
+ return item.split(',')
+
def get_items(self, keys):
- def _decode(obj):
- return pickle.loads(obj)
-
- self._open()
- placeholders = ','.join(['?'] * len(keys))
- query = 'SELECT * FROM %s WHERE key IN (%s)' % (self._table_name, placeholders)
- query_result = self._execute(False, query, list(keys))
- data_keys = ['play_count', 'total_time' 'played_time', 'played_percent']
- result = {}
- if query_result:
- for item in query_result:
- values = _decode(item[2]).split(',')
- result[item[0]] = {
- 'play_count': int(values[0]),
- 'total_time': float(values[1]),
- 'played_time': float(values[2]),
- 'played_percent': int(values[3]),
- 'last_played': item[1],
- }
-
- self._close()
+ query_result = self._get_by_ids(keys, process=self._process_item)
+ if not query_result:
+ return {}
+
+ result = {
+ item[0]: {
+ 'play_count': int(item[2][0]),
+ 'total_time': float(item[2][1]),
+ 'played_time': float(item[2][2]),
+ 'played_percent': int(item[2][3]),
+ 'last_played': str(item[1]),
+ } for item in query_result
+ }
return result
def get_item(self, key):
query_result = self._get(key)
- result = {}
- if query_result:
- values = query_result[0].split(',')
- result[key] = {
- 'play_count': int(values[0]),
- 'total_time': float(values[1]),
- 'played_time': float(values[2]),
- 'played_percent': int(values[3]),
- 'last_played': query_result[1],
- }
+ if not query_result:
+ return {}
+
+ values = query_result[0].split(',')
+ result = {key: {
+ 'play_count': int(values[0]),
+ 'total_time': float(values[1]),
+ 'played_time': float(values[2]),
+ 'played_percent': int(values[3]),
+ 'last_played': str(query_result[1]),
+ }}
return result
def clear(self):
@@ -69,16 +62,6 @@ def update(self, video_id, play_count, total_time, played_time, played_percent):
item = ','.join([str(play_count), str(total_time), str(played_time), str(played_percent)])
self._set(str(video_id), item)
- def _set(self, item_id, item):
- def _encode(obj):
- return sqlite3.Binary(pickle.dumps(obj, protocol=pickle.HIGHEST_PROTOCOL))
-
- self._open()
- now = datetime.datetime.now() + datetime.timedelta(microseconds=1) # add 1 microsecond, required for dbapi2
- query = 'REPLACE INTO %s (key,time,value) VALUES(?,?,?)' % self._table_name
- self._execute(True, query, values=[item_id, now, _encode(item)])
- self._close()
-
def _optimize_item_count(self):
pass
diff --git a/resources/lib/youtube_plugin/kodion/utils/search_history.py b/resources/lib/youtube_plugin/kodion/utils/search_history.py
index f94d7bfb0..9eae48318 100644
--- a/resources/lib/youtube_plugin/kodion/utils/search_history.py
+++ b/resources/lib/youtube_plugin/kodion/utils/search_history.py
@@ -15,25 +15,17 @@
class SearchHistory(Storage):
- def __init__(self, filename, max_items=10):
- super(SearchHistory, self).__init__(filename, max_item_count=max_items)
+ def __init__(self, filename, max_item_count=10):
+ super(SearchHistory, self).__init__(filename,
+ max_item_count=max_item_count)
def is_empty(self):
return self._is_empty()
- def list(self):
- result = []
-
- keys = self._get_ids(oldest_first=False)
- for i, key in enumerate(keys):
- if i >= self._max_item_count:
- break
- item = self._get(key)
-
- if item:
- result.append(item[0])
-
- return result
+ def get_items(self):
+ result = self._get_by_ids(oldest_first=False,
+ limit=self._max_item_count)
+ return [item[2] for item in result]
def clear(self):
self._clear()
diff --git a/resources/lib/youtube_plugin/kodion/utils/storage.py b/resources/lib/youtube_plugin/kodion/utils/storage.py
index e764274bd..00efd4983 100644
--- a/resources/lib/youtube_plugin/kodion/utils/storage.py
+++ b/resources/lib/youtube_plugin/kodion/utils/storage.py
@@ -9,6 +9,7 @@
"""
import datetime
+import json
import os
import pickle
import sqlite3
@@ -19,12 +20,24 @@
class Storage(object):
- def __init__(self, filename, max_item_count=0, max_file_size_kb=-1):
- self._table_name = 'storage'
+ _table_name = 'storage'
+ _clear_query = 'DELETE FROM %s' % _table_name
+ _create_table_query = 'CREATE TABLE IF NOT EXISTS %s (key TEXT PRIMARY KEY, time TIMESTAMP, value BLOB)' % _table_name
+ _get_query = 'SELECT * FROM %s WHERE key = ?' % _table_name
+ _get_by_query = 'SELECT * FROM %s WHERE key in ({0})' % _table_name
+ _get_all_asc_query = 'SELECT * FROM %s ORDER BY time ASC LIMIT {0}' % _table_name
+ _get_all_desc_query = 'SELECT * FROM %s ORDER BY time DESC LIMIT {0}' % _table_name
+ _is_empty_query = 'SELECT EXISTS(SELECT 1 FROM %s LIMIT 1)' % _table_name
+ _optimize_item_query = 'SELECT key FROM %s ORDER BY time DESC LIMIT -1 OFFSET {0}' % _table_name
+ _remove_query = 'DELETE FROM %s WHERE key = ?' % _table_name
+ _remove_all_query = 'DELETE FROM %s WHERE key in ({0})' % _table_name
+ _set_query = 'REPLACE INTO %s (key, time, value) VALUES(?, ?, ?)' % _table_name
+
+ def __init__(self, filename, max_item_count=-1, max_file_size_kb=-1):
self._filename = filename
if not self._filename.endswith('.sqlite'):
self._filename = ''.join([self._filename, '.sqlite'])
- self._file = None
+ self._db = None
self._cursor = None
self._max_item_count = max_item_count
self._max_file_size_kb = max_file_size_kb
@@ -32,6 +45,8 @@ def __init__(self, filename, max_item_count=0, max_file_size_kb=-1):
self._table_created = False
self._needs_commit = False
+ sqlite3.register_converter('timestamp', self._convert_timestamp)
+
def set_max_item_count(self, max_item_count):
self._max_item_count = max_item_count
@@ -42,51 +57,63 @@ def __del__(self):
self._close()
def _open(self):
- if self._file is None:
- self._optimize_file_size()
-
- path = os.path.dirname(self._filename)
- if not os.path.exists(path):
- os.makedirs(path)
+ if self._db:
+ return
- self._file = sqlite3.connect(self._filename, check_same_thread=False,
- detect_types=0, timeout=1)
+ self._optimize_file_size()
- self._file.isolation_level = None
- self._cursor = self._file.cursor()
- self._cursor.execute('PRAGMA journal_mode=MEMORY')
- self._cursor.execute('PRAGMA busy_timeout=20000')
- # self._cursor.execute('PRAGMA synchronous=OFF')
- self._create_table()
+ path = os.path.dirname(self._filename)
+ if not os.path.exists(path):
+ os.makedirs(path)
+
+ db = sqlite3.connect(self._filename, check_same_thread=False,
+ detect_types=sqlite3.PARSE_DECLTYPES,
+ timeout=1, isolation_level=None)
+ db.row_factory = sqlite3.Row
+ cursor = db.cursor()
+ # cursor.execute('PRAGMA journal_mode=MEMORY')
+ cursor.execute('PRAGMA journal_mode=WAL')
+ cursor.execute('PRAGMA busy_timeout=20000')
+ cursor.execute('PRAGMA read_uncommitted=TRUE')
+ cursor.execute('PRAGMA temp_store=MEMORY')
+ # cursor.execute('PRAGMA synchronous=OFF')
+ cursor.execute('PRAGMA synchronous=NORMAL')
+ cursor.arraysize = 100
+ self._db = db
+ self._cursor = cursor
+ self._create_table()
- def _execute(self, needs_commit, query, values=None):
+ def _execute(self, needs_commit, query, values=None, many=False):
if values is None:
- values = []
+ values = ()
if not self._needs_commit and needs_commit:
self._needs_commit = True
self._cursor.execute('BEGIN')
"""
- Tests revealed that sqlite has problems to release the database in time. This happens no so often, but just to
- be sure, we try at least 3 times to execute out statement.
+ Tests revealed that sqlite has problems to release the database in time
+ This happens no so often, but just to be sure, we try at least 3 times
+ to execute our statement.
"""
for _ in range(3):
try:
+ if many:
+ return self._cursor.executemany(query, values)
return self._cursor.execute(query, values)
except TypeError:
- return None
+ return []
except:
time.sleep(0.1)
- return None
+ return []
def _close(self):
- if self._file is not None:
- self.sync()
- self._file.commit()
+ if self._db:
+ self._sync()
+ self._db.commit()
self._cursor.close()
self._cursor = None
- self._file.close()
- self._file = None
+ self._db.close()
+ self._db = None
def _optimize_file_size(self):
# do nothing - only we have given a size
@@ -109,109 +136,126 @@ def _optimize_file_size(self):
pass
def _create_table(self):
- self._open()
- if not self._table_created:
- query = 'CREATE TABLE IF NOT EXISTS %s (key TEXT PRIMARY KEY, time TIMESTAMP, value BLOB)' % self._table_name
- self._execute(True, query)
- self._table_created = True
+ if self._table_created:
+ return
+ self._execute(True, self._create_table_query)
+ self._table_created = True
- def sync(self):
- if not self._cursor or not self._needs_commit:
+ def _sync(self):
+ if not self._needs_commit:
return None
self._needs_commit = False
return self._execute(False, 'COMMIT')
def _set(self, item_id, item):
- def _encode(obj):
- return sqlite3.Binary(pickle.dumps(obj, protocol=pickle.HIGHEST_PROTOCOL))
+ # add 1 microsecond, required for dbapi2
+ now = datetime.datetime.now().timestamp() + 0.000001
+ self._open()
+ self._execute(True, self._set_query, values=[item_id,
+ now,
+ self._encode(item)])
+ self._close()
+ self._optimize_item_count()
- if self._max_file_size_kb < 1 and self._max_item_count < 1:
- self._optimize_item_count()
- else:
- self._open()
- now = datetime.datetime.now() + datetime.timedelta(microseconds=1) # add 1 microsecond, required for dbapi2
- query = 'REPLACE INTO %s (key,time,value) VALUES(?,?,?)' % self._table_name
- self._execute(True, query, values=[item_id, now, _encode(item)])
- self._close()
- self._optimize_item_count()
+ def _set_all(self, items):
+ # add 1 microsecond, required for dbapi2
+ now = datetime.datetime.now().timestamp() + 0.000001
+ self._open()
+ self._execute(True, self._set_query,
+ values=[(key, now, self._encode(json.dumps(item)))
+ for key, item in items.items()],
+ many=True)
+ self._close()
+ self._optimize_item_count()
def _optimize_item_count(self):
- if self._max_item_count < 1:
+ if not self._max_item_count:
if not self._is_empty():
self._clear()
- else:
- self._open()
- query = 'SELECT key FROM %s ORDER BY time DESC LIMIT -1 OFFSET %d' % (self._table_name, self._max_item_count)
- result = self._execute(False, query)
- if result is not None:
- for item in result:
- self._remove(item[0])
- self._close()
+ return
+ if self._max_item_count < 0:
+ return
+ query = self._optimize_item_query.format(self._max_item_count)
+ self._open()
+ item_ids = self._execute(False, query)
+ item_ids = [item_id['key'] for item_id in item_ids]
+ if item_ids:
+ self._remove_all(item_ids)
+ self._close()
def _clear(self):
self._open()
- query = 'DELETE FROM %s' % self._table_name
- self._execute(True, query)
+ self._execute(True, self._clear_query)
self._create_table()
- self._close()
- self._open()
+ self._sync()
self._execute(False, 'VACUUM')
self._close()
def _is_empty(self):
self._open()
- query = 'SELECT exists(SELECT 1 FROM %s LIMIT 1);' % self._table_name
- result = self._execute(False, query)
- is_empty = True
- if result is not None:
- for item in result:
- is_empty = item[0] == 0
- break
+ result = self._execute(False, self._is_empty_query)
+ for item in result:
+ is_empty = item[0] == 0
+ break
+ else:
+ is_empty = True
self._close()
return is_empty
- def _get_ids(self, oldest_first=True):
- self._open()
- # self.sync()
- query = 'SELECT key FROM %s' % self._table_name
- if oldest_first:
- query = '%s ORDER BY time ASC' % query
- else:
- query = '%s ORDER BY time DESC' % query
-
- query_result = self._execute(False, query)
-
- result = []
- if query_result:
- for item in query_result:
- result.append(item[0])
+ @staticmethod
+ def _decode(obj, process=None):
+ decoded_obj = pickle.loads(obj, encoding='utf-8')
+ if process:
+ return process(decoded_obj)
+ return decoded_obj
- self._close()
- return result
+ @staticmethod
+ def _encode(obj):
+ return sqlite3.Binary(pickle.dumps(
+ obj, protocol=pickle.HIGHEST_PROTOCOL
+ ))
def _get(self, item_id):
- def _decode(obj):
- return pickle.loads(obj, encoding='utf-8')
-
self._open()
- query = 'SELECT time, value FROM %s WHERE key=?' % self._table_name
- result = self._execute(False, query, [item_id])
- if result is None:
- self._close()
- return None
+ result = self._execute(False, self._get_query, [item_id])
+ if result:
+ result = result.fetchone()
+ self._close()
+ if result:
+ return self._decode(result['value']), result['time']
+ return None
- item = result.fetchone()
- if item is None:
- self._close()
- return None
+ def _get_by_ids(self, item_ids=None, oldest_first=True, limit=-1,
+ process=None):
+ if not item_ids:
+ if oldest_first:
+ query = self._get_all_asc_query
+ else:
+ query = self._get_all_desc_query
+ query = query.format(limit)
+ else:
+ num_ids = len(item_ids)
+ query = self._get_by_query.format(('?,' * (num_ids - 1)) + '?')
+ item_ids = tuple(item_ids)
+ self._open()
+ result = self._execute(False, query, item_ids)
+ result = [
+ (item['key'], item['time'], self._decode(item['value'], process))
+ for item in result
+ ]
self._close()
- return _decode(item[1]), item[0]
+ return result
def _remove(self, item_id):
self._open()
- query = 'DELETE FROM %s WHERE key = ?' % self._table_name
- self._execute(True, query, [item_id])
+ self._execute(True, self._remove_query, [item_id])
+
+ def _remove_all(self, item_ids):
+ num_ids = len(item_ids)
+ query = self._remove_all_query.format(('?,' * (num_ids - 1)) + '?')
+ self._open()
+ self._execute(True, query, tuple(item_ids))
@staticmethod
def strptime(stamp, stamp_fmt):
@@ -223,27 +267,50 @@ def strptime(stamp, stamp_fmt):
pass
return time.strptime(stamp, stamp_fmt)
+ @classmethod
+ def _convert_timestamp(cls, val):
+ val = val.decode('utf-8')
+ if '-' in val or ':' in val:
+ return cls._parse_datetime_string(val)
+ return datetime.datetime.fromtimestamp(float(val))
+
+ @classmethod
+ def _parse_datetime_string(cls, current_stamp):
+ for stamp_format in ['%Y-%m-%d %H:%M:%S.%f', '%Y-%m-%d %H:%M:%S']:
+ try:
+ stamp_datetime = datetime.datetime(
+ *(cls.strptime(current_stamp, stamp_format)[0:6])
+ )
+ break
+ except ValueError: # current_stamp has no microseconds
+ continue
+ except TypeError:
+ logger.log_error('Exception while parsing timestamp:\n'
+ 'current_stamp |{cs}|{cst}|\n'
+ 'stamp_format |{sf}|{sft}|\n{tb}'
+ .format(cs=current_stamp,
+ cst=type(current_stamp),
+ sf=stamp_format,
+ sft=type(stamp_format),
+ tb=traceback.print_exc()))
+ else:
+ return None
+ return stamp_datetime
+
def get_seconds_diff(self, current_stamp):
- stamp_format = '%Y-%m-%d %H:%M:%S.%f'
- current_datetime = datetime.datetime.now()
if not current_stamp:
return 86400 # 24 hrs
- try:
- stamp_datetime = datetime.datetime(*(self.strptime(current_stamp, stamp_format)[0:6]))
- except ValueError: # current_stamp has no microseconds
- stamp_format = '%Y-%m-%d %H:%M:%S'
- stamp_datetime = datetime.datetime(*(self.strptime(current_stamp, stamp_format)[0:6]))
- except TypeError:
- logger.log_error('Exception while calculating timestamp difference: '
- 'current_stamp |{cs}|{cst}| stamp_format |{sf}|{sft}| \n{tb}'
- .format(cs=current_stamp, cst=type(current_stamp),
- sf=stamp_format, sft=type(stamp_format),
- tb=traceback.print_exc())
- )
- return 604800 # one week
+ current_datetime = datetime.datetime.now()
+ if isinstance(current_stamp, datetime.datetime):
+ time_delta = current_datetime - current_stamp
+ return time_delta.total_seconds()
+
+ if isinstance(current_stamp, (float, int)):
+ return current_datetime.timestamp() - current_stamp
+
+ stamp_datetime = self._parse_datetime_string(current_stamp)
+ if not stamp_datetime:
+ return 604800 # one week
time_delta = current_datetime - stamp_datetime
- total_seconds = 0
- if time_delta:
- total_seconds = ((time_delta.seconds + time_delta.days * 24 * 3600) * 10 ** 6) // (10 ** 6)
- return total_seconds
+ return time_delta.total_seconds()
diff --git a/resources/lib/youtube_plugin/kodion/utils/watch_later_list.py b/resources/lib/youtube_plugin/kodion/utils/watch_later_list.py
index 04e21cada..e6a570960 100644
--- a/resources/lib/youtube_plugin/kodion/utils/watch_later_list.py
+++ b/resources/lib/youtube_plugin/kodion/utils/watch_later_list.py
@@ -21,21 +21,13 @@ def __init__(self, filename):
def clear(self):
self._clear()
- def list(self):
- result = []
+ @staticmethod
+ def _sort_item(_item):
+ return _item[2].get_date()
- for key in self._get_ids():
- data = self._get(key)
- item = items.from_json(data[0])
- result.append(item)
-
- def _sort(video_item):
- return video_item.get_date()
-
- self.sync()
-
- sorted_list = sorted(result, key=_sort, reverse=False)
- return sorted_list
+ def get_items(self):
+ result = self._get_by_ids(process=items.from_json)
+ return sorted(result, key=self._sort_item, reverse=False)
def add(self, base_item):
now = datetime.datetime.now()
diff --git a/resources/lib/youtube_plugin/youtube/client/youtube.py b/resources/lib/youtube_plugin/youtube/client/youtube.py
index 03a959ad3..aa03648d4 100644
--- a/resources/lib/youtube_plugin/youtube/client/youtube.py
+++ b/resources/lib/youtube_plugin/youtube/client/youtube.py
@@ -362,7 +362,7 @@ def helper(video_id, responses):
# Truncate items to keep it manageable, and cache
items = items[:500]
- cache.set(cache_items_key, json.dumps(items))
+ cache.set_item(cache_items_key, json.dumps(items))
# Build the result set
items.sort(
@@ -421,7 +421,7 @@ def helper(video_id, responses):
}
"""
# Update cache
- cache.set(cache_home_key, json.dumps(payload))
+ cache.set_item(cache_home_key, json.dumps(payload))
# If there are no sorted_items we fall back to default API behaviour
return payload
@@ -867,7 +867,7 @@ def _sort_by_date_time(e):
_result['items'].sort(reverse=True, key=_sort_by_date_time)
# Update cache
- cache.set(cache_items_key, json.dumps(_result['items']))
+ cache.set_item(cache_items_key, json.dumps(_result['items']))
""" no cache, get uploads data from web """
# trim result
diff --git a/resources/lib/youtube_plugin/youtube/helper/resource_manager.py b/resources/lib/youtube_plugin/youtube/helper/resource_manager.py
index ee62dad50..ca1edcf95 100644
--- a/resources/lib/youtube_plugin/youtube/helper/resource_manager.py
+++ b/resources/lib/youtube_plugin/youtube/helper/resource_manager.py
@@ -82,7 +82,7 @@ def _update_channels(self, channel_ids):
if yt_item
}
result.update(channel_data)
- data_cache.set_all(channel_data)
+ data_cache.set_items(channel_data)
self._context.log_debug('Cached data for channels |%s|' % ', '.join(channel_data))
if self.handle_error(json_data):
@@ -112,7 +112,7 @@ def _update_videos(self, video_ids, live_details=False, suppress_errors=False):
if yt_item
}
result.update(video_data)
- data_cache.set_all(video_data)
+ data_cache.set_items(video_data)
self._context.log_debug('Cached data for videos |%s|' % ', '.join(video_data))
if self._context.get_settings().use_local_history():
@@ -163,7 +163,7 @@ def _update_playlists(self, playlists_ids):
if yt_item
}
result.update(playlist_data)
- data_cache.set_all(playlist_data)
+ data_cache.set_items(playlist_data)
self._context.log_debug('Cached data for playlists |%s|' % ', '.join(playlist_data))
if self.handle_error(json_data):
diff --git a/resources/lib/youtube_plugin/youtube/helper/video_info.py b/resources/lib/youtube_plugin/youtube/helper/video_info.py
index 4f66dac61..9b46657ee 100644
--- a/resources/lib/youtube_plugin/youtube/helper/video_info.py
+++ b/resources/lib/youtube_plugin/youtube/helper/video_info.py
@@ -737,7 +737,7 @@ def _get_player_js(self):
return ''
js_url = self._normalize_url(js_url)
- self._data_cache.set('player_js_url', json_dumps({'url': js_url}))
+ self._data_cache.set_item('player_js_url', json_dumps({'url': js_url}))
cache_key = quote(js_url)
cached_js = self._data_cache.get_item(
@@ -756,7 +756,7 @@ def _get_player_js(self):
return ''
javascript = result.text
- self._data_cache.set(cache_key, json_dumps({'js': javascript}))
+ self._data_cache.set_item(cache_key, json_dumps({'js': javascript}))
return javascript
@staticmethod
@@ -942,7 +942,7 @@ def _process_signature_cipher(self, stream_map):
'Failed to extract URL from signatureCipher'
)
return None
- self._data_cache.set(
+ self._data_cache.set_item(
encrypted_signature, json_dumps({'sig': signature})
)
diff --git a/resources/lib/youtube_plugin/youtube/provider.py b/resources/lib/youtube_plugin/youtube/provider.py
index 21367aed2..f92b017ca 100644
--- a/resources/lib/youtube_plugin/youtube/provider.py
+++ b/resources/lib/youtube_plugin/youtube/provider.py
@@ -1382,8 +1382,7 @@ def on_root(self, context, re_match):
#clear cache
cache = context.get_data_cache()
- cache_items_key = 'my-subscriptions-items'
- cache.set(cache_items_key, '[]')
+ cache.set_item('my-subscriptions-items', '[]')
my_subscriptions_item = DirectoryItem(
context.get_ui().bold(context.localize(self.LOCAL_MAP['youtube.my_subscriptions'])),
From b748a9fadd57736eb716d508dfa60b07c2d36035 Mon Sep 17 00:00:00 2001
From: MoojMidge <56883549+MoojMidge@users.noreply.github.com>
Date: Thu, 9 Nov 2023 13:20:01 +1100
Subject: [PATCH 031/141] Update listitem creation to fix date sorting
- Fixes #411
- Workaround issue with infotagger until updated
- Fix resume properties
- Update ISA properties
---
.../kodion/plugin/xbmc/xbmc_runner.py | 4 +-
.../kodion/ui/xbmc/info_labels.py | 8 +-
.../kodion/ui/xbmc/xbmc_items.py | 283 ++++++++++--------
3 files changed, 162 insertions(+), 133 deletions(-)
diff --git a/resources/lib/youtube_plugin/kodion/plugin/xbmc/xbmc_runner.py b/resources/lib/youtube_plugin/kodion/plugin/xbmc/xbmc_runner.py
index dddeca507..4b84d1cab 100644
--- a/resources/lib/youtube_plugin/kodion/plugin/xbmc/xbmc_runner.py
+++ b/resources/lib/youtube_plugin/kodion/plugin/xbmc/xbmc_runner.py
@@ -104,7 +104,8 @@ def _add_directory(directory_item, show_fanart=False):
item = xbmcgui.ListItem(label=directory_item.get_name(), offscreen=True)
- info_tag = xbmc_items.ListItemInfoTag(item, tag_type='video')
+ info = info_labels.create_from_item(directory_item)
+ xbmc_items.set_info_tag(item, info, 'video')
# only set fanart is enabled
if show_fanart:
@@ -118,7 +119,6 @@ def _add_directory(directory_item, show_fanart=False):
item.addContextMenuItems(directory_item.get_context_menu(),
replaceItems=directory_item.replace_context_menu())
- info_tag.set_info(info_labels.create_from_item(directory_item))
item.setPath(directory_item.get_uri())
is_folder = True
diff --git a/resources/lib/youtube_plugin/kodion/ui/xbmc/info_labels.py b/resources/lib/youtube_plugin/kodion/ui/xbmc/info_labels.py
index 86f8f9427..9f779b9aa 100644
--- a/resources/lib/youtube_plugin/kodion/ui/xbmc/info_labels.py
+++ b/resources/lib/youtube_plugin/kodion/ui/xbmc/info_labels.py
@@ -15,8 +15,7 @@
def _process_date(info_labels, param):
if param:
datetime = utils.datetime_parser.parse(param)
- datetime = '%02d.%02d.%04d' % (datetime.day, datetime.month, datetime.year)
- info_labels['date'] = datetime
+ info_labels['date'] = datetime.isoformat()
def _process_int_value(info_labels, name, param):
@@ -68,8 +67,7 @@ def _process_video_rating(info_labels, param):
def _process_date_value(info_labels, name, param):
if param:
date = utils.datetime_parser.parse(param)
- date = '%04d-%02d-%02d' % (date.year, date.month, date.day)
- info_labels[name] = date
+ info_labels[name] = date.isoformat()
def _process_list_value(info_labels, name, param):
@@ -92,7 +90,7 @@ def _process_last_played(info_labels, name, param):
def create_from_item(base_item):
info_labels = {}
- # 'date' = '09.03.1982'
+ # 'date' = '1982-03-09'
_process_date(info_labels, base_item.get_date())
# Directory
diff --git a/resources/lib/youtube_plugin/kodion/ui/xbmc/xbmc_items.py b/resources/lib/youtube_plugin/kodion/ui/xbmc/xbmc_items.py
index 2cbbe6324..b938c4250 100644
--- a/resources/lib/youtube_plugin/kodion/ui/xbmc/xbmc_items.py
+++ b/resources/lib/youtube_plugin/kodion/ui/xbmc/xbmc_items.py
@@ -8,216 +8,247 @@
See LICENSES/GPL-2.0-only for more information.
"""
-import xbmcgui
+from xbmcgui import ListItem
try:
- from infotagger.listitem import ListItemInfoTag
+ from infotagger.listitem import set_info_tag
except ImportError:
+ def set_info_tag(listitem, info, tag_type, *_args, **_kwargs):
+ listitem.setInfo(tag_type, info)
+ return ListItemInfoTag(listitem, tag_type)
+
class ListItemInfoTag(object):
- __slots__ = ('__li__', '__type__')
+ __slots__ = ('__li__', )
- def __init__(self, list_item, tag_type):
- self.__li__ = list_item
- self.__type__ = tag_type
+ def __init__(self, listitem, *_args, **_kwargs):
+ self.__li__ = listitem
def add_stream_info(self, *args, **kwargs):
return self.__li__.addStreamInfo(*args, **kwargs)
- def set_info(self, *args, **kwargs):
- return self.__li__.setInfo(self.__type__, *args, **kwargs)
-
+ def set_resume_point(self, *_args, **_kwargs):
+ pass
-from ...items import VideoItem, AudioItem, UriItem
-from ... import utils
from . import info_labels
+from ...items import VideoItem, AudioItem, UriItem
+from ...utils import datetime_parser
def to_play_item(context, play_item):
uri = play_item.get_uri()
context.log_debug('Converting PlayItem |%s|' % uri)
- is_strm = str(context.get_param('strm', False)).lower() == 'true'
-
- thumb = play_item.get_image() if play_item.get_image() else 'DefaultVideo.png'
- title = play_item.get_title() if play_item.get_title() else play_item.get_name()
- fanart = ''
settings = context.get_settings()
- if is_strm:
- list_item = xbmcgui.ListItem(offscreen=True)
- else:
- list_item = xbmcgui.ListItem(label=utils.to_unicode(title), offscreen=True)
-
- info_tag = ListItemInfoTag(list_item, tag_type='video')
-
- if not is_strm:
- list_item.setProperty('IsPlayable', 'true')
-
- if play_item.get_fanart() and settings.show_fanart():
- fanart = play_item.get_fanart()
-
- list_item.setArt({'icon': thumb, 'thumb': thumb, 'fanart': fanart})
-
headers = play_item.get_headers()
license_key = play_item.get_license_key()
alternative_player = settings.is_support_alternative_player_enabled()
-
- if (alternative_player and settings.alternative_player_web_urls()
+ is_strm = context.get_param('strm')
+ mime_type = None
+
+ kwargs = {
+ 'label': (None if is_strm
+ else (play_item.get_title() or play_item.get_name())),
+ 'offscreen': True,
+ }
+ props = {
+ 'isPlayable': 'true',
+ }
+
+ if (alternative_player
+ and settings.alternative_player_web_urls()
and not license_key):
play_item.set_uri('https://www.youtube.com/watch?v={video_id}'.format(
video_id=play_item.video_id
))
- isa_enabled = settings.use_isa() and context.addon_enabled('inputstream.adaptive')
-
- if isa_enabled and play_item.use_isa_video():
+ elif (play_item.use_isa_video()
+ and context.addon_enabled('inputstream.adaptive')):
if play_item.use_mpd_video():
manifest_type = 'mpd'
mime_type = 'application/xml+dash'
# MPD manifest update is currently broken
# Following line will force a full update but restart live stream from start
# if play_item.live:
- # list_item.setProperty('inputstream.adaptive.manifest_update_parameter', 'full')
+ # props['inputstream.adaptive.manifest_update_parameter'] = 'full'
if 'auto' in settings.stream_select():
- list_item.setProperty('inputstream.adaptive.stream_selection_type', 'adaptive')
+ props['inputstream.adaptive.stream_selection_type'] = 'adaptive'
else:
manifest_type = 'hls'
mime_type = 'application/x-mpegURL'
- list_item.setContentLookup(False)
- list_item.setMimeType(mime_type)
- list_item.setProperty('inputstream', 'inputstream.adaptive')
- list_item.setProperty('inputstream.adaptive.manifest_type', manifest_type)
+ props['inputstream'] = 'inputstream.adaptive'
+ props['inputstream.adaptive.manifest_type'] = manifest_type
if headers:
- list_item.setProperty('inputstream.adaptive.manifest_headers', headers)
- list_item.setProperty('inputstream.adaptive.stream_headers', headers)
+ props['inputstream.adaptive.manifest_headers'] = headers
+ props['inputstream.adaptive.stream_headers'] = headers
if license_key:
- list_item.setProperty('inputstream.adaptive.license_type', 'com.widevine.alpha')
- list_item.setProperty('inputstream.adaptive.license_key', license_key)
+ props['inputstream.adaptive.license_type'] = 'com.widevine.alpha'
+ props['inputstream.adaptive.license_key'] = license_key
+
else:
if 'mime=' in uri:
- try:
- mime_type = uri.split('mime=', 1)[-1].split('&', 1)[0].replace('%2F', '/', 1)
- list_item.setMimeType(mime_type)
- list_item.setContentLookup(False)
- except:
- pass
+ mime_type = uri.partition('mime=')[2].partition('&')[0].replace('%2F', '/')
+
if not alternative_player and headers and uri.startswith('http'):
play_item.set_uri('|'.join([uri, headers]))
- if not is_strm:
- if play_item.get_play_count() == 0:
- if play_item.get_start_percent():
- list_item.setProperty('StartPercent', str(play_item.get_start_percent()))
+ list_item = ListItem(**kwargs)
+ if mime_type:
+ list_item.setContentLookup(False)
+ list_item.setMimeType(mime_type)
- if play_item.get_start_time():
- list_item.setProperty('StartOffset', str(play_item.get_start_time()))
+ if is_strm:
+ return list_item
- if play_item.subtitles:
- list_item.setSubtitles(play_item.subtitles)
+ if not context.get_param('resume'):
+ if 'ResumeTime' in props:
+ del props['ResumeTime']
- _info_labels = info_labels.create_from_item(play_item)
+ prop_value = play_item.get_duration()
+ if prop_value:
+ props['TotalTime'] = str(prop_value)
- # This should work for all versions of XBMC/KODI.
- if 'duration' in _info_labels:
- duration = _info_labels['duration']
- info_tag.add_stream_info('video', {'duration': duration})
+ fanart = settings.show_fanart() and play_item.get_fanart() or ''
+ thumb = play_item.get_image() or 'DefaultVideo.png'
+ list_item.setArt({'icon': thumb, 'thumb': thumb, 'fanart': fanart})
- info_tag.set_info(_info_labels)
+ if play_item.subtitles:
+ list_item.setSubtitles(play_item.subtitles)
+
+ info = info_labels.create_from_item(play_item)
+ info_tag = set_info_tag(list_item, info, 'video')
+ info_tag.set_resume_point(props)
+
+ # This should work for all versions of XBMC/KODI.
+ if 'duration' in info:
+ info_tag.add_stream_info('video', {'duration': info['duration']})
+
+ list_item.setProperties(props)
return list_item
def to_video_item(context, video_item):
context.log_debug('Converting VideoItem |%s|' % video_item.get_uri())
- thumb = video_item.get_image() if video_item.get_image() else 'DefaultVideo.png'
- title = video_item.get_title() if video_item.get_title() else video_item.get_name()
- fanart = ''
- settings = context.get_settings()
- item = xbmcgui.ListItem(label=utils.to_unicode(title), offscreen=True)
- info_tag = ListItemInfoTag(item, tag_type='video')
-
- if video_item.get_fanart() and settings.show_fanart():
- fanart = video_item.get_fanart()
+ kwargs = {
+ 'label': video_item.get_title() or video_item.get_name(),
+ 'offscreen': True,
+ }
+ props = {
+ 'isPlayable': 'true',
+ }
+
+ list_item = ListItem(**kwargs)
+
+ published_at = video_item.get_aired_utc()
+ scheduled_start = video_item.get_scheduled_start_utc()
+ datetime_string = scheduled_start or published_at
+ local_datetime = None
+ if datetime_string:
+ local_datetime = datetime_parser.utc_to_local(datetime_string)
+ props['PublishedLocal'] = str(local_datetime)
+ if video_item.live:
+ props['PublishedSince'] = context.localize('30539')
+ elif local_datetime:
+ props['PublishedSince'] = str(datetime_parser.datetime_to_since(
+ context, local_datetime
+ ))
- item.setArt({'icon': thumb, 'thumb': thumb, 'fanart': fanart})
+ prop_value = video_item.get_start_time()
+ if prop_value:
+ props['ResumeTime'] = str(prop_value)
- if video_item.get_context_menu() is not None:
- item.addContextMenuItems(video_item.get_context_menu(), replaceItems=video_item.replace_context_menu())
+ prop_value = video_item.get_duration()
+ if prop_value:
+ props['TotalTime'] = str(prop_value)
- item.setProperty('IsPlayable', 'true')
-
- if not video_item.live:
- published_at = video_item.get_aired_utc()
- scheduled_start = video_item.get_scheduled_start_utc()
- use_dt = scheduled_start or published_at
- if use_dt:
- local_dt = utils.datetime_parser.utc_to_local(use_dt)
- item.setProperty('PublishedSince',
- utils.to_unicode(utils.datetime_parser.datetime_to_since(context, local_dt)))
- item.setProperty('PublishedLocal', str(local_dt))
- else:
- item.setProperty('PublishedSince', context.localize('30539'))
+ # make channel_id property available for keymapping
+ prop_value = video_item.get_channel_id()
+ if prop_value:
+ props['channel_id'] = prop_value
- _info_labels = info_labels.create_from_item(video_item)
+ # make subscription_id property available for keymapping
+ prop_value = video_item.get_subscription_id()
+ if prop_value:
+ props['subscription_id'] = prop_value
- if video_item.get_play_count() == 0:
- if video_item.get_start_percent():
- item.setProperty('StartPercent', str(video_item.get_start_percent()))
+ # make playlist_id property available for keymapping
+ prop_value = video_item.get_playlist_id()
+ if prop_value:
+ props['playlist_id'] = prop_value
- if video_item.get_start_time():
- item.setProperty('StartOffset', str(video_item.get_start_time()))
+ # make playlist_item_id property available for keymapping
+ prop_value = video_item.get_playlist_item_id()
+ if prop_value:
+ props['playlist_item_id'] = prop_value
- # This should work for all versions of XBMC/KODI.
- if 'duration' in _info_labels:
- duration = _info_labels['duration']
- info_tag.add_stream_info('video', {'duration': duration})
+ fanart = (context.get_settings().show_fanart()
+ and video_item.get_fanart()
+ or '')
+ thumb = video_item.get_image() or 'DefaultVideo.png'
+ list_item.setArt({'icon': thumb, 'thumb': thumb, 'fanart': fanart})
- info_tag.set_info(_info_labels)
+ if video_item.subtitles:
+ list_item.setSubtitles(video_item.subtitles)
- if video_item.get_channel_id(): # make channel_id property available for keymapping
- item.setProperty('channel_id', video_item.get_channel_id())
+ info = info_labels.create_from_item(video_item)
+ info_tag = set_info_tag(list_item, info, 'video')
+ info_tag.set_resume_point(props)
- if video_item.get_subscription_id(): # make subscription_id property available for keymapping
- item.setProperty('subscription_id', video_item.get_subscription_id())
+ # This should work for all versions of XBMC/KODI.
+ if 'duration' in info:
+ info_tag.add_stream_info('video', {'duration': info['duration']})
- if video_item.get_playlist_id(): # make playlist_id property available for keymapping
- item.setProperty('playlist_id', video_item.get_playlist_id())
+ list_item.setProperties(props)
- if video_item.get_playlist_item_id(): # make playlist_item_id property available for keymapping
- item.setProperty('playlist_item_id', video_item.get_playlist_item_id())
+ context_menu = video_item.get_context_menu()
+ if context_menu:
+ list_item.addContextMenuItems(
+ context_menu, replaceItems=video_item.replace_context_menu()
+ )
- return item
+ return list_item
def to_audio_item(context, audio_item):
context.log_debug('Converting AudioItem |%s|' % audio_item.get_uri())
- thumb = audio_item.get_image() if audio_item.get_image() else 'DefaultAudio.png'
- title = audio_item.get_name()
- fanart = ''
- settings = context.get_settings()
- item = xbmcgui.ListItem(label=utils.to_unicode(title), offscreen=True)
- info_tag = ListItemInfoTag(item, tag_type='music')
- if audio_item.get_fanart() and settings.show_fanart():
- fanart = audio_item.get_fanart()
+ kwargs = {
+ 'label': audio_item.get_title() or audio_item.get_name(),
+ 'offscreen': True,
+ }
+ props = {
+ 'isPlayable': 'true',
+ }
- item.setArt({'icon': thumb, 'thumb': thumb, 'fanart': fanart})
+ list_item = ListItem(**kwargs)
- if audio_item.get_context_menu() is not None:
- item.addContextMenuItems(audio_item.get_context_menu(), replaceItems=audio_item.replace_context_menu())
+ fanart = (context.get_settings().show_fanart()
+ and audio_item.get_fanart()
+ or '')
+ thumb = audio_item.get_image() or 'DefaultAudio.png'
+ list_item.setArt({'icon': thumb, 'thumb': thumb, 'fanart': fanart})
- item.setProperty('IsPlayable', 'true')
+ info = info_labels.create_from_item(audio_item)
+ set_info_tag(list_item, info, 'music')
- info_tag.set_info(info_labels.create_from_item(audio_item))
- return item
+ list_item.setProperties(props)
+
+ context_menu = audio_item.get_context_menu()
+ if context_menu:
+ list_item.addContextMenuItems(
+ context_menu, replaceItems=audio_item.replace_context_menu()
+ )
+
+ return list_item
def to_uri_item(context, base_item):
context.log_debug('Converting UriItem')
- item = xbmcgui.ListItem(path=base_item.get_uri(), offscreen=True)
+ item = ListItem(path=base_item.get_uri(), offscreen=True)
item.setProperty('IsPlayable', 'true')
return item
From 9ba6c09e810bfeed8d371ae7f491f115268b6254 Mon Sep 17 00:00:00 2001
From: MoojMidge <56883549+MoojMidge@users.noreply.github.com>
Date: Fri, 10 Nov 2023 17:22:59 +1100
Subject: [PATCH 032/141] Update stream selection logic for codecs and HDR
- Fix #532
- Workaround for ISA not initialising decoder when codec changes within Adaptationset
- AV1 and H264 are no longer grouped together in the same AdaptationSet
- HDR is grouped separately for ISA stream selection dialog
---
.../lib/youtube_plugin/youtube/helper/video_info.py | 10 +++++++---
1 file changed, 7 insertions(+), 3 deletions(-)
diff --git a/resources/lib/youtube_plugin/youtube/helper/video_info.py b/resources/lib/youtube_plugin/youtube/helper/video_info.py
index 9b46657ee..d042e2970 100644
--- a/resources/lib/youtube_plugin/youtube/helper/video_info.py
+++ b/resources/lib/youtube_plugin/youtube/helper/video_info.py
@@ -1529,12 +1529,16 @@ def _process_stream_data(self, stream_data, default_lang_code='und'):
else:
frame_rate = None
- mime_group = mime_type
+ mime_group = '{mime_type}_{codec}{hdr}'.format(
+ mime_type=mime_type,
+ codec=codec,
+ hdr='_hdr' if hdr else ''
+ )
channels = language = role = role_type = sample_rate = None
label = quality['label'].format(fps if fps > 30 else '',
' HDR' if hdr else '',
compare_height)
- quality_group = '{0}_{1}'.format(container, label)
+ quality_group = '{0}_{1}_{2}'.format(container, codec, label)
if mime_group not in data:
data[mime_group] = {}
@@ -1599,7 +1603,7 @@ def _group_sort(item):
main_stream = streams[0]
key = (
- group != main_stream['mimeType'],
+ not group.startswith(main_stream['mimeType']),
) if main_stream['mediaType'] == 'video' else (
not group.startswith(main_stream['mimeType']),
preferred_audio['id'] not in group,
From db0d8c18e9a7cda585dcd9cbb794bb2965a3007b Mon Sep 17 00:00:00 2001
From: MoojMidge <56883549+MoojMidge@users.noreply.github.com>
Date: Tue, 21 Nov 2023 11:04:38 +1100
Subject: [PATCH 033/141] Improve robustness of JSONStore
- Fix #536 and hopefully prevents it from reoccuring
---
.../kodion/json_store/api_keys.py | 4 +-
.../kodion/json_store/json_store.py | 103 +++++++++++-------
.../kodion/json_store/login_tokens.py | 4 +-
3 files changed, 66 insertions(+), 45 deletions(-)
diff --git a/resources/lib/youtube_plugin/kodion/json_store/api_keys.py b/resources/lib/youtube_plugin/kodion/json_store/api_keys.py
index e5a4b8533..aeaf8d0cd 100644
--- a/resources/lib/youtube_plugin/kodion/json_store/api_keys.py
+++ b/resources/lib/youtube_plugin/kodion/json_store/api_keys.py
@@ -14,8 +14,8 @@ class APIKeyStore(JSONStore):
def __init__(self):
super(APIKeyStore, self).__init__('api_keys.json')
- def set_defaults(self):
- data = self.get_data()
+ def set_defaults(self, reset=False):
+ data = {} if reset else self.get_data()
if 'keys' not in data:
data = {'keys': {'personal': {'api_key': '', 'client_id': '', 'client_secret': ''}, 'developer': {}}}
if 'personal' not in data['keys']:
diff --git a/resources/lib/youtube_plugin/kodion/json_store/json_store.py b/resources/lib/youtube_plugin/kodion/json_store/json_store.py
index 52b1e06f3..001397737 100644
--- a/resources/lib/youtube_plugin/kodion/json_store/json_store.py
+++ b/resources/lib/youtube_plugin/kodion/json_store/json_store.py
@@ -9,13 +9,13 @@
import os
import json
-from copy import deepcopy
import xbmcaddon
import xbmcvfs
import xbmc
-from .. import logger
+from ..logger import log_debug, log_error
+from ..utils import make_dirs
try:
@@ -34,53 +34,74 @@ def __init__(self, filename):
except AttributeError:
self.base_path = xbmc.translatePath(addon.getAddonInfo('profile'))
- self.filename = os.path.join(self.base_path, filename)
+ if not xbmcvfs.exists(self.base_path) and not make_dirs(self.base_path):
+ log_error('JSONStore.__init__ |{path}| invalid path'.format(
+ path=self.base_path
+ ))
+ return
- self._data = None
+ self.filename = os.path.join(self.base_path, filename)
+ self._data = {}
self.load()
self.set_defaults()
- def set_defaults(self):
+ def set_defaults(self, reset=False):
raise NotImplementedError
def save(self, data):
- if data != self._data:
- self._data = deepcopy(data)
- if not xbmcvfs.exists(self.base_path):
- if not self.make_dirs(self.base_path):
- logger.log_debug('JSONStore Save |{filename}| failed to create directories.'.format(filename=self.filename.encode("utf-8")))
- return
- with open(self.filename, 'w') as jsonfile:
- logger.log_debug('JSONStore Save |{filename}|'.format(filename=self.filename.encode("utf-8")))
- json.dump(self._data, jsonfile, indent=4, sort_keys=True)
+ if data == self._data:
+ log_debug('JSONStore.save |{filename}| data unchanged'.format(
+ filename=self.filename
+ ))
+ return
+ log_debug('JSONStore.save |{filename}|'.format(
+ filename=self.filename
+ ))
+ try:
+ if not data:
+ raise ValueError
+ _data = json.loads(json.dumps(data))
+ with open(self.filename, mode='w', encoding='utf-8') as jsonfile:
+ json.dump(_data, jsonfile, indent=4, sort_keys=True)
+ self._data = _data
+ except (IOError, OSError):
+ log_error('JSONStore.save |{filename}| no access to file'.format(
+ filename=self.filename
+ ))
+ return
+ except (TypeError, ValueError):
+ log_error('JSONStore.save |{data}| invalid data'.format(
+ data=data
+ ))
+ self.set_defaults(reset=True)
def load(self):
- if xbmcvfs.exists(self.filename) and xbmcvfs.Stat(self.filename).st_size() > 0:
- with open(self.filename, 'r') as jsonfile:
- data = json.load(jsonfile)
- self._data = data
- logger.log_debug('JSONStore Load |{filename}|'.format(filename=self.filename.encode("utf-8")))
- else:
- self._data = dict()
+ log_debug('JSONStore.load |{filename}|'.format(
+ filename=self.filename
+ ))
+ try:
+ with open(self.filename, mode='r', encoding='utf-8') as jsonfile:
+ data = jsonfile.read()
+ if not data:
+ raise ValueError
+ self._data = json.loads(data)
+ except (IOError, OSError):
+ log_error('JSONStore.load |{filename}| no access to file'.format(
+ filename=self.filename
+ ))
+ except (TypeError, ValueError):
+ log_error('JSONStore.load |{data}| invalid data'.format(
+ data=data
+ ))
def get_data(self):
- return deepcopy(self._data)
-
- @staticmethod
- def make_dirs(path):
- if not path.endswith('/'):
- path = ''.join([path, '/'])
- path = xbmc.translatePath(path)
- if not xbmcvfs.exists(path):
- try:
- _ = xbmcvfs.mkdirs(path)
- except:
- pass
- if not xbmcvfs.exists(path):
- try:
- os.makedirs(path)
- except:
- pass
- return xbmcvfs.exists(path)
-
- return True
+ try:
+ if not self._data:
+ raise ValueError
+ return json.loads(json.dumps(self._data))
+ except (TypeError, ValueError):
+ log_error('JSONStore.get_data |{data}| invalid data'.format(
+ data=self._data
+ ))
+ self.set_defaults(reset=True)
+ return json.loads(json.dumps(self._data))
diff --git a/resources/lib/youtube_plugin/kodion/json_store/login_tokens.py b/resources/lib/youtube_plugin/kodion/json_store/login_tokens.py
index 3f691e1ca..e6eb27745 100644
--- a/resources/lib/youtube_plugin/kodion/json_store/login_tokens.py
+++ b/resources/lib/youtube_plugin/kodion/json_store/login_tokens.py
@@ -16,8 +16,8 @@ class LoginTokenStore(JSONStore):
def __init__(self):
super(LoginTokenStore, self).__init__('access_manager.json')
- def set_defaults(self):
- data = self.get_data()
+ def set_defaults(self, reset=False):
+ data = {} if reset else self.get_data()
if 'access_manager' not in data:
data = {'access_manager': {'users': {'0': {'access_token': '', 'refresh_token': '', 'token_expires': -1,
'last_key_hash': '', 'name': 'Default', 'watch_later': ' WL', 'watch_history': 'HL'}}}}
From e67fa3aa19e7732fce7a20dcd285f6171df263ee Mon Sep 17 00:00:00 2001
From: MoojMidge <56883549+MoojMidge@users.noreply.github.com>
Date: Tue, 21 Nov 2023 11:22:37 +1100
Subject: [PATCH 034/141] Update date processing and display
- Remove workarounds for total_seconds()
- Fix issues with incorrect date display
- Fix issue with storing incorrect date values
- Fix mixups with dateadded, date, premiered, etc.
- Fixes #464
- Possibly fixes #425, #434
---
.../resource.language.en_au/strings.po | 4 +
.../resource.language.en_gb/strings.po | 4 +
.../resource.language.en_nz/strings.po | 4 +
.../resource.language.en_us/strings.po | 4 +
.../kodion/context/xbmc/xbmc_context.py | 14 +-
.../youtube_plugin/kodion/items/base_item.py | 30 +++-
.../youtube_plugin/kodion/items/video_item.py | 31 ++--
.../lib/youtube_plugin/kodion/service.py | 5 +-
.../kodion/ui/xbmc/info_labels.py | 4 +-
.../kodion/ui/xbmc/xbmc_items.py | 8 +-
.../kodion/utils/datetime_parser.py | 148 ++++++++++--------
.../kodion/utils/function_cache.py | 1 -
.../youtube_plugin/youtube/client/youtube.py | 5 +-
.../youtube_plugin/youtube/helper/utils.py | 67 +++++---
.../lib/youtube_plugin/youtube/helper/v3.py | 5 +-
.../lib/youtube_plugin/youtube/provider.py | 1 +
16 files changed, 198 insertions(+), 137 deletions(-)
diff --git a/resources/language/resource.language.en_au/strings.po b/resources/language/resource.language.en_au/strings.po
index 7d72626af..fd9e033c3 100644
--- a/resources/language/resource.language.en_au/strings.po
+++ b/resources/language/resource.language.en_au/strings.po
@@ -1360,3 +1360,7 @@ msgstr ""
msgctxt "#30765"
msgid "Requests read timeout"
msgstr ""
+
+msgctxt "#30766"
+msgid "Premieres"
+msgstr ""
diff --git a/resources/language/resource.language.en_gb/strings.po b/resources/language/resource.language.en_gb/strings.po
index 461237005..f1eff0027 100644
--- a/resources/language/resource.language.en_gb/strings.po
+++ b/resources/language/resource.language.en_gb/strings.po
@@ -1360,3 +1360,7 @@ msgstr ""
msgctxt "#30765"
msgid "Requests read timeout"
msgstr ""
+
+msgctxt "#30766"
+msgid "Premieres"
+msgstr ""
diff --git a/resources/language/resource.language.en_nz/strings.po b/resources/language/resource.language.en_nz/strings.po
index 168654a38..03fe7bcd5 100644
--- a/resources/language/resource.language.en_nz/strings.po
+++ b/resources/language/resource.language.en_nz/strings.po
@@ -1356,3 +1356,7 @@ msgstr ""
msgctxt "#30765"
msgid "Requests read timeout"
msgstr ""
+
+msgctxt "#30766"
+msgid "Premieres"
+msgstr ""
diff --git a/resources/language/resource.language.en_us/strings.po b/resources/language/resource.language.en_us/strings.po
index 731014011..770bf552d 100644
--- a/resources/language/resource.language.en_us/strings.po
+++ b/resources/language/resource.language.en_us/strings.po
@@ -1361,3 +1361,7 @@ msgstr ""
msgctxt "#30765"
msgid "Requests read timeout"
msgstr ""
+
+msgctxt "#30766"
+msgid "Premieres"
+msgstr ""
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 e5bdd7ac4..aa0c6c9bc 100644
--- a/resources/lib/youtube_plugin/kodion/context/xbmc/xbmc_context.py
+++ b/resources/lib/youtube_plugin/kodion/context/xbmc/xbmc_context.py
@@ -98,7 +98,13 @@ def addon(self):
def is_plugin_path(self, uri, uri_path):
return uri.startswith('plugin://%s/%s/' % (self.get_id(), uri_path))
- def format_date_short(self, date_obj):
+ @staticmethod
+ def format_date_short(date_obj, short_isoformat=False):
+ if short_isoformat:
+ if isinstance(date_obj, datetime.datetime):
+ date_obj = date_obj.date()
+ return date_obj.isoformat()
+
date_format = xbmc.getRegion('dateshort')
_date_obj = date_obj
if isinstance(_date_obj, datetime.date):
@@ -106,7 +112,11 @@ def format_date_short(self, date_obj):
return _date_obj.strftime(date_format)
- def format_time(self, time_obj):
+ @staticmethod
+ def format_time(time_obj, short_isoformat=False):
+ if short_isoformat:
+ return '{:02d}:{:02d}'.format(time_obj.hour, time_obj.minute)
+
time_format = xbmc.getRegion('time')
_time_obj = time_obj
if isinstance(_time_obj, datetime.time):
diff --git a/resources/lib/youtube_plugin/kodion/items/base_item.py b/resources/lib/youtube_plugin/kodion/items/base_item.py
index d3b8572e2..1ff804c51 100644
--- a/resources/lib/youtube_plugin/kodion/items/base_item.py
+++ b/resources/lib/youtube_plugin/kodion/items/base_item.py
@@ -35,7 +35,9 @@ def __init__(self, name, uri, image='', fanart=''):
self._fanart = fanart
self._context_menu = None
self._replace_context_menu = False
+ self._added_utc = None
self._date = None
+ self._dateadded = None
self._next_page = False
@@ -103,12 +105,36 @@ def set_date(self, year, month, day, hour=0, minute=0, second=0):
self._date = date.isoformat(sep=' ')
def set_date_from_datetime(self, date_time):
- self.set_date(year=date_time.year, month=date_time.month, day=date_time.day, hour=date_time.hour,
- minute=date_time.minute, second=date_time.second)
+ self.set_date(year=date_time.year,
+ month=date_time.month,
+ day=date_time.day,
+ hour=date_time.hour,
+ minute=date_time.minute,
+ second=date_time.second)
+
+ def set_dateadded(self, year, month, day, hour=0, minute=0, second=0):
+ date = datetime.datetime(year, month, day, hour, minute, second)
+ self._dateadded = date.isoformat(sep=' ')
+
+ def set_dateadded_from_datetime(self, date_time):
+ self.set_dateadded(year=date_time.year,
+ month=date_time.month,
+ day=date_time.day,
+ hour=date_time.hour,
+ minute=date_time.minute,
+ second=date_time.second)
+
+ def set_added_utc(self, dt):
+ self._added_utc = dt
+
+ def get_added_utc(self):
+ return self._added_utc
def get_date(self):
return self._date
+ def get_dateadded(self):
+ return self._dateadded
@property
def next_page(self):
return self._next_page
diff --git a/resources/lib/youtube_plugin/kodion/items/video_item.py b/resources/lib/youtube_plugin/kodion/items/video_item.py
index 65e3579ef..5aca523a6 100644
--- a/resources/lib/youtube_plugin/kodion/items/video_item.py
+++ b/resources/lib/youtube_plugin/kodion/items/video_item.py
@@ -23,7 +23,6 @@ def __init__(self, name, uri, image='', fanart=''):
super(VideoItem, self).__init__(name, uri, image, fanart)
self._genre = None
self._aired = None
- self._aired_utc = None
self._scheduled_start_utc = None
self._duration = None
self._director = None
@@ -46,6 +45,7 @@ def __init__(self, name, uri, image='', fanart=''):
self._start_percent = None
self._start_time = None
self._live = False
+ self._upcoming = False
self.subtitles = None
self._headers = None
self.license_key = None
@@ -186,12 +186,6 @@ def set_aired(self, year, month, day):
date = datetime.date(year, month, day)
self._aired = date.isoformat()
- def set_aired_utc(self, dt):
- self._aired_utc = dt
-
- def get_aired_utc(self):
- return self._aired_utc
-
def set_aired_from_datetime(self, date_time):
self.set_aired(year=date_time.year,
month=date_time.month,
@@ -211,6 +205,14 @@ def live(self):
def live(self, value):
self._live = value
+ @property
+ def upcoming(self):
+ return self._upcoming
+
+ @upcoming.setter
+ def upcoming(self, value):
+ self._upcoming = value
+
def get_aired(self):
return self._aired
@@ -220,21 +222,6 @@ def set_genre(self, genre):
def get_genre(self):
return self._genre
- def set_date(self, year, month, day, hour=0, minute=0, second=0):
- date = datetime.datetime(year, month, day, hour, minute, second)
- self._date = date.isoformat(sep=' ')
-
- def set_date_from_datetime(self, date_time):
- self.set_date(year=date_time.year,
- month=date_time.month,
- day=date_time.day,
- hour=date_time.hour,
- minute=date_time.minute,
- second=date_time.second)
-
- def get_date(self):
- return self._date
-
def set_isa_video(self, value=True):
self._uses_isa = value
diff --git a/resources/lib/youtube_plugin/kodion/service.py b/resources/lib/youtube_plugin/kodion/service.py
index 163f1375c..5c75b7d3b 100644
--- a/resources/lib/youtube_plugin/kodion/service.py
+++ b/resources/lib/youtube_plugin/kodion/service.py
@@ -38,10 +38,9 @@ def get_stamp_diff(current_stamp):
stamp_datetime = datetime(*(strptime(current_stamp, stamp_format)[0:6]))
time_delta = current_datetime - stamp_datetime
- total_seconds = 0
if time_delta:
- total_seconds = ((time_delta.seconds + time_delta.days * 24 * 3600) * 10 ** 6) // (10 ** 6)
- return total_seconds
+ return time_delta.total_seconds()
+ return 0
def run():
diff --git a/resources/lib/youtube_plugin/kodion/ui/xbmc/info_labels.py b/resources/lib/youtube_plugin/kodion/ui/xbmc/info_labels.py
index 9f779b9aa..13601ddf2 100644
--- a/resources/lib/youtube_plugin/kodion/ui/xbmc/info_labels.py
+++ b/resources/lib/youtube_plugin/kodion/ui/xbmc/info_labels.py
@@ -130,8 +130,8 @@ def create_from_item(base_item):
# 'artist' = [] (list)
_process_list_value(info_labels, 'artist', base_item.get_artist())
- # 'dateadded' = '2014-08-11 13:08:56' (string) will be taken from 'date'
- _process_video_dateadded(info_labels, base_item.get_date())
+ # 'dateadded' = '2014-08-11 13:08:56' (string) will be taken from 'dateadded'
+ _process_video_dateadded(info_labels, base_item.get_dateadded())
# TODO: starting with Helix this could be seconds
# 'duration' = '3:18' (string)
diff --git a/resources/lib/youtube_plugin/kodion/ui/xbmc/xbmc_items.py b/resources/lib/youtube_plugin/kodion/ui/xbmc/xbmc_items.py
index b938c4250..67ad59afa 100644
--- a/resources/lib/youtube_plugin/kodion/ui/xbmc/xbmc_items.py
+++ b/resources/lib/youtube_plugin/kodion/ui/xbmc/xbmc_items.py
@@ -143,12 +143,12 @@ def to_video_item(context, video_item):
list_item = ListItem(**kwargs)
- published_at = video_item.get_aired_utc()
+ published_at = video_item.get_added_utc()
scheduled_start = video_item.get_scheduled_start_utc()
- datetime_string = scheduled_start or published_at
+ datetime = scheduled_start or published_at
local_datetime = None
- if datetime_string:
- local_datetime = datetime_parser.utc_to_local(datetime_string)
+ if datetime:
+ local_datetime = datetime_parser.utc_to_local(datetime)
props['PublishedLocal'] = str(local_datetime)
if video_item.live:
props['PublishedSince'] = context.localize('30539')
diff --git a/resources/lib/youtube_plugin/kodion/utils/datetime_parser.py b/resources/lib/youtube_plugin/kodion/utils/datetime_parser.py
index 41b7bf4c1..b547bd228 100644
--- a/resources/lib/youtube_plugin/kodion/utils/datetime_parser.py
+++ b/resources/lib/youtube_plugin/kodion/utils/datetime_parser.py
@@ -10,17 +10,23 @@
import re
import time
-from datetime import date, datetime, timedelta
-from datetime import time as dt_time
+from datetime import date, datetime, time as dt_time, timedelta
from ..exceptions import KodionException
+
__RE_MATCH_TIME_ONLY__ = re.compile(r'^(?P[0-9]{2})([:]?(?P[0-9]{2})([:]?(?P[0-9]{2}))?)?$')
__RE_MATCH_DATE_ONLY__ = re.compile(r'^(?P[0-9]{4})[-]?(?P[0-9]{2})[-]?(?P[0-9]{2})$')
__RE_MATCH_DATETIME__ = re.compile(r'^(?P[0-9]{4})[-]?(?P[0-9]{2})[-]?(?P[0-9]{2})["T ](?P[0-9]{2})[:]?(?P[0-9]{2})[:]?(?P[0-9]{2})')
__RE_MATCH_PERIOD__ = re.compile(r'P((?P\d+)Y)?((?P\d+)M)?((?P\d+)D)?(T((?P\d+)H)?((?P\d+)M)?((?P\d+)S)?)?')
__RE_MATCH_ABBREVIATED__ = re.compile(r'(\w+), (?P\d+) (?P\w+) (?P\d+) (?P\d+):(?P\d+):(?P\d+)')
+now = time.time()
+__LOCAL_OFFSET__ = datetime.fromtimestamp(now) - datetime.utcfromtimestamp(now)
+
+__EPOCH_DT__ = datetime.fromtimestamp(0)
+
+
now = datetime.now
@@ -28,8 +34,8 @@ def py2_utf8(text):
return text
-def parse(datetime_string, localize=True):
- _utc_to_local = utc_to_local if localize else lambda x: x
+def parse(datetime_string, as_utc=True):
+ offset = 0 if as_utc else None
def _to_int(value):
if value is None:
@@ -39,28 +45,38 @@ def _to_int(value):
# match time only '00:45:10'
time_only_match = __RE_MATCH_TIME_ONLY__.match(datetime_string)
if time_only_match:
- return _utc_to_local(datetime.combine(date.today(),
- dt_time(hour=_to_int(time_only_match.group('hour')),
- minute=_to_int(time_only_match.group('minute')),
- second=_to_int(time_only_match.group('second'))))
- ).time()
+ return utc_to_local(
+ dt=datetime.combine(
+ date.today(),
+ dt_time(hour=_to_int(time_only_match.group('hour')),
+ minute=_to_int(time_only_match.group('minute')),
+ second=_to_int(time_only_match.group('second')))
+ ),
+ offset=offset
+ ).time()
# match date only '2014-11-08'
date_only_match = __RE_MATCH_DATE_ONLY__.match(datetime_string)
if date_only_match:
- return _utc_to_local(date(_to_int(date_only_match.group('year')),
- _to_int(date_only_match.group('month')),
- _to_int(date_only_match.group('day'))))
+ return utc_to_local(
+ dt=date(_to_int(date_only_match.group('year')),
+ _to_int(date_only_match.group('month')),
+ _to_int(date_only_match.group('day'))),
+ offset=offset
+ )
# full date time
date_time_match = __RE_MATCH_DATETIME__.match(datetime_string)
if date_time_match:
- return _utc_to_local(datetime(_to_int(date_time_match.group('year')),
- _to_int(date_time_match.group('month')),
- _to_int(date_time_match.group('day')),
- _to_int(date_time_match.group('hour')),
- _to_int(date_time_match.group('minute')),
- _to_int(date_time_match.group('second'))))
+ return utc_to_local(
+ dt=datetime(_to_int(date_time_match.group('year')),
+ _to_int(date_time_match.group('month')),
+ _to_int(date_time_match.group('day')),
+ _to_int(date_time_match.group('hour')),
+ _to_int(date_time_match.group('minute')),
+ _to_int(date_time_match.group('second'))),
+ offset=offset
+ )
# period - at the moment we support only hours, minutes and seconds (e.g. videos and audio)
period_match = __RE_MATCH_PERIOD__.match(datetime_string)
@@ -74,40 +90,40 @@ def _to_int(value):
if abbreviated_match:
month = {'Jan': 1, 'Feb': 2, 'Mar': 3, 'Apr': 4, 'May': 5, 'June': 6, 'Jun': 6, 'July': 7, 'Jul': 7, 'Aug': 8,
'Sept': 9, 'Sep': 9, 'Oct': 10, 'Nov': 11, 'Dec': 12}
- return _utc_to_local(datetime(year=_to_int(abbreviated_match.group('year')),
- month=month[abbreviated_match.group('month')],
- day=_to_int(abbreviated_match.group('day')),
- hour=_to_int(abbreviated_match.group('hour')),
- minute=_to_int(abbreviated_match.group('minute')),
- second=_to_int(abbreviated_match.group('second'))))
+ return utc_to_local(
+ dt=datetime(year=_to_int(abbreviated_match.group('year')),
+ month=month[abbreviated_match.group('month')],
+ day=_to_int(abbreviated_match.group('day')),
+ hour=_to_int(abbreviated_match.group('hour')),
+ minute=_to_int(abbreviated_match.group('minute')),
+ second=_to_int(abbreviated_match.group('second'))),
+ offset=offset
+ )
raise KodionException("Could not parse iso 8601 timestamp '%s'" % datetime_string)
-def get_scheduled_start(datetime_object, localize=True):
- start_hour = '{:02d}'.format(datetime_object.hour)
- start_minute = '{:<02d}'.format(datetime_object.minute)
- start_time = ':'.join([start_hour, start_minute])
- start_date = str(datetime_object.date())
- if localize:
- now = datetime.now()
- else:
- now = datetime.utcnow()
- start_date = start_date.replace(str(now.year), '').lstrip('-')
- start_date = start_date.replace('-'.join(['{:02d}'.format(now.month), '{:02d}'.format(now.day)]), '')
- return start_date, start_time
-
-
-local_timezone_offset = None
-
+def get_scheduled_start(context, datetime_object, local=True):
+ now = datetime.now() if local else datetime.utcnow()
+ if datetime_object.date() == now.date():
+ return '@ {start_time}'.format(
+ start_time=context.format_time(
+ datetime_object.timetz(), short_isoformat=True
+ )
+ )
+ return '@ {start_date}, {start_time}'.format(
+ start_time=context.format_time(
+ datetime_object.timetz(), short_isoformat=True
+ ),
+ start_date=context.format_date_short(
+ datetime_object.date(), short_isoformat=True
+ )
+ )
-def utc_to_local(dt):
- global local_timezone_offset
- if local_timezone_offset is None:
- now = time.time()
- local_timezone_offset = datetime.fromtimestamp(now) - datetime.utcfromtimestamp(now)
- return dt + local_timezone_offset
+def utc_to_local(dt, offset=None):
+ offset = __LOCAL_OFFSET__ if offset is None else timedelta(hours=offset)
+ return dt + offset
def datetime_to_since(context, dt):
@@ -115,47 +131,47 @@ def datetime_to_since(context, dt):
diff = now - dt
yesterday = now - timedelta(days=1)
yyesterday = now - timedelta(days=2)
- use_yesterday = total_seconds(now - yesterday) > 10800
+ use_yesterday = (now - yesterday).total_seconds() > 10800
today = now.date()
tomorrow = today + timedelta(days=1)
- seconds = total_seconds(diff)
+ seconds = diff.total_seconds()
if seconds > 0:
if seconds < 60:
return py2_utf8(context.localize('30676'))
- elif 60 <= seconds < 120:
+ if 60 <= seconds < 120:
return py2_utf8(context.localize('30677'))
- elif 120 <= seconds < 3600:
+ if 120 <= seconds < 3600:
return py2_utf8(context.localize('30678'))
- elif 3600 <= seconds < 7200:
+ if 3600 <= seconds < 7200:
return py2_utf8(context.localize('30679'))
- elif 7200 <= seconds < 10800:
+ if 7200 <= seconds < 10800:
return py2_utf8(context.localize('30680'))
- elif 10800 <= seconds < 14400:
+ if 10800 <= seconds < 14400:
return py2_utf8(context.localize('30681'))
- elif use_yesterday and dt.date() == yesterday.date():
+ if use_yesterday and dt.date() == yesterday.date():
return ' '.join([py2_utf8(context.localize('30682')), context.format_time(dt)])
- elif dt.date() == yyesterday.date():
+ if dt.date() == yyesterday.date():
return py2_utf8(context.localize('30683'))
- elif 5400 <= seconds < 86400:
+ if 5400 <= seconds < 86400:
return ' '.join([py2_utf8(context.localize('30684')), context.format_time(dt)])
- elif 86400 <= seconds < 172800:
+ if 86400 <= seconds < 172800:
return ' '.join([py2_utf8(context.localize('30682')), context.format_time(dt)])
else:
seconds *= -1
if seconds < 60:
return py2_utf8(context.localize('30691'))
- elif 60 <= seconds < 120:
+ if 60 <= seconds < 120:
return py2_utf8(context.localize('30692'))
- elif 120 <= seconds < 3600:
+ if 120 <= seconds < 3600:
return py2_utf8(context.localize('30693'))
- elif 3600 <= seconds < 7200:
+ if 3600 <= seconds < 7200:
return py2_utf8(context.localize('30694'))
- elif 7200 <= seconds < 10800:
+ if 7200 <= seconds < 10800:
return py2_utf8(context.localize('30695'))
- elif dt.date() == today:
+ if dt.date() == today:
return ' '.join([py2_utf8(context.localize('30696')), context.format_time(dt)])
- elif dt.date() == tomorrow:
+ if dt.date() == tomorrow:
return ' '.join([py2_utf8(context.localize('30697')), context.format_time(dt)])
return ' '.join([context.format_date_short(dt), context.format_time(dt)])
@@ -178,9 +194,5 @@ def strptime(s, fmt='%Y-%m-%dT%H:%M:%S.%fZ'):
return datetime(*time.strptime(s, fmt)[:6])
-def total_seconds(t_delta): # required for python 2.6 which doesn't have datetime.timedelta.total_seconds
- return 24 * 60 * 60 * t_delta.days + t_delta.seconds + (t_delta.microseconds // 1000000.)
-
-
def since_epoch(dt_object):
- return total_seconds(dt_object - datetime(1970, 1, 1))
+ return (dt_object - __EPOCH_DT__).total_seconds()
diff --git a/resources/lib/youtube_plugin/kodion/utils/function_cache.py b/resources/lib/youtube_plugin/kodion/utils/function_cache.py
index 7218a8ba8..ea0b7c03f 100644
--- a/resources/lib/youtube_plugin/kodion/utils/function_cache.py
+++ b/resources/lib/youtube_plugin/kodion/utils/function_cache.py
@@ -101,7 +101,6 @@ def get(self, seconds, func, *args, **keywords):
diff_seconds = 0
if cached_time is not None:
- # this is so stupid, but we have the function 'total_seconds' only starting with python 2.7
diff_seconds = self.get_seconds_diff(cached_time)
if cached_data is None or diff_seconds > seconds:
diff --git a/resources/lib/youtube_plugin/youtube/client/youtube.py b/resources/lib/youtube_plugin/youtube/client/youtube.py
index aa03648d4..5943a012b 100644
--- a/resources/lib/youtube_plugin/youtube/client/youtube.py
+++ b/resources/lib/youtube_plugin/youtube/client/youtube.py
@@ -400,13 +400,10 @@ def helper(video_id, responses):
items.append(item)
# Finally sort items per page by date for a better distribution
- now = datetime_parser.now()
sorted_items.sort(
key=lambda a: (
a['page_number'],
- datetime_parser.total_seconds(
- now - datetime_parser.parse(a['snippet']['publishedAt'])
- )
+ -datetime_parser.parse(a['snippet']['publishedAt']).timestamp()
),
)
diff --git a/resources/lib/youtube_plugin/youtube/helper/utils.py b/resources/lib/youtube_plugin/youtube/helper/utils.py
index 70f6e688b..df23b1291 100644
--- a/resources/lib/youtube_plugin/youtube/helper/utils.py
+++ b/resources/lib/youtube_plugin/youtube/helper/utils.py
@@ -245,7 +245,7 @@ def update_playlist_infos(provider, context, playlist_id_dict,
def update_video_infos(provider, context, video_id_dict,
playlist_item_id_dict=None,
channel_items_dict=None,
- live_details=False,
+ live_details=True,
use_play_data=True,
data=None):
video_ids = list(video_id_dict)
@@ -283,7 +283,9 @@ def update_video_infos(provider, context, video_id_dict,
snippet = yt_item['snippet'] # crash if not conform
play_data = use_play_data and yt_item.get('play_data')
- video_item.live = snippet.get('liveBroadcastContent') == 'live'
+ broadcast_type = snippet.get('liveBroadcastContent')
+ video_item.live = broadcast_type == 'live'
+ video_item.upcoming = broadcast_type == 'upcoming'
# duration
if not video_item.live and play_data and 'total_time' in play_data:
@@ -311,21 +313,34 @@ def update_video_infos(provider, context, video_id_dict,
elif video_item.live:
video_item.set_play_count(0)
- scheduled_start = yt_item.get('liveStreamingDetails', {}).get('scheduledStartTime')
- if scheduled_start:
- datetime = utils.datetime_parser.parse(scheduled_start)
+ if ((video_item.live or video_item.upcoming)
+ and 'liveStreamingDetails' in yt_item):
+ start_at = yt_item['liveStreamingDetails'].get('scheduledStartTime')
+ else:
+ start_at = None
+ if start_at:
+ datetime = utils.datetime_parser.parse(start_at, as_utc=True)
video_item.set_scheduled_start_utc(datetime)
- start_date, start_time = utils.datetime_parser.get_scheduled_start(datetime)
- if start_date:
- title = '({live} {date}@{time}) {title}' \
- .format(live=context.localize(provider.LOCAL_MAP['youtube.live']), date=start_date, time=start_time, title=snippet['title'])
- else:
- title = '({live} @ {time}) {title}' \
- .format(live=context.localize(provider.LOCAL_MAP['youtube.live']), time=start_time, title=snippet['title'])
- video_item.set_title(title)
- # set the title
- elif not video_item.get_title():
- video_item.set_title(snippet['title'])
+ local_datetime = utils.datetime_parser.utc_to_local(datetime)
+ video_item.set_year_from_datetime(local_datetime)
+ video_item.set_aired_from_datetime(local_datetime)
+ video_item.set_premiered_from_datetime(local_datetime)
+ video_item.set_date_from_datetime(local_datetime)
+ type_label = context.localize(provider.LOCAL_MAP[
+ 'youtube.live' if video_item.live else 'youtube.upcoming'
+ ])
+ start_at = '{type_label} {start_at}'.format(
+ type_label=type_label,
+ start_at=utils.datetime_parser.get_scheduled_start(
+ context, local_datetime
+ )
+ )
+
+ # update and set the title
+ title = video_item.get_title() or snippet['title'] or ''
+ if video_item.upcoming:
+ title = ui.italic(title)
+ video_item.set_title(title)
"""
This is experimental. We try to get the most information out of the title of a video.
@@ -355,15 +370,17 @@ def update_video_infos(provider, context, video_id_dict,
video_item.set_plot(description)
# date time
- if not datetime and 'publishedAt' in snippet and snippet['publishedAt']:
- datetime = utils.datetime_parser.parse(snippet['publishedAt'])
- video_item.set_aired_utc(utils.datetime_parser.strptime(snippet['publishedAt']))
-
- if datetime:
- video_item.set_year_from_datetime(datetime)
- video_item.set_aired_from_datetime(datetime)
- video_item.set_premiered_from_datetime(datetime)
- video_item.set_date_from_datetime(datetime)
+ published_at = snippet.get('publishedAt')
+ if published_at:
+ datetime = utils.datetime_parser.parse(published_at, as_utc=True)
+ video_item.set_added_utc(datetime)
+ local_datetime = utils.datetime_parser.utc_to_local(datetime)
+ video_item.set_dateadded_from_datetime(local_datetime)
+ if not start_at:
+ video_item.set_year_from_datetime(local_datetime)
+ video_item.set_aired_from_datetime(local_datetime)
+ video_item.set_premiered_from_datetime(local_datetime)
+ video_item.set_date_from_datetime(local_datetime)
# try to find a better resolution for the image
image = video_item.get_image()
diff --git a/resources/lib/youtube_plugin/youtube/helper/v3.py b/resources/lib/youtube_plugin/youtube/helper/v3.py
index 3a5c1a7b3..e7fd3e429 100644
--- a/resources/lib/youtube_plugin/youtube/helper/v3.py
+++ b/resources/lib/youtube_plugin/youtube/helper/v3.py
@@ -23,8 +23,6 @@ def _process_list_response(provider, context, json_data):
result = []
- is_upcoming = False
-
thumb_size = context.get_settings().use_thumbnail_size()
yt_items = json_data.get('items', [])
if not yt_items:
@@ -208,7 +206,6 @@ def _process_list_response(provider, context, json_data):
if kind == 'video':
video_id = yt_item['id']['videoId']
snippet = yt_item.get('snippet', {})
- is_upcoming = snippet.get('liveBroadcastContent', '').lower() == 'upcoming'
title = snippet.get('title', context.localize(provider.LOCAL_MAP['youtube.untitled']))
image = utils.get_thumbnail(thumb_size, snippet.get('thumbnails', {}))
item_params = {'video_id': video_id}
@@ -271,7 +268,7 @@ def _process_list_response(provider, context, json_data):
# this will also update the channel_id_dict with the correct channel id for each video.
channel_items_dict = {}
utils.update_video_infos(provider, context, video_id_dict, playlist_item_id_dict, channel_items_dict,
- live_details=is_upcoming, use_play_data=use_play_data)
+ live_details=True, use_play_data=use_play_data)
utils.update_playlist_infos(provider, context, playlist_id_dict, channel_items_dict)
utils.update_channel_infos(provider, context, channel_id_dict, subscription_id_dict, channel_items_dict)
if video_id_dict or playlist_id_dict:
diff --git a/resources/lib/youtube_plugin/youtube/provider.py b/resources/lib/youtube_plugin/youtube/provider.py
index f92b017ca..f4eb1dba3 100644
--- a/resources/lib/youtube_plugin/youtube/provider.py
+++ b/resources/lib/youtube_plugin/youtube/provider.py
@@ -104,6 +104,7 @@ class Provider(AbstractProvider):
'youtube.playlist.play.from_here': 30537,
'youtube.video.disliked': 30538,
'youtube.live': 30539,
+ 'youtube.upcoming': 30766,
'youtube.video.play_with': 30540,
'youtube.error.rtmpe_not_supported': 30542,
'youtube.refresh': 30543,
From 940a106bd5d2bfa9731b5a02467382dc5761b45d Mon Sep 17 00:00:00 2001
From: MoojMidge <56883549+MoojMidge@users.noreply.github.com>
Date: Wed, 22 Nov 2023 11:49:20 +1100
Subject: [PATCH 035/141] Update label formatting methods
- Adds new format:
- [LIGHT]blah[/LIGHT]
- [I]blah[/I]
- [TABS]x[/TABS]blah
- [CR]blah and/or blah[CR]
- Optional [CR] to other format methods
---
.../kodion/ui/xbmc/xbmc_context_ui.py | 58 +++++++++++++++++--
1 file changed, 52 insertions(+), 6 deletions(-)
diff --git a/resources/lib/youtube_plugin/kodion/ui/xbmc/xbmc_context_ui.py b/resources/lib/youtube_plugin/kodion/ui/xbmc/xbmc_context_ui.py
index b8cdb0185..2e03d801a 100644
--- a/resources/lib/youtube_plugin/kodion/ui/xbmc/xbmc_context_ui.py
+++ b/resources/lib/youtube_plugin/kodion/ui/xbmc/xbmc_context_ui.py
@@ -169,16 +169,62 @@ def clear_home_window_property(property_id):
xbmcgui.Window(10000).clearProperty(property_id)
@staticmethod
- def bold(value):
- return ''.join(['[B]', value, '[/B]'])
+ def bold(value, cr_before=0, cr_after=0):
+ return ''.join((
+ '[CR]' * cr_before,
+ '[B]', value, '[/B]',
+ '[CR]' * cr_after,
+ ))
@staticmethod
- def uppercase(value):
- return ''.join(['[UPPERCASE]', value, '[/UPPERCASE]'])
+ def uppercase(value, cr_before=0, cr_after=0):
+ return ''.join((
+ '[CR]' * cr_before,
+ '[UPPERCASE]', value, '[/UPPERCASE]',
+ '[CR]' * cr_after,
+ ))
@staticmethod
- def color(color, value):
- return ''.join(['[COLOR=', color.lower(), ']', value, '[/COLOR]'])
+ def color(color, value, cr_before=0, cr_after=0):
+ return ''.join((
+ '[CR]' * cr_before,
+ '[COLOR=', color.lower(), ']', value, '[/COLOR]',
+ '[CR]' * cr_after,
+ ))
+
+ @staticmethod
+ def light(value, cr_before=0, cr_after=0):
+ return ''.join((
+ '[CR]' * cr_before,
+ '[LIGHT]', value, '[/LIGHT]',
+ '[CR]' * cr_after,
+ ))
+
+ @staticmethod
+ def italic(value, cr_before=0, cr_after=0):
+ return ''.join((
+ '[CR]' * cr_before,
+ '[I]', value, '[/I]',
+ '[CR]' * cr_after,
+ ))
+
+ @staticmethod
+ def indent(number=1, value='', cr_before=0, cr_after=0):
+ return ''.join((
+ '[CR]' * cr_before,
+ '[TABS]', str(number), '[/TABS]', value,
+ '[CR]' * cr_after,
+ ))
+
+ @staticmethod
+ def new_line(value=1, cr_before=0, cr_after=0):
+ if isinstance(value, int):
+ return '[CR]' * value
+ return ''.join((
+ '[CR]' * cr_before,
+ value,
+ '[CR]' * cr_after,
+ ))
def set_focus_next_item(self):
cid = xbmcgui.Window(xbmcgui.getCurrentWindowId()).getFocusId()
From c542880f4f08894251f8c6748a27f7787a4b118f Mon Sep 17 00:00:00 2001
From: MoojMidge <56883549+MoojMidge@users.noreply.github.com>
Date: Wed, 22 Nov 2023 12:17:26 +1100
Subject: [PATCH 036/141] Retrieve and display video stats
- Existing "Show channel name in description" option changed to
"Show channel name and video details in description"
- Displays number of views, likes, comments in description if enabled
- Displays stats and premiere datetime in label2
- Closes #503, #18
---
.../resource.language.en_au/strings.po | 6 +++-
.../resource.language.en_gb/strings.po | 6 +++-
.../resource.language.en_nz/strings.po | 6 +++-
.../resource.language.en_us/strings.po | 6 +++-
.../kodion/constants/const_settings.py | 1 +
.../youtube_plugin/kodion/items/base_item.py | 8 +++++
.../kodion/settings/abstract_settings.py | 3 ++
.../kodion/ui/xbmc/xbmc_items.py | 3 ++
.../youtube_plugin/kodion/utils/__init__.py | 6 ++--
.../youtube_plugin/kodion/utils/methods.py | 29 ++++++++++++++++--
.../youtube_plugin/youtube/client/youtube.py | 2 +-
.../youtube_plugin/youtube/helper/utils.py | 30 +++++++++++++++++--
.../lib/youtube_plugin/youtube/provider.py | 4 +++
resources/settings.xml | 2 +-
14 files changed, 99 insertions(+), 13 deletions(-)
diff --git a/resources/language/resource.language.en_au/strings.po b/resources/language/resource.language.en_au/strings.po
index fd9e033c3..783ff9c92 100644
--- a/resources/language/resource.language.en_au/strings.po
+++ b/resources/language/resource.language.en_au/strings.po
@@ -462,7 +462,7 @@ msgid "Play with..."
msgstr ""
msgctxt "#30541"
-msgid "Show channel name in description"
+msgid "Show channel name and video details in description"
msgstr ""
msgctxt "#30542"
@@ -1364,3 +1364,7 @@ msgstr ""
msgctxt "#30766"
msgid "Premieres"
msgstr ""
+
+msgctxt "#30767"
+msgid "Views"
+msgstr ""
diff --git a/resources/language/resource.language.en_gb/strings.po b/resources/language/resource.language.en_gb/strings.po
index f1eff0027..f8007413a 100644
--- a/resources/language/resource.language.en_gb/strings.po
+++ b/resources/language/resource.language.en_gb/strings.po
@@ -462,7 +462,7 @@ msgid "Play with..."
msgstr ""
msgctxt "#30541"
-msgid "Show channel name in description"
+msgid "Show channel name and video details in description"
msgstr ""
msgctxt "#30542"
@@ -1364,3 +1364,7 @@ msgstr ""
msgctxt "#30766"
msgid "Premieres"
msgstr ""
+
+msgctxt "#30767"
+msgid "Views"
+msgstr ""
diff --git a/resources/language/resource.language.en_nz/strings.po b/resources/language/resource.language.en_nz/strings.po
index 03fe7bcd5..b1e21fa84 100644
--- a/resources/language/resource.language.en_nz/strings.po
+++ b/resources/language/resource.language.en_nz/strings.po
@@ -458,7 +458,7 @@ msgid "Play with..."
msgstr ""
msgctxt "#30541"
-msgid "Show channel name in description"
+msgid "Show channel name and video details in description"
msgstr ""
msgctxt "#30542"
@@ -1360,3 +1360,7 @@ msgstr ""
msgctxt "#30766"
msgid "Premieres"
msgstr ""
+
+msgctxt "#30767"
+msgid "Views"
+msgstr ""
diff --git a/resources/language/resource.language.en_us/strings.po b/resources/language/resource.language.en_us/strings.po
index 770bf552d..2248415ba 100644
--- a/resources/language/resource.language.en_us/strings.po
+++ b/resources/language/resource.language.en_us/strings.po
@@ -463,7 +463,7 @@ msgid "Play with..."
msgstr ""
msgctxt "#30541"
-msgid "Show channel name in description"
+msgid "Show channel name and video details in description"
msgstr ""
msgctxt "#30542"
@@ -1365,3 +1365,7 @@ msgstr ""
msgctxt "#30766"
msgid "Premieres"
msgstr ""
+
+msgctxt "#30767"
+msgid "Views"
+msgstr ""
diff --git a/resources/lib/youtube_plugin/kodion/constants/const_settings.py b/resources/lib/youtube_plugin/kodion/constants/const_settings.py
index 1f13d7468..25a61cc0a 100644
--- a/resources/lib/youtube_plugin/kodion/constants/const_settings.py
+++ b/resources/lib/youtube_plugin/kodion/constants/const_settings.py
@@ -26,6 +26,7 @@
USE_REMOTE_HISTORY = 'kodion.history.remote' # (bool)
REMOTE_FRIENDLY_SEARCH = 'youtube.search.remote.friendly' # (bool)
HIDE_SHORT_VIDEOS = 'youtube.hide_shorts' # (bool)
+DETAILED_DESCRIPTION = 'youtube.view.description.details' # (bool)
SUPPORT_ALTERNATIVE_PLAYER = 'kodion.support.alternative_player' # (bool)
ALTERNATIVE_PLAYER_WEB_URLS = 'kodion.alternative_player.web.urls' # (bool)
diff --git a/resources/lib/youtube_plugin/kodion/items/base_item.py b/resources/lib/youtube_plugin/kodion/items/base_item.py
index 1ff804c51..02c96ff79 100644
--- a/resources/lib/youtube_plugin/kodion/items/base_item.py
+++ b/resources/lib/youtube_plugin/kodion/items/base_item.py
@@ -38,6 +38,7 @@ def __init__(self, name, uri, image='', fanart=''):
self._added_utc = None
self._date = None
self._dateadded = None
+ self._short_details = None
self._next_page = False
@@ -135,6 +136,13 @@ def get_date(self):
def get_dateadded(self):
return self._dateadded
+
+ def get_short_details(self):
+ return self._short_details
+
+ def set_short_details(self, details):
+ self._short_details = details or ''
+
@property
def next_page(self):
return self._next_page
diff --git a/resources/lib/youtube_plugin/kodion/settings/abstract_settings.py b/resources/lib/youtube_plugin/kodion/settings/abstract_settings.py
index b4d975f45..5a461224f 100644
--- a/resources/lib/youtube_plugin/kodion/settings/abstract_settings.py
+++ b/resources/lib/youtube_plugin/kodion/settings/abstract_settings.py
@@ -314,3 +314,6 @@ def hide_short_videos(self):
def client_selection(self):
return self.get_int(SETTINGS.CLIENT_SELECTION, 0)
+
+ def show_detailed_description(self):
+ return self.get_bool(SETTINGS.DETAILED_DESCRIPTION, True)
diff --git a/resources/lib/youtube_plugin/kodion/ui/xbmc/xbmc_items.py b/resources/lib/youtube_plugin/kodion/ui/xbmc/xbmc_items.py
index 67ad59afa..c7fe95dd9 100644
--- a/resources/lib/youtube_plugin/kodion/ui/xbmc/xbmc_items.py
+++ b/resources/lib/youtube_plugin/kodion/ui/xbmc/xbmc_items.py
@@ -48,6 +48,7 @@ def to_play_item(context, play_item):
kwargs = {
'label': (None if is_strm
else (play_item.get_title() or play_item.get_name())),
+ 'label2': None if is_strm else play_item.get_short_details(),
'offscreen': True,
}
props = {
@@ -135,6 +136,7 @@ def to_video_item(context, video_item):
kwargs = {
'label': video_item.get_title() or video_item.get_name(),
+ 'label2': video_item.get_short_details(),
'offscreen': True,
}
props = {
@@ -218,6 +220,7 @@ def to_audio_item(context, audio_item):
kwargs = {
'label': audio_item.get_title() or audio_item.get_name(),
+ 'label2': audio_item.get_short_details(),
'offscreen': True,
}
props = {
diff --git a/resources/lib/youtube_plugin/kodion/utils/__init__.py b/resources/lib/youtube_plugin/kodion/utils/__init__.py
index f74045045..fe5c07d99 100644
--- a/resources/lib/youtube_plugin/kodion/utils/__init__.py
+++ b/resources/lib/youtube_plugin/kodion/utils/__init__.py
@@ -14,6 +14,7 @@
create_uri_path,
find_best_fit,
find_video_id,
+ friendly_number,
loose_version,
make_dirs,
select_stream,
@@ -33,12 +34,13 @@
from .system_version import SystemVersion
-__all__ = [
+__all__ = (
'create_path',
'create_uri_path',
'datetime_parser',
'find_best_fit',
'find_video_id',
+ 'friendly_number',
'loose_version',
'make_dirs',
'select_stream',
@@ -55,4 +57,4 @@
'WatchLaterList',
'YouTubeMonitor',
'YouTubePlayer'
-]
+)
diff --git a/resources/lib/youtube_plugin/kodion/utils/methods.py b/resources/lib/youtube_plugin/kodion/utils/methods.py
index dc5df817f..a4d506e88 100644
--- a/resources/lib/youtube_plugin/kodion/utils/methods.py
+++ b/resources/lib/youtube_plugin/kodion/utils/methods.py
@@ -11,6 +11,7 @@
import os
import copy
import re
+from math import floor, log
from urllib.parse import quote
from ..constants import localize
@@ -19,8 +20,21 @@
import xbmcvfs
-__all__ = ['create_path', 'create_uri_path', 'strip_html_from_text', 'print_items', 'find_best_fit', 'to_utf8',
- 'to_str', 'to_unicode', 'select_stream', 'make_dirs', 'loose_version', 'find_video_id']
+__all__ = (
+ 'create_path',
+ 'create_uri_path',
+ 'find_best_fit',
+ 'find_video_id',
+ 'friendly_number',
+ 'loose_version',
+ 'make_dirs',
+ 'print_items',
+ 'select_stream',
+ 'strip_html_from_text',
+ 'to_str',
+ 'to_unicode',
+ 'to_utf8',
+)
try:
@@ -251,3 +265,14 @@ def find_video_id(plugin_path):
if match:
return match.group('video_id')
return ''
+
+
+def friendly_number(number, precision=3, scale=('', 'K', 'M', 'B')):
+ _input = float('{input:.{precision}g}'.format(
+ input=float(number), precision=precision
+ ))
+ _abs_input = abs(_input)
+ magnitude = 0 if _abs_input < 1000 else int(log(floor(_abs_input), 1000))
+ return '{output:f}'.format(
+ output=_input / 1000 ** magnitude
+ ).rstrip('0').rstrip('.') + scale[magnitude]
diff --git a/resources/lib/youtube_plugin/youtube/client/youtube.py b/resources/lib/youtube_plugin/youtube/client/youtube.py
index 5943a012b..fdb04295c 100644
--- a/resources/lib/youtube_plugin/youtube/client/youtube.py
+++ b/resources/lib/youtube_plugin/youtube/client/youtube.py
@@ -551,7 +551,7 @@ def get_videos(self, video_id, live_details=False):
if isinstance(video_id, list):
video_id = ','.join(video_id)
- parts = ['snippet,contentDetails,status']
+ parts = ['snippet,contentDetails,status,statistics']
if live_details:
parts.append(',liveStreamingDetails')
diff --git a/resources/lib/youtube_plugin/youtube/helper/utils.py b/resources/lib/youtube_plugin/youtube/helper/utils.py
index df23b1291..5a11bb1f5 100644
--- a/resources/lib/youtube_plugin/youtube/helper/utils.py
+++ b/resources/lib/youtube_plugin/youtube/helper/utils.py
@@ -265,7 +265,7 @@ def update_video_infos(provider, context, video_id_dict,
playlist_item_id_dict = {}
settings = context.get_settings()
- show_channel_name = settings.get_bool('youtube.view.description.show_channel_name', True)
+ show_details = settings.show_detailed_description()
alternate_player = settings.is_support_alternative_player_enabled()
thumb_size = settings.use_thumbnail_size()
thumb_stamp = get_thumb_timestamp()
@@ -342,6 +342,24 @@ def update_video_infos(provider, context, video_id_dict,
title = ui.italic(title)
video_item.set_title(title)
+ stats = []
+ if 'statistics' in yt_item:
+ for stat, value in yt_item['statistics'].items():
+ label = provider.LOCAL_MAP.get('youtube.stats.' + stat)
+ if label:
+ stats.append('{value} {name}'.format(
+ name=context.localize(label).lower(),
+ value=utils.friendly_number(value)
+ ))
+ stats = ', '.join(stats)
+
+ # Used for label2, but is poorly supported in skins
+ video_details = ' | '.join((detail for detail in (
+ ui.light(stats) if stats else '',
+ ui.italic(start_at) if start_at else '',
+ ) if detail))
+ video_item.set_short_details(video_details)
+
"""
This is experimental. We try to get the most information out of the title of a video.
This is not based on any language. In some cases this won't work at all.
@@ -362,8 +380,14 @@ def update_video_infos(provider, context, video_id_dict,
# plot
channel_name = snippet.get('channelTitle', '')
description = utils.strip_html_from_text(snippet['description'])
- if show_channel_name and channel_name:
- description = '%s[CR][CR]%s' % (ui.uppercase(ui.bold(channel_name)), description)
+ if show_details:
+ description = ''.join((
+ ui.bold(channel_name, cr_after=2) if channel_name else '',
+ ui.light(stats, cr_after=1) if stats else '',
+ ui.italic(start_at, cr_after=1) if start_at else '',
+ ui.new_line() if stats or start_at else '',
+ description,
+ ))
video_item.set_studio(channel_name)
# video_item.add_cast(channel_name)
video_item.add_artist(channel_name)
diff --git a/resources/lib/youtube_plugin/youtube/provider.py b/resources/lib/youtube_plugin/youtube/provider.py
index f4eb1dba3..a5a9c433c 100644
--- a/resources/lib/youtube_plugin/youtube/provider.py
+++ b/resources/lib/youtube_plugin/youtube/provider.py
@@ -200,6 +200,10 @@ class Provider(AbstractProvider):
'youtube.video.comments.likes': 30733,
'youtube.video.comments.replies': 30734,
'youtube.video.comments.edited': 30735,
+ 'youtube.stats.viewCount': 30767,
+ 'youtube.stats.likeCount': 30733,
+ # 'youtube.stats.favoriteCount': 30100,
+ 'youtube.stats.commentCount': 30732,
}
def __init__(self):
diff --git a/resources/settings.xml b/resources/settings.xml
index ae4d15438..e6ee7a6e6 100644
--- a/resources/settings.xml
+++ b/resources/settings.xml
@@ -669,7 +669,7 @@
-
+
0
true
From 2ff3e3ed357f922eff32206ac0785b688b3903a4 Mon Sep 17 00:00:00 2001
From: MoojMidge <56883549+MoojMidge@users.noreply.github.com>
Date: Wed, 22 Nov 2023 13:23:30 +1100
Subject: [PATCH 037/141] Remove duplicated update_video_infos code
---
.../youtube_plugin/youtube/helper/utils.py | 203 ++++++------------
.../youtube_plugin/youtube/helper/yt_play.py | 4 +-
2 files changed, 63 insertions(+), 144 deletions(-)
diff --git a/resources/lib/youtube_plugin/youtube/helper/utils.py b/resources/lib/youtube_plugin/youtube/helper/utils.py
index 5a11bb1f5..8dfaea98d 100644
--- a/resources/lib/youtube_plugin/youtube/helper/utils.py
+++ b/resources/lib/youtube_plugin/youtube/helper/utils.py
@@ -16,17 +16,20 @@
from ...youtube.helper import yt_context_menu
try:
- import inputstreamhelper
+ from inputstreamhelper import Helper as ISHelper
except ImportError:
- inputstreamhelper = None
+ ISHelper = None
-__RE_SEASON_EPISODE_MATCHES__ = [re.compile(r'Part (?P\d+)'),
- re.compile(r'#(?P\d+)'),
- re.compile(r'Ep.[^\w]?(?P\d+)'),
- re.compile(r'\[(?P\d+)\]'),
- re.compile(r'S(?P\d+)E(?P\d+)'),
- re.compile(r'Season (?P\d+)(.+)Episode (?P\d+)'),
- re.compile(r'Episode (?P\d+)')]
+
+__RE_SEASON_EPISODE_MATCHES__ = [
+ re.compile(r'Part (?P\d+)'),
+ re.compile(r'#(?P\d+)'),
+ re.compile(r'Ep.[^\w]?(?P\d+)'),
+ re.compile(r'\[(?P\d+)\]'),
+ re.compile(r'S(?P\d+)E(?P\d+)'),
+ re.compile(r'Season (?P\d+)(.+)Episode (?P\d+)'),
+ re.compile(r'Episode (?P\d+)'),
+]
def extract_urls(text):
@@ -113,7 +116,9 @@ def update_channel_infos(provider, context, channel_id_dict,
subscription_id_dict = {}
filter_list = []
- if context.get_path() == '/subscriptions/list/':
+ logged_in = provider.is_logged_in()
+ path = context.get_path()
+ if path == '/subscriptions/list/':
filter_string = context.get_settings().get_string('youtube.filter.my_subscriptions_filtered.list', '')
filter_string = filter_string.replace(', ', ',')
filter_list = filter_string.split(',')
@@ -143,10 +148,10 @@ def update_channel_infos(provider, context, channel_id_dict,
channel_item.set_channel_subscription_id(subscription_id)
yt_context_menu.append_unsubscribe_from_channel(context_menu, provider, context, subscription_id)
# -- subscribe to the channel
- if provider.is_logged_in() and context.get_path() != '/subscriptions/list/':
+ if logged_in and path != '/subscriptions/list/':
yt_context_menu.append_subscribe_to_channel(context_menu, provider, context, channel_id)
- if context.get_path() == '/subscriptions/list/':
+ if path == '/subscriptions/list/':
channel = title.lower().replace(',', '')
if channel in filter_list:
yt_context_menu.append_remove_my_subscriptions_filter(context_menu, provider, context, title)
@@ -187,6 +192,8 @@ def update_playlist_infos(provider, context, playlist_id_dict,
access_manager = context.get_access_manager()
custom_watch_later_id = access_manager.get_watch_later_id()
custom_history_id = access_manager.get_watch_history_id()
+ logged_in = provider.is_logged_in()
+ path = context.get_path()
thumb_size = context.get_settings().use_thumbnail_size()
for playlist_id, yt_item in data.items():
@@ -200,14 +207,14 @@ def update_playlist_infos(provider, context, playlist_id_dict,
channel_id = snippet['channelId']
# if the path directs to a playlist of our own, we correct the channel id to 'mine'
- if context.get_path() == '/channel/mine/playlists/':
+ if path == '/channel/mine/playlists/':
channel_id = 'mine'
channel_name = snippet.get('channelTitle', '')
context_menu = []
# play all videos of the playlist
yt_context_menu.append_play_all_from_playlist(context_menu, provider, context, playlist_id)
- if provider.is_logged_in():
+ if logged_in:
if channel_id != 'mine':
# subscribe to the channel via the playlist item
yt_context_menu.append_subscribe_to_channel(context_menu, provider, context, channel_id,
@@ -265,14 +272,15 @@ def update_video_infos(provider, context, video_id_dict,
playlist_item_id_dict = {}
settings = context.get_settings()
- show_details = settings.show_detailed_description()
alternate_player = settings.is_support_alternative_player_enabled()
+ logged_in = provider.is_logged_in()
+ path = context.get_path()
+ show_details = settings.show_detailed_description()
thumb_size = settings.use_thumbnail_size()
thumb_stamp = get_thumb_timestamp()
ui = context.get_ui()
for video_id, yt_item in data.items():
- datetime = None
video_item = video_id_dict[video_id]
# set mediatype
@@ -439,7 +447,7 @@ def update_video_infos(provider, context, video_id_dict,
/channel/[CHANNEL_ID]/playlist/[PLAYLIST_ID]/
/playlist/[PLAYLIST_ID]/
"""
- some_playlist_match = re.match(r'^(/channel/([^/]+))/playlist/(?P[^/]+)/$', context.get_path())
+ some_playlist_match = re.match(r'^(/channel/([^/]+))/playlist/(?P[^/]+)/$', path)
if some_playlist_match:
replace_context_menu = True
playlist_id = some_playlist_match.group('playlist_id')
@@ -451,7 +459,7 @@ def update_video_infos(provider, context, video_id_dict,
if alternate_player:
yt_context_menu.append_play_with(context_menu, provider, context)
- if provider.is_logged_in():
+ if logged_in:
# add 'Watch Later' only if we are not in my 'Watch Later' list
watch_later_playlist_id = context.get_access_manager().get_watch_later_id()
if watch_later_playlist_id:
@@ -459,7 +467,7 @@ def update_video_infos(provider, context, video_id_dict,
# provide 'remove' for videos in my playlists
if video_id in playlist_item_id_dict:
- playlist_match = re.match('^/channel/mine/playlist/(?P[^/]+)/$', context.get_path())
+ playlist_match = re.match('^/channel/mine/playlist/(?P[^/]+)/$', path)
if playlist_match:
playlist_id = playlist_match.group('playlist_id')
# we support all playlist except 'Watch History'
@@ -470,8 +478,10 @@ def update_video_infos(provider, context, video_id_dict,
context_menu.append((context.localize(provider.LOCAL_MAP['youtube.remove']),
'RunPlugin(%s)' % context.create_uri(
['playlist', 'remove', 'video'],
- {'playlist_id': playlist_id, 'video_id': playlist_item_id,
- 'video_name': video_item.get_name()})))
+ {'playlist_id': playlist_id,
+ 'video_id': playlist_item_id,
+ 'video_name': video_item.get_name()}
+ )))
is_history = re.match('^/special/watch_history_tv/$', context.get_path())
if is_history:
@@ -479,11 +489,11 @@ def update_video_infos(provider, context, video_id_dict,
# got to [CHANNEL], only if we are not directly in the channel provide a jump to the channel
if (channel_id and channel_name and
- utils.create_path('channel', channel_id) != context.get_path()):
+ utils.create_path('channel', channel_id) != path):
video_item.set_channel_id(channel_id)
yt_context_menu.append_go_to_channel(context_menu, provider, context, channel_id, channel_name)
- if provider.is_logged_in():
+ if logged_in:
# subscribe to the channel of the video
video_item.set_subscription_id(channel_id)
yt_context_menu.append_subscribe_to_channel(context_menu, provider, context, channel_id, channel_name)
@@ -498,16 +508,15 @@ def update_video_infos(provider, context, video_id_dict,
yt_context_menu.append_reset_resume_point(context_menu, provider, context, video_id)
# more...
- refresh_container = \
- context.get_path().startswith('/channel/mine/playlist/LL') or \
- context.get_path() == '/special/disliked_videos/'
- yt_context_menu.append_more_for_video(context_menu, provider, context, video_id,
- is_logged_in=provider.is_logged_in(),
+ refresh_container = (path.startswith('/channel/mine/playlist/LL')
+ or path == '/special/disliked_videos/')
+ yt_context_menu.append_more_for_video(context_menu, context, video_id,
+ is_logged_in=logged_in,
refresh_container=refresh_container)
if not video_item.live:
- yt_context_menu.append_play_with_subtitles(context_menu, provider, context, video_id)
- yt_context_menu.append_play_audio_only(context_menu, provider, context, video_id)
+ yt_context_menu.append_play_with_subtitles(context_menu, context, video_id)
+ yt_context_menu.append_play_audio_only(context_menu, context, video_id)
yt_context_menu.append_play_ask_for_quality(context_menu, provider, context, video_id)
@@ -515,22 +524,26 @@ def update_video_infos(provider, context, video_id_dict,
video_item.set_context_menu(context_menu, replace=replace_context_menu)
-def update_play_info(provider, context, video_id, video_item, video_stream, use_play_data=True):
+def update_play_info(provider, context, video_id, video_item, video_stream,
+ use_play_data=True):
+ video_item.video_id = video_id
+ update_video_infos(provider,
+ context,
+ {video_id: video_item},
+ use_play_data=use_play_data)
+
settings = context.get_settings()
ui = context.get_ui()
- resource_manager = provider.get_resource_manager(context)
-
- video_data = resource_manager.get_videos([video_id], suppress_errors=True)
meta_data = video_stream.get('meta', None)
- thumb_size = settings.use_thumbnail_size()
- image = None
-
- video_item.video_id = video_id
-
if meta_data:
video_item.set_subtitles(meta_data.get('subtitles', None))
- image = get_thumbnail(thumb_size, meta_data.get('images', {}))
+ image = get_thumbnail(settings.use_thumbnail_size(),
+ meta_data.get('images', {}))
+ if image:
+ if video_item.live:
+ image = ''.join([image, '?ct=', get_thumb_timestamp()])
+ video_item.set_image(image)
if 'headers' in video_stream:
video_item.set_headers(video_stream['headers'])
@@ -542,110 +555,16 @@ def update_play_info(provider, context, video_id, video_item, video_stream, use_
video_item.set_isa_video(settings.use_isa())
license_info = video_stream.get('license_info', {})
+ license_proxy = license_info.get('proxy', '')
+ license_url = license_info.get('url', '')
+ license_token = license_info.get('token', '')
- if inputstreamhelper and \
- license_info.get('proxy') and \
- license_info.get('url') and \
- license_info.get('token'):
- ishelper = inputstreamhelper.Helper('mpd', drm='com.widevine.alpha')
- ishelper.check_inputstream()
-
- video_item.set_license_key(license_info.get('proxy'))
- ui.set_home_window_property('license_url', license_info.get('url'))
- ui.set_home_window_property('license_token', license_info.get('token'))
-
- """
- This is experimental. We try to get the most information out of the title of a video.
- This is not based on any language. In some cases this won't work at all.
- TODO: via language and settings provide the regex for matching episode and season.
- """
-
- for regex in __RE_SEASON_EPISODE_MATCHES__:
- re_match = regex.search(video_item.get_name())
- if re_match:
- if 'season' in re_match.groupdict():
- video_item.set_season(int(re_match.group('season')))
-
- if 'episode' in re_match.groupdict():
- video_item.set_episode(int(re_match.group('episode')))
- break
-
- if video_item.live:
- video_item.set_play_count(0)
-
- if image:
- if video_item.live:
- image = ''.join([image, '?ct=', get_thumb_timestamp()])
- video_item.set_image(image)
-
- # set fanart
- video_item.set_fanart(provider.get_fanart(context))
-
- if not video_data:
- return video_item
-
- # requires API
- # ===============
- yt_item = video_data[video_id]
-
- snippet = yt_item['snippet'] # crash if not conform
- play_data = use_play_data and yt_item.get('play_data')
- video_item.live = snippet.get('liveBroadcastContent') == 'live'
-
- # set the title
- if not video_item.get_title():
- video_item.set_title(snippet['title'])
-
- # duration
- if not video_item.live and play_data and 'total_time' in play_data:
- duration = play_data['total_time']
- else:
- duration = yt_item.get('contentDetails', {}).get('duration')
- if duration:
- # subtract 1s because YouTube duration is +1s too long
- duration = utils.datetime_parser.parse(duration).seconds - 1
- if duration:
- video_item.set_duration_from_seconds(duration)
-
- if not video_item.live and play_data:
- if 'play_count' in play_data:
- video_item.set_play_count(play_data['play_count'])
-
- if 'played_percent' in play_data:
- video_item.set_start_percent(play_data['played_percent'])
-
- if 'played_time' in play_data:
- video_item.set_start_time(play_data['played_time'])
-
- if 'last_played' in play_data:
- video_item.set_last_played(play_data['last_played'])
-
- # plot
- channel_name = snippet.get('channelTitle', '')
- description = utils.strip_html_from_text(snippet['description'])
- if channel_name and settings.get_bool('youtube.view.description.show_channel_name', True):
- description = '%s[CR][CR]%s' % (ui.uppercase(ui.bold(channel_name)), description)
- video_item.set_studio(channel_name)
- # video_item.add_cast(channel_name)
- video_item.add_artist(channel_name)
- video_item.set_plot(description)
-
- # date time
- if 'publishedAt' in snippet and snippet['publishedAt']:
- date_time = utils.datetime_parser.parse(snippet['publishedAt'])
- video_item.set_year_from_datetime(date_time)
- video_item.set_aired_from_datetime(date_time)
- video_item.set_premiered_from_datetime(date_time)
- video_item.set_date_from_datetime(date_time)
-
- if not image:
- image = get_thumbnail(thumb_size, snippet.get('thumbnails', {}))
-
- if video_item.live and image:
- image = ''.join([image, '?ct=', get_thumb_timestamp()])
- video_item.set_image(image)
+ if ISHelper and license_proxy and license_url and license_token:
+ ISHelper('mpd', drm='com.widevine.alpha').check_inputstream()
- return video_item
+ video_item.set_license_key(license_proxy)
+ ui.set_home_window_property('license_url', license_url)
+ ui.set_home_window_property('license_token', license_token)
def update_fanarts(provider, context, channel_items_dict, data=None):
diff --git a/resources/lib/youtube_plugin/youtube/helper/yt_play.py b/resources/lib/youtube_plugin/youtube/helper/yt_play.py
index a2c126327..75e97e5c0 100644
--- a/resources/lib/youtube_plugin/youtube/helper/yt_play.py
+++ b/resources/lib/youtube_plugin/youtube/helper/yt_play.py
@@ -85,8 +85,8 @@ def play_video(provider, context):
use_remote_history = use_history and settings.use_remote_history()
use_play_data = use_history and settings.use_local_history()
- video_item = utils.update_play_info(provider, context, video_id, video_item, video_stream,
- use_play_data=use_play_data)
+ utils.update_play_info(provider, context, video_id, video_item,
+ video_stream, use_play_data=use_play_data)
seek_time = None
play_count = 0
From 483dfad4779e529afd284437d812070a384d6776 Mon Sep 17 00:00:00 2001
From: MoojMidge <56883549+MoojMidge@users.noreply.github.com>
Date: Thu, 23 Nov 2023 10:37:03 +1100
Subject: [PATCH 038/141] Update UrlToItemConverter for timestamps
- Also support live videos and playlists
- Partially fix #115
- Fix #502
- Fix #538
- Remove unnecessary seek on start when resuming
---
.../youtube/helper/url_resolver.py | 14 +-
.../youtube/helper/url_to_item_converter.py | 143 +++++++++++-------
.../youtube_plugin/youtube/helper/yt_play.py | 18 +--
.../lib/youtube_plugin/youtube/provider.py | 2 +-
4 files changed, 107 insertions(+), 70 deletions(-)
diff --git a/resources/lib/youtube_plugin/youtube/helper/url_resolver.py b/resources/lib/youtube_plugin/youtube/helper/url_resolver.py
index fe6750c34..ffca68a24 100644
--- a/resources/lib/youtube_plugin/youtube/helper/url_resolver.py
+++ b/resources/lib/youtube_plugin/youtube/helper/url_resolver.py
@@ -139,13 +139,13 @@ def _loop(_url, tries=5):
if _url_components.path == '/supported_browsers':
# "sometimes", we get a redirect through an URL of the form https://.../supported_browsers?next_url=&further=paramaters&stuck=here
# put together query string from both what's encoded inside next_url and the remaining paramaters of this URL...
- _query = parse_qs(_url_components.query) # top-level query string
- _nc = urlparse(_query['next_url'][0]) # components of next_url
- _next_query = parse_qs(_nc.query) # query string encoded inside next_url
- del _query['next_url'] # remove next_url from top level query string
- _next_query.update(_query) # add/overwrite all other params from top level query string
- _next_query = dict(map(lambda kv: (kv[0], kv[1][0]), _next_query.items())) # flatten to only use first argument of each param
- _next_url = urlunsplit((_nc.scheme, _nc.netloc, _nc.path, urlencode(_next_query), _nc.fragment)) # build new URL from these components
+ _query = parse_qs(_url_components.query) # top-level query string
+ _nc = urlparse(_query['next_url'][0]) # components of next_url
+ _next_query = parse_qs(_nc.query) # query string encoded inside next_url
+ del _query['next_url'] # remove next_url from top level query string
+ _next_query.update(_query) # add/overwrite all other params from top level query string
+ _next_query = dict(map(lambda kv: (kv[0], kv[1][0]), _next_query.items())) # flatten to only use first argument of each param
+ _next_url = urlunsplit((_nc.scheme, _nc.netloc, _nc.path, urlencode(_next_query), _nc.fragment)) # build new URL from these components
return _next_url
except:
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 068d853b0..d26ded6b3 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
@@ -17,8 +17,15 @@
class UrlToItemConverter(object):
- RE_CHANNEL_ID = re.compile(r'^/channel/(?P.+)$')
- RE_SHORTS_VID = re.compile(r'^/shorts/(?P.+)$')
+ RE_CHANNEL_ID = re.compile(r'^/channel/(?P.+)$', re.I)
+ RE_LIVE_VID = re.compile(r'^/live/(?P.+)$', re.I)
+ RE_SHORTS_VID = re.compile(r'^/shorts/(?P[^?/]+)$', re.I)
+ RE_SEEK_TIME = re.compile(r'\d+')
+ VALID_HOSTNAMES = {
+ 'youtube.com',
+ 'www.youtube.com',
+ 'm.youtube.com',
+ }
def __init__(self, flatten=True):
self._flatten = flatten
@@ -35,50 +42,78 @@ def __init__(self, flatten=True):
self._channel_ids = []
def add_url(self, url, provider, context):
- url_components = urlparse(url)
- if url_components.hostname.lower() in ('youtube.com', 'www.youtube.com', 'm.youtube.com'):
- params = dict(parse_qsl(url_components.query))
- if url_components.path.lower() == '/watch':
- video_id = params.get('v', '')
- if video_id:
- plugin_uri = context.create_uri(['play'], {'video_id': video_id})
- video_item = VideoItem('', plugin_uri)
- self._video_id_dict[video_id] = video_item
-
- playlist_id = params.get('list', '')
- if playlist_id:
- if self._flatten:
- self._playlist_ids.append(playlist_id)
- else:
- playlist_item = DirectoryItem('', context.create_uri(['playlist', playlist_id]))
- playlist_item.set_fanart(provider.get_fanart(context))
- self._playlist_id_dict[playlist_id] = playlist_item
- elif url_components.path.lower() == '/playlist':
- playlist_id = params.get('list', '')
- if playlist_id:
- if self._flatten:
- self._playlist_ids.append(playlist_id)
- else:
- playlist_item = DirectoryItem('', context.create_uri(['playlist', playlist_id]))
- playlist_item.set_fanart(provider.get_fanart(context))
- self._playlist_id_dict[playlist_id] = playlist_item
- elif self.RE_SHORTS_VID.match(url_components.path):
- re_match = self.RE_SHORTS_VID.match(url_components.path)
- video_id = re_match.group('video_id')
- plugin_uri = context.create_uri(['play'], {'video_id': video_id})
- video_item = VideoItem('', plugin_uri)
- self._video_id_dict[video_id] = video_item
- elif self.RE_CHANNEL_ID.match(url_components.path):
- re_match = self.RE_CHANNEL_ID.match(url_components.path)
- channel_id = re_match.group('channel_id')
- if self._flatten:
- self._channel_ids.append(channel_id)
- else:
- channel_item = DirectoryItem('', context.create_uri(['channel', channel_id]))
- channel_item.set_fanart(provider.get_fanart(context))
- self._channel_id_dict[channel_id] = channel_item
+ parsed_url = urlparse(url)
+ if parsed_url.hostname.lower() not in self.VALID_HOSTNAMES:
+ context.log_debug('Unknown hostname "{0}" in url "{1}"'.format(
+ parsed_url.hostname, url
+ ))
+ return
+
+ params = dict(parse_qsl(parsed_url.query))
+ path = parsed_url.path.lower()
+
+ video_id = playlist_id = channel_id = seek_time = None
+ if path == '/watch':
+ video_id = params.get('v')
+ playlist_id = params.get('list')
+ seek_time = params.get('t')
+ elif path == '/playlist':
+ playlist_id = params.get('list')
+ elif path.startswith('/shorts/'):
+ re_match = self.RE_SHORTS_VID.match(parsed_url.path)
+ video_id = re_match.group('video_id')
+ elif path.startswith('/channel/'):
+ re_match = self.RE_CHANNEL_ID.match(parsed_url.path)
+ channel_id = re_match.group('channel_id')
+ elif path.startswith('/live/'):
+ re_match = self.RE_LIVE_VID.match(parsed_url.path)
+ video_id = re_match.group('video_id')
+ else:
+ context.log_debug('Unknown path "{0}" in url "{1}"'.format(
+ parsed_url.path, url
+ ))
+ return
+
+ if video_id:
+ plugin_params = {
+ 'video_id': video_id,
+ }
+ if seek_time:
+ seek_time = sum(
+ int(number) * seconds_per_unit
+ for number, seconds_per_unit in zip(
+ reversed(re.findall(self.RE_SEEK_TIME, seek_time)),
+ (1, 60, 3600, 86400)
+ )
+ if number
+ )
+ plugin_params['seek'] = seek_time
+ plugin_uri = context.create_uri(['play'], plugin_params)
+ video_item = VideoItem('', plugin_uri)
+ self._video_id_dict[video_id] = video_item
+
+ elif playlist_id:
+ if self._flatten:
+ self._playlist_ids.append(playlist_id)
else:
- context.log_debug('Unknown path "%s"' % url_components.path)
+ playlist_item = DirectoryItem(
+ '', context.create_uri(['playlist', playlist_id])
+ )
+ playlist_item.set_fanart(provider.get_fanart(context))
+ self._playlist_id_dict[playlist_id] = playlist_item
+
+ elif channel_id:
+ if self._flatten:
+ self._channel_ids.append(channel_id)
+ else:
+ channel_item = DirectoryItem(
+ '', context.create_uri(['channel', channel_id])
+ )
+ channel_item.set_fanart(provider.get_fanart(context))
+ self._channel_id_dict[channel_id] = channel_item
+
+ else:
+ context.log_debug('No items found in url "{0}"'.format(url))
def add_urls(self, urls, provider, context):
for url in urls:
@@ -91,10 +126,12 @@ def get_items(self, provider, context, title_required=True):
# remove duplicates
self._channel_ids = list(set(self._channel_ids))
- channels_item = DirectoryItem(context.get_ui().bold(context.localize(provider.LOCAL_MAP['youtube.channels'])),
- context.create_uri(['special', 'description_links'],
- {'channel_ids': ','.join(self._channel_ids)}),
- context.create_resource_path('media', 'playlist.png'))
+ channels_item = DirectoryItem(
+ context.get_ui().bold(context.localize(provider.LOCAL_MAP['youtube.channels'])),
+ context.create_uri(['special', 'description_links'],
+ {'channel_ids': ','.join(self._channel_ids)}),
+ context.create_resource_path('media', 'playlist.png')
+ )
channels_item.set_fanart(provider.get_fanart(context))
result.append(channels_item)
@@ -126,7 +163,9 @@ def get_video_items(self, provider, context, title_required=True):
if not self._video_items:
channel_id_dict = {}
- utils.update_video_infos(provider, context, self._video_id_dict, None, 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,
+ use_play_data=use_play_data)
utils.update_fanarts(provider, context, channel_id_dict)
self._video_items = [
@@ -140,7 +179,9 @@ def get_video_items(self, provider, context, title_required=True):
def get_playlist_items(self, provider, context):
if not self._playlist_items:
channel_id_dict = {}
- utils.update_playlist_infos(provider, context, self._playlist_id_dict, channel_id_dict)
+ utils.update_playlist_infos(provider, context,
+ self._playlist_id_dict,
+ channel_items_dict=channel_id_dict)
utils.update_fanarts(provider, context, channel_id_dict)
self._playlist_items = [
diff --git a/resources/lib/youtube_plugin/youtube/helper/yt_play.py b/resources/lib/youtube_plugin/youtube/helper/yt_play.py
index 75e97e5c0..3d21507f3 100644
--- a/resources/lib/youtube_plugin/youtube/helper/yt_play.py
+++ b/resources/lib/youtube_plugin/youtube/helper/yt_play.py
@@ -88,26 +88,22 @@ def play_video(provider, context):
utils.update_play_info(provider, context, video_id, video_item,
video_stream, use_play_data=use_play_data)
- seek_time = None
+ seek_time = 0.0
play_count = 0
playback_stats = video_stream.get('playback_stats')
+ if not context.get_param('resume'):
+ try:
+ seek_time = context.get_param('seek', 0.0)
+ except (ValueError, TypeError):
+ pass
+
if use_play_data:
- seek = video_item.get_start_time()
- if seek and context.get_param('resume'):
- seek_time = start_time
play_count = video_item.get_play_count() or 0
item = to_playback_item(context, video_item)
item.setPath(video_item.get_uri())
- try:
- seek = float(context.get_param('seek', None))
- if seek:
- seek_time = seek
- except (ValueError, TypeError):
- pass
-
playback_json = {
"video_id": video_id,
"channel_id": metadata.get('channel', {}).get('id', ''),
diff --git a/resources/lib/youtube_plugin/youtube/provider.py b/resources/lib/youtube_plugin/youtube/provider.py
index a5a9c433c..a64685dd6 100644
--- a/resources/lib/youtube_plugin/youtube/provider.py
+++ b/resources/lib/youtube_plugin/youtube/provider.py
@@ -436,7 +436,7 @@ def on_uri2addon(self, context, re_match):
resolver = UrlResolver(context)
res_url = resolver.resolve(uri)
url_converter = UrlToItemConverter(flatten=True)
- url_converter.add_urls([res_url], self, context)
+ url_converter.add_url(res_url, self, context)
items = url_converter.get_items(self, context, title_required=False)
if items:
return items[0]
From 4bc2d3943e74e3ca57b3bfabb5f4dd7dcb8d914b Mon Sep 17 00:00:00 2001
From: MoojMidge <56883549+MoojMidge@users.noreply.github.com>
Date: Thu, 23 Nov 2023 14:52:45 +1100
Subject: [PATCH 039/141] Consolidate localisation ids
- Move LOCAL_MAP from provider to context as that is where it is used
- Remove const_localize (redundant when using LOCAL_MAP)
- Remove _local_map (redundant when using LOCAL_MAP)
- Update and sort id names
- Replace hardcoded integer ids with id names
- Tidy up provider
---
.patches/unofficial.patch | 2 +-
.../kodion/abstract_provider.py | 20 +-
.../kodion/constants/__init__.py | 3 +-
.../kodion/constants/const_localize.py | 46 -
.../kodion/context/xbmc/xbmc_context.py | 224 ++++-
.../kodion/items/favorites_item.py | 2 +-
.../kodion/items/new_search_item.py | 2 +-
.../kodion/items/next_page_item.py | 3 +-
.../kodion/items/search_history_item.py | 16 +-
.../kodion/items/search_item.py | 6 +-
.../kodion/items/watch_later_item.py | 2 +-
.../kodion/ui/xbmc/xbmc_context_ui.py | 9 +-
.../kodion/ui/xbmc/xbmc_items.py | 2 +-
.../kodion/utils/datetime_parser.py | 50 +-
.../youtube_plugin/kodion/utils/methods.py | 4 +-
.../youtube/helper/resource_manager.py | 2 +-
.../youtube/helper/subtitles.py | 2 +-
.../youtube/helper/url_to_item_converter.py | 11 +-
.../youtube_plugin/youtube/helper/utils.py | 71 +-
.../lib/youtube_plugin/youtube/helper/v3.py | 32 +-
.../youtube/helper/video_info.py | 23 +-
.../youtube/helper/yt_context_menu.py | 116 +--
.../youtube_plugin/youtube/helper/yt_login.py | 10 +-
.../youtube_plugin/youtube/helper/yt_play.py | 19 +-
.../youtube/helper/yt_playlist.py | 36 +-
.../youtube/helper/yt_setup_wizard.py | 96 +--
.../youtube/helper/yt_specials.py | 25 +-
.../youtube/helper/yt_subscriptions.py | 10 +-
.../youtube_plugin/youtube/helper/yt_video.py | 31 +-
.../lib/youtube_plugin/youtube/provider.py | 784 ++++++++----------
30 files changed, 858 insertions(+), 801 deletions(-)
delete mode 100644 resources/lib/youtube_plugin/kodion/constants/const_localize.py
diff --git a/.patches/unofficial.patch b/.patches/unofficial.patch
index 758a8d719..32e255cce 100644
--- a/.patches/unofficial.patch
+++ b/.patches/unofficial.patch
@@ -34,7 +34,7 @@ index ff8ffd44..1306ca2c 100644
def _process_wizard(self, context):
+ def _setup_views(_context, _view):
+ view_manager = utils.ViewManager(_context)
-+ if not view_manager.update_view_mode(_context.localize(self._local_map['kodion.wizard.view.%s' % _view]),
++ if not view_manager.update_view_mode(_context.localize('setup_wizard.view.%s' % _view]),
+ _view):
+ return
+
diff --git a/resources/lib/youtube_plugin/kodion/abstract_provider.py b/resources/lib/youtube_plugin/kodion/abstract_provider.py
index 0a21812eb..15c150ece 100644
--- a/resources/lib/youtube_plugin/kodion/abstract_provider.py
+++ b/resources/lib/youtube_plugin/kodion/abstract_provider.py
@@ -30,16 +30,6 @@ class AbstractProvider(object):
RESULT_UPDATE_LISTING = 'update_listing'
def __init__(self):
- self._local_map = {
- 'kodion.wizard.view.default': 30027,
- 'kodion.wizard.view.episodes': 30028,
- 'kodion.wizard.view.movies': 30029,
- 'kodion.wizard.view.tvshows': 30032,
- 'kodion.wizard.view.songs': 30033,
- 'kodion.wizard.view.artists': 30034,
- 'kodion.wizard.view.albums': 30035
- }
-
# map for regular expression (path) to method (names)
self._dict_path = {}
@@ -85,7 +75,7 @@ def _process_wizard(self, context):
wizard_steps.extend(self.get_wizard_steps(context))
if wizard_steps and context.get_ui().on_yes_no_input(context.get_name(),
- context.localize(constants.localize.SETUP_WIZARD_EXECUTE)):
+ context.localize('setup_wizard.execute')):
for wizard_step in wizard_steps:
wizard_step[0](*wizard_step[1])
@@ -162,7 +152,7 @@ def _internal_favorite(context, re_match):
directory_items = context.get_favorite_list().get_items()
for directory_item in directory_items:
- context_menu = [(context.localize(constants.localize.WATCH_LATER_REMOVE),
+ context_menu = [(context.localize('watch_later.remove'),
'RunPlugin(%s)' % context.create_uri([constants.paths.FAVORITES, 'remove'],
params={'item': to_jsons(directory_item)}))]
directory_item.set_context_menu(context_menu)
@@ -189,7 +179,7 @@ def _internal_watch_later(self, context, re_match):
video_items = context.get_watch_later_list().get_items()
for video_item in video_items:
- context_menu = [(context.localize(constants.localize.WATCH_LATER_REMOVE),
+ context_menu = [(context.localize('watch_later.remove'),
'RunPlugin(%s)' % context.create_uri([constants.paths.WATCH_LATER, 'remove'],
params={'item': to_jsons(video_item)}))]
video_item.set_context_menu(context_menu)
@@ -220,7 +210,7 @@ def _internal_search(self, context, re_match):
return True
if command == 'rename':
query = params['q']
- result, new_query = context.get_ui().on_keyboard_input(context.localize(constants.localize.SEARCH_RENAME),
+ result, new_query = context.get_ui().on_keyboard_input(context.localize('search.rename'),
query)
if result:
search_history.rename(query, new_query)
@@ -244,7 +234,7 @@ def _internal_search(self, context, re_match):
query = to_unicode(query)
query = unquote(query)
else:
- result, input_query = context.get_ui().on_keyboard_input(context.localize(constants.localize.SEARCH_TITLE))
+ result, input_query = context.get_ui().on_keyboard_input(context.localize('search.title'))
if result:
query = input_query
diff --git a/resources/lib/youtube_plugin/kodion/constants/__init__.py b/resources/lib/youtube_plugin/kodion/constants/__init__.py
index 83f50817b..655985e9b 100644
--- a/resources/lib/youtube_plugin/kodion/constants/__init__.py
+++ b/resources/lib/youtube_plugin/kodion/constants/__init__.py
@@ -9,10 +9,9 @@
"""
from . import const_settings as setting
-from . import const_localize as localize
from . import const_sort_methods as sort_method
from . import const_content_types as content_type
from . import const_paths as paths
-__all__ = ['setting', 'localize', 'sort_method', 'content_type', 'paths']
+__all__ = ['setting', 'sort_method', 'content_type', 'paths']
diff --git a/resources/lib/youtube_plugin/kodion/constants/const_localize.py b/resources/lib/youtube_plugin/kodion/constants/const_localize.py
deleted file mode 100644
index e944a0fa0..000000000
--- a/resources/lib/youtube_plugin/kodion/constants/const_localize.py
+++ /dev/null
@@ -1,46 +0,0 @@
-# -*- coding: utf-8 -*-
-"""
-
- Copyright (C) 2014-2016 bromix (plugin.video.youtube)
- Copyright (C) 2016-2018 plugin.video.youtube
-
- SPDX-License-Identifier: GPL-2.0-only
- See LICENSES/GPL-2.0-only for more information.
-"""
-
-SELECT_VIDEO_QUALITY = 30010
-
-COMMON_PLEASE_WAIT = 30119
-
-FAVORITES = 30100
-FAVORITES_ADD = 30101
-FAVORITES_REMOVE = 30108
-
-SEARCH = 30102
-SEARCH_TITLE = 30102
-SEARCH_NEW = 30110
-SEARCH_RENAME = 30113
-SEARCH_REMOVE = 30108
-SEARCH_CLEAR = 30120
-
-SETUP_WIZARD_EXECUTE = 30030
-SETUP_VIEW_DEFAULT = 30027
-SETUP_VIEW_VIDEOS = 30028
-
-LIBRARY = 30103
-HIGHLIGHTS = 30104
-ARCHIVE = 30105
-NEXT_PAGE = 30106
-
-WATCH_LATER = 30107
-WATCH_LATER_ADD = 30107
-WATCH_LATER_REMOVE = 30108
-
-LATEST_VIDEOS = 30109
-
-CONFIRM_DELETE = 30114
-CONFIRM_REMOVE = 30115
-DELETE_CONTENT = 30116
-REMOVE_CONTENT = 30117
-
-WATCH_LATER_RETRIEVAL_PAGE = 30711
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 aa0c6c9bc..0ab4d9236 100644
--- a/resources/lib/youtube_plugin/kodion/context/xbmc/xbmc_context.py
+++ b/resources/lib/youtube_plugin/kodion/context/xbmc/xbmc_context.py
@@ -35,6 +35,217 @@
class XbmcContext(AbstractContext):
+ LOCAL_MAP = {
+ 'api.id': 30202,
+ 'api.key': 30201,
+ 'api.key.incorrect': 30648,
+ 'api.personal.enabled': 30598,
+ 'api.personal.failed': 30599,
+ 'api.secret': 30203,
+ 'archive': 30105,
+ 'are_you_sure': 30703,
+ 'browse_channels': 30512,
+ 'cache.data': 30687,
+ 'cache.function': 30557,
+ 'cancel': 30615,
+ 'channels': 30500,
+ 'clear_history': 30609,
+ 'clear_history_confirmation': 30610,
+ 'client.id.incorrect': 30649,
+ 'client.ip': 30700,
+ 'client.ip.failed': 30701,
+ 'client.secret.incorrect': 30650,
+ 'content.delete': 30116,
+ 'content.delete.confirm': 30114,
+ 'content.remove': 30117,
+ 'content.remove.confirm': 30115,
+ 'datetime.a_minute_ago': 30677,
+ 'datetime.airing_now': 30691,
+ 'datetime.airing_soon': 30693,
+ 'datetime.airing_today_at': 30696,
+ 'datetime.an_hour_ago': 30679,
+ 'datetime.in_a_minute': 30692,
+ 'datetime.in_over_an_hour': 30694,
+ 'datetime.in_over_two_hours': 30695,
+ 'datetime.just_now': 30676,
+ 'datetime.recently': 30678,
+ 'datetime.three_hours_ago': 30681,
+ 'datetime.today_at': 30684,
+ 'datetime.tomorrow_at': 30697,
+ 'datetime.two_days_ago': 30683,
+ 'datetime.two_hours_ago': 30680,
+ 'datetime.yesterday_at': 30682,
+ 'delete': 30118,
+ 'disliked.video': 30717,
+ 'error.no_video_streams_found': 30549,
+ 'error.rtmpe_not_supported': 30542,
+ 'failed': 30576,
+ 'failed.watch_later.retry': 30614,
+ 'failed.watch_later.retry.2': 30709,
+ 'failed.watch_later.retry.3': 30710,
+ 'favorites': 30100,
+ 'favorites.add': 30101,
+ 'favorites.remove': 30108,
+ 'go_to_channel': 30502,
+ 'highlights': 30104,
+ 'history': 30509,
+ 'history.list.remove': 30572,
+ 'history.list.remove.confirm': 30573,
+ 'history.list.set': 30571,
+ 'history.list.set.confirm': 30574,
+ 'httpd.not.running': 30699,
+ 'inputstreamhelper.is_installed': 30625,
+ 'isa.enable.confirm': 30579,
+ 'key.requirement.notification': 30731,
+ 'latest_videos': 30109,
+ 'library': 30103,
+ 'liked.video': 30716,
+ 'live': 30539,
+ 'live.completed': 30647,
+ 'live.upcoming': 30646,
+ 'mark.unwatched': 30669,
+ 'mark.watched': 30670,
+ 'must_be_signed_in': 30616,
+ 'my_channel': 30507,
+ 'my_location': 30654,
+ 'my_subscriptions': 30510,
+ 'my_subscriptions.filter.add': 30587,
+ 'my_subscriptions.filter.added': 30589,
+ 'my_subscriptions.filter.remove': 30588,
+ 'my_subscriptions.filter.removed': 30590,
+ 'my_subscriptions.filtered': 30584,
+ 'next_page': 30106,
+ 'none': 30561,
+ 'perform_geolocation': 30653,
+ 'playback.history': 30673,
+ 'playlist.added_to': 30714,
+ 'playlist.create': 30522,
+ 'playlist.play.all': 30531,
+ 'playlist.play.default': 30532,
+ 'playlist.play.from_here': 30537,
+ 'playlist.play.reverse': 30533,
+ 'playlist.play.select': 30535,
+ 'playlist.play.shuffle': 30534,
+ 'playlist.progress.updating': 30536,
+ 'playlist.removed_from': 30715,
+ 'playlist.select': 30521,
+ 'playlists': 30501,
+ 'please_wait': 30119,
+ 'popular_right_now': 30513,
+ 'prompt': 30566,
+ 'purchases': 30622,
+ 'recommendations': 30551,
+ 'refresh': 30543,
+ 'related_videos': 30514,
+ 'remove': 30108,
+ 'removed': 30666,
+ 'rename': 30113,
+ 'renamed': 30667,
+ 'requires.krypton': 30624,
+ 'reset.access_manager.confirm': 30581,
+ 'reset.resume_point': 30674,
+ 'retry': 30612,
+ 'saved.playlists': 30611,
+ 'search': 30102,
+ 'search.clear': 30120,
+ 'search.history': 30558,
+ 'search.new': 30110,
+ 'search.quick': 30605,
+ 'search.quick.incognito': 30606,
+ 'search.remove': 30108,
+ 'search.rename': 30113,
+ 'search.title': 30102,
+ 'select.listen.ip': 30644,
+ 'select_video_quality': 30010,
+ 'setting.auto_remove_watch_later': 30515,
+ 'settings': 30577,
+ 'setup.view_default': 30027,
+ 'setup.view_videos': 30028,
+ 'setup_wizard.adjust': 30526,
+ 'setup_wizard.adjust.language_and_region': 30527,
+ 'setup_wizard.execute': 30030,
+ 'setup_wizard.select_language': 30524,
+ 'setup_wizard.select_region': 30525,
+ 'sign.enter_code': 30519,
+ 'sign.go_to': 30518,
+ 'sign.in': 30111,
+ 'sign.out': 30112,
+ 'sign.twice.text': 30547,
+ 'sign.twice.title': 30546,
+ 'stats.commentCount': 30732,
+ 'stats.favoriteCount': 30100,
+ 'stats.likeCount': 30733,
+ 'stats.viewCount': 30767,
+ 'stream.alternate': 30747,
+ 'stream.automatic': 30583,
+ 'stream.descriptive': 30746,
+ 'stream.dubbed': 30745,
+ 'stream.multi_audio': 30763,
+ 'stream.multi_language': 30762,
+ 'stream.original': 30744,
+ 'subscribe': 30506,
+ 'subscribe_to': 30517,
+ 'subscribed.to.channel': 30719,
+ 'subscriptions': 30504,
+ 'subtitles.download': 30705,
+ 'subtitles.download.pre': 30706,
+ 'subtitles.language': 30560,
+ 'subtitles.no_auto_generated': 30602,
+ 'subtitles.with_fallback': 30601,
+ 'succeeded': 30575,
+ 'unrated.video': 30718,
+ 'unsubscribe': 30505,
+ 'unsubscribed.from.channel': 30720,
+ 'untitled': 30707,
+ 'upcoming': 30766,
+ 'updated_': 30597,
+ 'uploads': 30726,
+ 'user.changed': 30659,
+ 'user.enter_name': 30658,
+ 'user.new': 30656,
+ 'user.remove': 30662,
+ 'user.rename': 30663,
+ 'user.switch': 30655,
+ 'user.switch.now': 30665,
+ 'user.unnamed': 30657,
+ 'video.add_to_playlist': 30520,
+ 'video.comments': 30732,
+ 'video.comments.edited': 30735,
+ 'video.comments.likes': 30733,
+ 'video.comments.replies': 30734,
+ 'video.description.links': 30544,
+ 'video.description.links.not_found': 30545,
+ 'video.disliked': 30538,
+ 'video.liked': 30508,
+ 'video.more': 30548,
+ 'video.play.ask_for_quality': 30730,
+ 'video.play.audio_only': 30708,
+ 'video.play.with': 30540,
+ 'video.play.with_subtitles': 30702,
+ 'video.queue': 30511,
+ 'video.rate': 30528,
+ 'video.rate.dislike': 30530,
+ 'video.rate.like': 30529,
+ 'video.rate.none': 30108,
+ 'watch_later': 30107,
+ 'watch_later.add': 30107,
+ 'watch_later.added_to': 30713,
+ 'watch_later.list.remove': 30568,
+ 'watch_later.list.remove.confirm': 30569,
+ 'watch_later.list.set': 30567,
+ 'watch_later.list.set.confirm': 30570,
+ 'watch_later.remove': 30108,
+ 'watch_later.retrieval_page': 30711,
+ # For unofficial setup wizard
+ 'setup_wizard.view.episodes': 30028,
+ 'setup_wizard.view.movies': 30029,
+ 'setup_wizard.view.tvshows': 30032,
+ 'setup_wizard.view.default': 30027,
+ 'setup_wizard.view.songs': 30033,
+ 'setup_wizard.view.artists': 30034,
+ 'setup_wizard.view.albums': 30035
+ }
+
def __init__(self, path='/', params=None, plugin_name='', plugin_id='', override=True):
super(XbmcContext, self).__init__(path, params, plugin_name, plugin_id)
@@ -104,7 +315,7 @@ def format_date_short(date_obj, short_isoformat=False):
if isinstance(date_obj, datetime.datetime):
date_obj = date_obj.date()
return date_obj.isoformat()
-
+
date_format = xbmc.getRegion('dateshort')
_date_obj = date_obj
if isinstance(_date_obj, datetime.date):
@@ -197,9 +408,12 @@ def get_settings(self):
def localize(self, text_id, default_text=''):
if not isinstance(text_id, int):
try:
- text_id = int(text_id)
- except ValueError:
- return default_text
+ text_id = self.LOCAL_MAP[text_id]
+ except KeyError:
+ try:
+ text_id = int(text_id)
+ except ValueError:
+ return default_text
if text_id <= 0:
return default_text
@@ -291,7 +505,7 @@ def use_inputstream_adaptive(self):
if self._settings.use_isa():
if self.addon_enabled('inputstream.adaptive'):
success = True
- elif self.get_ui().on_yes_no_input(self.get_name(), self.localize(30579)):
+ elif self.get_ui().on_yes_no_input(self.get_name(), self.localize('isa.enable.confirm')):
success = self.set_addon_enabled('inputstream.adaptive')
else:
success = False
diff --git a/resources/lib/youtube_plugin/kodion/items/favorites_item.py b/resources/lib/youtube_plugin/kodion/items/favorites_item.py
index acb939436..a32c5b756 100644
--- a/resources/lib/youtube_plugin/kodion/items/favorites_item.py
+++ b/resources/lib/youtube_plugin/kodion/items/favorites_item.py
@@ -16,7 +16,7 @@ class FavoritesItem(DirectoryItem):
def __init__(self, context, alt_name=None, image=None, fanart=None):
name = alt_name
if not name:
- name = context.localize(constants.localize.FAVORITES)
+ name = context.localize('favorites')
if image is None:
image = context.create_resource_path('media/favorites.png')
diff --git a/resources/lib/youtube_plugin/kodion/items/new_search_item.py b/resources/lib/youtube_plugin/kodion/items/new_search_item.py
index 5499e598c..f0985d96d 100644
--- a/resources/lib/youtube_plugin/kodion/items/new_search_item.py
+++ b/resources/lib/youtube_plugin/kodion/items/new_search_item.py
@@ -16,7 +16,7 @@ class NewSearchItem(DirectoryItem):
def __init__(self, context, alt_name=None, image=None, fanart=None, incognito=False, channel_id='', addon_id='', location=False):
name = alt_name
if not name:
- name = context.get_ui().bold(context.localize(constants.localize.SEARCH_NEW))
+ name = context.get_ui().bold(context.localize('search.new'))
if image is None:
image = context.create_resource_path('media/new_search.png')
diff --git a/resources/lib/youtube_plugin/kodion/items/next_page_item.py b/resources/lib/youtube_plugin/kodion/items/next_page_item.py
index 364baa36c..fc4b83576 100644
--- a/resources/lib/youtube_plugin/kodion/items/next_page_item.py
+++ b/resources/lib/youtube_plugin/kodion/items/next_page_item.py
@@ -9,7 +9,6 @@
"""
from .directory_item import DirectoryItem
-from .. import constants
class NextPageItem(DirectoryItem):
@@ -17,7 +16,7 @@ def __init__(self, context, current_page=1, image=None, fanart=None):
new_params = {}
new_params.update(context.get_params())
new_params['page'] = current_page + 1
- name = context.localize(constants.localize.NEXT_PAGE, 'Next Page')
+ name = context.localize('next_page', 'Next Page')
if name.find('%d') != -1:
name %= current_page + 1
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 10b8c7646..42e9f7c1f 100644
--- a/resources/lib/youtube_plugin/kodion/items/search_history_item.py
+++ b/resources/lib/youtube_plugin/kodion/items/search_history_item.py
@@ -9,7 +9,7 @@
"""
from .directory_item import DirectoryItem
-from .. import constants
+from ..constants.const_paths import SEARCH
class SearchHistoryItem(DirectoryItem):
@@ -21,16 +21,16 @@ def __init__(self, context, query, image=None, fanart=None, location=False):
if location:
params['location'] = location
- super(SearchHistoryItem, self).__init__(query, context.create_uri([constants.paths.SEARCH, 'query'], params=params), image=image)
+ super(SearchHistoryItem, self).__init__(query, context.create_uri([SEARCH, 'query'], params=params), image=image)
if fanart:
self.set_fanart(fanart)
else:
self.set_fanart(context.get_fanart())
- context_menu = [(context.localize(constants.localize.SEARCH_REMOVE),
- 'RunPlugin(%s)' % context.create_uri([constants.paths.SEARCH, 'remove'], params={'q': query})),
- (context.localize(constants.localize.SEARCH_RENAME),
- 'RunPlugin(%s)' % context.create_uri([constants.paths.SEARCH, 'rename'], params={'q': query})),
- (context.localize(constants.localize.SEARCH_CLEAR),
- 'RunPlugin(%s)' % context.create_uri([constants.paths.SEARCH, 'clear']))]
+ context_menu = [(context.localize('search.remove'),
+ 'RunPlugin(%s)' % context.create_uri([SEARCH, 'remove'], params={'q': query})),
+ (context.localize('search.rename'),
+ 'RunPlugin(%s)' % context.create_uri([SEARCH, 'rename'], params={'q': query})),
+ (context.localize('search.clear'),
+ 'RunPlugin(%s)' % context.create_uri([SEARCH, 'clear']))]
self.set_context_menu(context_menu)
diff --git a/resources/lib/youtube_plugin/kodion/items/search_item.py b/resources/lib/youtube_plugin/kodion/items/search_item.py
index 51e292dd2..39869c2c0 100644
--- a/resources/lib/youtube_plugin/kodion/items/search_item.py
+++ b/resources/lib/youtube_plugin/kodion/items/search_item.py
@@ -9,21 +9,21 @@
"""
from .directory_item import DirectoryItem
-from .. import constants
+from ..constants.const_paths import SEARCH
class SearchItem(DirectoryItem):
def __init__(self, context, alt_name=None, image=None, fanart=None, location=False):
name = alt_name
if not name:
- name = context.localize(constants.localize.SEARCH)
+ name = context.localize('search')
if image is None:
image = context.create_resource_path('media/search.png')
params = {'location': location} if location else {}
- super(SearchItem, self).__init__(name, context.create_uri([constants.paths.SEARCH, 'list'], params=params), image=image)
+ super(SearchItem, self).__init__(name, context.create_uri([SEARCH, 'list'], params=params), image=image)
if fanart:
self.set_fanart(fanart)
else:
diff --git a/resources/lib/youtube_plugin/kodion/items/watch_later_item.py b/resources/lib/youtube_plugin/kodion/items/watch_later_item.py
index 0a816c277..0a10c27f0 100644
--- a/resources/lib/youtube_plugin/kodion/items/watch_later_item.py
+++ b/resources/lib/youtube_plugin/kodion/items/watch_later_item.py
@@ -16,7 +16,7 @@ class WatchLaterItem(DirectoryItem):
def __init__(self, context, alt_name=None, image=None, fanart=None):
name = alt_name
if not name:
- name = context.localize(constants.localize.WATCH_LATER)
+ name = context.localize('watch_later')
if image is None:
image = context.create_resource_path('media/watch_later.png')
diff --git a/resources/lib/youtube_plugin/kodion/ui/xbmc/xbmc_context_ui.py b/resources/lib/youtube_plugin/kodion/ui/xbmc/xbmc_context_ui.py
index 2e03d801a..460683607 100644
--- a/resources/lib/youtube_plugin/kodion/ui/xbmc/xbmc_context_ui.py
+++ b/resources/lib/youtube_plugin/kodion/ui/xbmc/xbmc_context_ui.py
@@ -14,7 +14,6 @@
from .xbmc_progress_dialog import XbmcProgressDialog
from .xbmc_progress_dialog_bg import XbmcProgressDialogBG
from ..abstract_context_ui import AbstractContextUI
-from ... import constants
from ... import utils
@@ -72,12 +71,12 @@ def on_ok(self, title, text):
return dialog.ok(title, text)
def on_remove_content(self, content_name):
- text = self._context.localize(constants.localize.REMOVE_CONTENT) % utils.to_unicode(content_name)
- return self.on_yes_no_input(self._context.localize(constants.localize.CONFIRM_REMOVE), text)
+ text = self._context.localize('content.remove') % utils.to_unicode(content_name)
+ return self.on_yes_no_input(self._context.localize('content.remove.confirm'), text)
def on_delete_content(self, content_name):
- text = self._context.localize(constants.localize.DELETE_CONTENT) % utils.to_unicode(content_name)
- return self.on_yes_no_input(self._context.localize(constants.localize.CONFIRM_DELETE), text)
+ text = self._context.localize('content.delete') % utils.to_unicode(content_name)
+ return self.on_yes_no_input(self._context.localize('content.delete.confirm'), text)
def on_select(self, title, items=None):
if items is None:
diff --git a/resources/lib/youtube_plugin/kodion/ui/xbmc/xbmc_items.py b/resources/lib/youtube_plugin/kodion/ui/xbmc/xbmc_items.py
index c7fe95dd9..e58372b02 100644
--- a/resources/lib/youtube_plugin/kodion/ui/xbmc/xbmc_items.py
+++ b/resources/lib/youtube_plugin/kodion/ui/xbmc/xbmc_items.py
@@ -153,7 +153,7 @@ def to_video_item(context, video_item):
local_datetime = datetime_parser.utc_to_local(datetime)
props['PublishedLocal'] = str(local_datetime)
if video_item.live:
- props['PublishedSince'] = context.localize('30539')
+ props['PublishedSince'] = context.localize('live')
elif local_datetime:
props['PublishedSince'] = str(datetime_parser.datetime_to_since(
context, local_datetime
diff --git a/resources/lib/youtube_plugin/kodion/utils/datetime_parser.py b/resources/lib/youtube_plugin/kodion/utils/datetime_parser.py
index b547bd228..a9d0cb29d 100644
--- a/resources/lib/youtube_plugin/kodion/utils/datetime_parser.py
+++ b/resources/lib/youtube_plugin/kodion/utils/datetime_parser.py
@@ -30,10 +30,6 @@
now = datetime.now
-def py2_utf8(text):
- return text
-
-
def parse(datetime_string, as_utc=True):
offset = 0 if as_utc else None
@@ -88,8 +84,9 @@ def _to_int(value):
# abbreviated match
abbreviated_match = __RE_MATCH_ABBREVIATED__.match(datetime_string)
if abbreviated_match:
- month = {'Jan': 1, 'Feb': 2, 'Mar': 3, 'Apr': 4, 'May': 5, 'June': 6, 'Jun': 6, 'July': 7, 'Jul': 7, 'Aug': 8,
- 'Sept': 9, 'Sep': 9, 'Oct': 10, 'Nov': 11, 'Dec': 12}
+ month = {'Jan': 1, 'Feb': 2, 'Mar': 3, 'Apr': 4, 'May': 5, 'June': 6,
+ 'Jun': 6, 'July': 7, 'Jul': 7, 'Aug': 8, 'Sept': 9, 'Sep': 9,
+ 'Oct': 10, 'Nov': 11, 'Dec': 12}
return utc_to_local(
dt=datetime(year=_to_int(abbreviated_match.group('year')),
month=month[abbreviated_match.group('month')],
@@ -138,43 +135,48 @@ def datetime_to_since(context, dt):
if seconds > 0:
if seconds < 60:
- return py2_utf8(context.localize('30676'))
+ return context.localize('datetime.just_now')
if 60 <= seconds < 120:
- return py2_utf8(context.localize('30677'))
+ return context.localize('datetime.a_minute_ago')
if 120 <= seconds < 3600:
- return py2_utf8(context.localize('30678'))
+ return context.localize('datetime.recently')
if 3600 <= seconds < 7200:
- return py2_utf8(context.localize('30679'))
+ return context.localize('datetime.an_hour_ago')
if 7200 <= seconds < 10800:
- return py2_utf8(context.localize('30680'))
+ return context.localize('datetime.two_hours_ago')
if 10800 <= seconds < 14400:
- return py2_utf8(context.localize('30681'))
+ return context.localize('datetime.three_hours_ago')
if use_yesterday and dt.date() == yesterday.date():
- return ' '.join([py2_utf8(context.localize('30682')), context.format_time(dt)])
+ return ' '.join((context.localize('datetime.yesterday_at'),
+ context.format_time(dt)))
if dt.date() == yyesterday.date():
- return py2_utf8(context.localize('30683'))
+ return context.localize('datetime.two_days_ago')
if 5400 <= seconds < 86400:
- return ' '.join([py2_utf8(context.localize('30684')), context.format_time(dt)])
+ return ' '.join((context.localize('datetime.today_at'),
+ context.format_time(dt)))
if 86400 <= seconds < 172800:
- return ' '.join([py2_utf8(context.localize('30682')), context.format_time(dt)])
+ return ' '.join((context.localize('datetime.yesterday_at'),
+ context.format_time(dt)))
else:
seconds *= -1
if seconds < 60:
- return py2_utf8(context.localize('30691'))
+ return context.localize('datetime.airing_now')
if 60 <= seconds < 120:
- return py2_utf8(context.localize('30692'))
+ return context.localize('datetime.in_a_minute')
if 120 <= seconds < 3600:
- return py2_utf8(context.localize('30693'))
+ return context.localize('datetime.airing_soon')
if 3600 <= seconds < 7200:
- return py2_utf8(context.localize('30694'))
+ return context.localize('datetime.in_over_an_hour')
if 7200 <= seconds < 10800:
- return py2_utf8(context.localize('30695'))
+ return context.localize('datetime.in_over_two_hours')
if dt.date() == today:
- return ' '.join([py2_utf8(context.localize('30696')), context.format_time(dt)])
+ return ' '.join((context.localize('datetime.airing_today_at'),
+ context.format_time(dt)))
if dt.date() == tomorrow:
- return ' '.join([py2_utf8(context.localize('30697')), context.format_time(dt)])
+ return ' '.join((context.localize('datetime.tomorrow_at'),
+ context.format_time(dt)))
- return ' '.join([context.format_date_short(dt), context.format_time(dt)])
+ return ' '.join((context.format_date_short(dt), context.format_time(dt)))
def strptime(s, fmt='%Y-%m-%dT%H:%M:%S.%fZ'):
diff --git a/resources/lib/youtube_plugin/kodion/utils/methods.py b/resources/lib/youtube_plugin/kodion/utils/methods.py
index a4d506e88..e3fc6d72d 100644
--- a/resources/lib/youtube_plugin/kodion/utils/methods.py
+++ b/resources/lib/youtube_plugin/kodion/utils/methods.py
@@ -14,8 +14,6 @@
from math import floor, log
from urllib.parse import quote
-from ..constants import localize
-
import xbmc
import xbmcvfs
@@ -173,7 +171,7 @@ def _find_best_fit_video(_stream_data):
for sorted_stream_data in sorted_stream_data_list
]
- result = context.get_ui().on_select(context.localize(localize.SELECT_VIDEO_QUALITY), items)
+ result = context.get_ui().on_select(context.localize('select_video_quality'), items)
if result != -1:
selected_stream_data = result
else:
diff --git a/resources/lib/youtube_plugin/youtube/helper/resource_manager.py b/resources/lib/youtube_plugin/youtube/helper/resource_manager.py
index ca1edcf95..5f74d5824 100644
--- a/resources/lib/youtube_plugin/youtube/helper/resource_manager.py
+++ b/resources/lib/youtube_plugin/youtube/helper/resource_manager.py
@@ -239,7 +239,7 @@ def handle_error(self, json_data, suppress_errors=False):
context.log_error(error_message)
if reason == 'accessNotConfigured':
- message = context.localize(30731)
+ message = context.localize('key.requirement.notification')
ok_dialog = True
elif reason in {'quotaExceeded', 'dailyLimitExceeded'}:
diff --git a/resources/lib/youtube_plugin/youtube/helper/subtitles.py b/resources/lib/youtube_plugin/youtube/helper/subtitles.py
index f61c02808..0e3c767be 100644
--- a/resources/lib/youtube_plugin/youtube/helper/subtitles.py
+++ b/resources/lib/youtube_plugin/youtube/helper/subtitles.py
@@ -166,7 +166,7 @@ def _prompt(self):
translations = [(track.get('languageCode'), self._get_language_name(track)) for track in self.translation_langs]
languages = tracks + translations
if languages:
- choice = self.context.get_ui().on_select(self.context.localize(30560), [language for _, language in languages])
+ choice = self.context.get_ui().on_select(self.context.localize('subtitles.language'), [language for _, language in languages])
if choice != -1:
return self._get(lang_code=languages[choice][0], language=languages[choice][1])
self.context.log_debug('Subtitle selection cancelled')
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 d26ded6b3..192c7c225 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
@@ -127,7 +127,7 @@ def get_items(self, provider, context, title_required=True):
self._channel_ids = list(set(self._channel_ids))
channels_item = DirectoryItem(
- context.get_ui().bold(context.localize(provider.LOCAL_MAP['youtube.channels'])),
+ context.get_ui().bold(context.localize('channels')),
context.create_uri(['special', 'description_links'],
{'channel_ids': ','.join(self._channel_ids)}),
context.create_resource_path('media', 'playlist.png')
@@ -139,10 +139,11 @@ def get_items(self, provider, context, title_required=True):
# remove duplicates
self._playlist_ids = list(set(self._playlist_ids))
- playlists_item = DirectoryItem(context.get_ui().bold(context.localize(provider.LOCAL_MAP['youtube.playlists'])),
- context.create_uri(['special', 'description_links'],
- {'playlist_ids': ','.join(self._playlist_ids)}),
- context.create_resource_path('media', 'playlist.png'))
+ playlists_item = DirectoryItem(
+ context.get_ui().bold(context.localize('playlists')),
+ context.create_uri(['special', 'description_links'],
+ {'playlist_ids': ','.join(self._playlist_ids)}),
+ context.create_resource_path('media', 'playlist.png'))
playlists_item.set_fanart(provider.get_fanart(context))
result.append(playlists_item)
diff --git a/resources/lib/youtube_plugin/youtube/helper/utils.py b/resources/lib/youtube_plugin/youtube/helper/utils.py
index 8dfaea98d..62d5790f5 100644
--- a/resources/lib/youtube_plugin/youtube/helper/utils.py
+++ b/resources/lib/youtube_plugin/youtube/helper/utils.py
@@ -46,7 +46,7 @@ def get_thumb_timestamp(minutes=15):
return str(time.mktime(time.gmtime(minutes * 60 * (round(time.time() / (minutes * 60))))))
-def make_comment_item(context, provider, snippet, uri, total_replies=0):
+def make_comment_item(context, snippet, uri, total_replies=0):
author = '[B]{}[/B]'.format(utils.to_str(snippet['authorDisplayName']))
body = utils.to_str(snippet['textOriginal'])
@@ -60,16 +60,16 @@ def make_comment_item(context, provider, snippet, uri, total_replies=0):
if snippet['likeCount'] and total_replies:
label_props = '[COLOR lime][B]+%s[/B][/COLOR]|[COLOR cyan][B]%s[/B][/COLOR]' % (str_likes, str_replies)
plot_props = '[COLOR lime][B]%s %s[/B][/COLOR]|[COLOR cyan][B]%s %s[/B][/COLOR]' % (str_likes,
- context.localize(provider.LOCAL_MAP['youtube.video.comments.likes']), str_replies,
- context.localize(provider.LOCAL_MAP['youtube.video.comments.replies']))
+ context.localize('video.comments.likes'), str_replies,
+ context.localize('video.comments.replies'))
elif snippet['likeCount']:
label_props = '[COLOR lime][B]+%s[/B][/COLOR]' % str_likes
plot_props = '[COLOR lime][B]%s %s[/B][/COLOR]' % (str_likes,
- context.localize(provider.LOCAL_MAP['youtube.video.comments.likes']))
+ context.localize('video.comments.likes'))
elif total_replies:
label_props = '[COLOR cyan][B]%s[/B][/COLOR]' % str_replies
plot_props = '[COLOR cyan][B]%s %s[/B][/COLOR]' % (str_replies,
- context.localize(provider.LOCAL_MAP['youtube.video.comments.replies']))
+ context.localize('video.comments.replies'))
else:
pass # The comment has no likes or replies.
@@ -82,7 +82,7 @@ def make_comment_item(context, provider, snippet, uri, total_replies=0):
label = '{author}{edited} {body}'.format(author=author, edited=edited, body=body.replace('\n', ' '))
# Format the plot of the comment item.
- edited = ' (%s)' % context.localize(provider.LOCAL_MAP['youtube.video.comments.edited']) if is_edited else ''
+ edited = ' (%s)' % context.localize('video.comments.edited') if is_edited else ''
if plot_props:
plot = '{author} ({props}){edited}[CR][CR]{body}'.format(author=author, props=plot_props,
edited=edited, body=body)
@@ -146,17 +146,17 @@ def update_channel_infos(provider, context, channel_id_dict,
subscription_id = subscription_id_dict.get(channel_id, '')
if subscription_id:
channel_item.set_channel_subscription_id(subscription_id)
- yt_context_menu.append_unsubscribe_from_channel(context_menu, provider, context, subscription_id)
+ yt_context_menu.append_unsubscribe_from_channel(context_menu, context, subscription_id)
# -- subscribe to the channel
if logged_in and path != '/subscriptions/list/':
- yt_context_menu.append_subscribe_to_channel(context_menu, provider, context, channel_id)
+ yt_context_menu.append_subscribe_to_channel(context_menu, context, channel_id)
if path == '/subscriptions/list/':
channel = title.lower().replace(',', '')
if channel in filter_list:
- yt_context_menu.append_remove_my_subscriptions_filter(context_menu, provider, context, title)
+ yt_context_menu.append_remove_my_subscriptions_filter(context_menu, context, title)
else:
- yt_context_menu.append_add_my_subscriptions_filter(context_menu, provider, context, title)
+ yt_context_menu.append_add_my_subscriptions_filter(context_menu, context, title)
channel_item.set_context_menu(context_menu)
fanart_images = yt_item.get('brandingSettings', {}).get('image', {})
@@ -212,32 +212,32 @@ def update_playlist_infos(provider, context, playlist_id_dict,
channel_name = snippet.get('channelTitle', '')
context_menu = []
# play all videos of the playlist
- yt_context_menu.append_play_all_from_playlist(context_menu, provider, context, playlist_id)
+ yt_context_menu.append_play_all_from_playlist(context_menu, context, playlist_id)
if logged_in:
if channel_id != 'mine':
# subscribe to the channel via the playlist item
- yt_context_menu.append_subscribe_to_channel(context_menu, provider, context, channel_id,
+ yt_context_menu.append_subscribe_to_channel(context_menu, context, channel_id,
channel_name)
else:
# remove my playlist
- yt_context_menu.append_delete_playlist(context_menu, provider, context, playlist_id, title)
+ yt_context_menu.append_delete_playlist(context_menu, context, playlist_id, title)
# rename playlist
- yt_context_menu.append_rename_playlist(context_menu, provider, context, playlist_id, title)
+ yt_context_menu.append_rename_playlist(context_menu, context, playlist_id, title)
# remove as my custom watch later playlist
if playlist_id == custom_watch_later_id:
- yt_context_menu.append_remove_as_watchlater(context_menu, provider, context, playlist_id, title)
+ yt_context_menu.append_remove_as_watchlater(context_menu, context, playlist_id, title)
# set as my custom watch later playlist
else:
- yt_context_menu.append_set_as_watchlater(context_menu, provider, context, playlist_id, title)
+ yt_context_menu.append_set_as_watchlater(context_menu, context, playlist_id, title)
# remove as custom history playlist
if playlist_id == custom_history_id:
- yt_context_menu.append_remove_as_history(context_menu, provider, context, playlist_id, title)
+ yt_context_menu.append_remove_as_history(context_menu, context, playlist_id, title)
# set as custom history playlist
else:
- yt_context_menu.append_set_as_history(context_menu, provider, context, playlist_id, title)
+ yt_context_menu.append_set_as_history(context_menu, context, playlist_id, title)
if context_menu:
playlist_item.set_context_menu(context_menu)
@@ -334,9 +334,8 @@ def update_video_infos(provider, context, video_id_dict,
video_item.set_aired_from_datetime(local_datetime)
video_item.set_premiered_from_datetime(local_datetime)
video_item.set_date_from_datetime(local_datetime)
- type_label = context.localize(provider.LOCAL_MAP[
- 'youtube.live' if video_item.live else 'youtube.upcoming'
- ])
+ type_label = context.localize('live' if video_item.live
+ else 'upcoming')
start_at = '{type_label} {start_at}'.format(
type_label=type_label,
start_at=utils.datetime_parser.get_scheduled_start(
@@ -353,7 +352,7 @@ def update_video_infos(provider, context, video_id_dict,
stats = []
if 'statistics' in yt_item:
for stat, value in yt_item['statistics'].items():
- label = provider.LOCAL_MAP.get('youtube.stats.' + stat)
+ label = context.LOCAL_MAP.get('stats.' + stat)
if label:
stats.append('{value} {name}'.format(
name=context.localize(label).lower(),
@@ -436,10 +435,10 @@ def update_video_infos(provider, context, video_id_dict,
replace_context_menu = False
# Refresh
- yt_context_menu.append_refresh(context_menu, provider, context)
+ yt_context_menu.append_refresh(context_menu, context)
# Queue Video
- yt_context_menu.append_queue_video(context_menu, provider, context)
+ yt_context_menu.append_queue_video(context_menu, context)
"""
Play all videos of the playlist.
@@ -452,8 +451,8 @@ def update_video_infos(provider, context, video_id_dict,
replace_context_menu = True
playlist_id = some_playlist_match.group('playlist_id')
- yt_context_menu.append_play_all_from_playlist(context_menu, provider, context, playlist_id, video_id)
- yt_context_menu.append_play_all_from_playlist(context_menu, provider, context, playlist_id)
+ yt_context_menu.append_play_all_from_playlist(context_menu, context, playlist_id, video_id)
+ yt_context_menu.append_play_all_from_playlist(context_menu, context, playlist_id)
# 'play with...' (external player)
if alternate_player:
@@ -463,11 +462,11 @@ def update_video_infos(provider, context, video_id_dict,
# add 'Watch Later' only if we are not in my 'Watch Later' list
watch_later_playlist_id = context.get_access_manager().get_watch_later_id()
if watch_later_playlist_id:
- yt_context_menu.append_watch_later(context_menu, provider, context, watch_later_playlist_id, video_id)
+ yt_context_menu.append_watch_later(context_menu, context, watch_later_playlist_id, video_id)
# provide 'remove' for videos in my playlists
if video_id in playlist_item_id_dict:
- playlist_match = re.match('^/channel/mine/playlist/(?P[^/]+)/$', path)
+ playlist_match = re.match('^/channel/mine/playlist/(?P[^/]+)/$', context.get_path())
if playlist_match:
playlist_id = playlist_match.group('playlist_id')
# we support all playlist except 'Watch History'
@@ -475,7 +474,7 @@ def update_video_infos(provider, context, video_id_dict,
playlist_item_id = playlist_item_id_dict[video_id]
video_item.set_playlist_id(playlist_id)
video_item.set_playlist_item_id(playlist_item_id)
- context_menu.append((context.localize(provider.LOCAL_MAP['youtube.remove']),
+ context_menu.append((context.localize('remove'),
'RunPlugin(%s)' % context.create_uri(
['playlist', 'remove', 'video'],
{'playlist_id': playlist_id,
@@ -485,27 +484,27 @@ def update_video_infos(provider, context, video_id_dict,
is_history = re.match('^/special/watch_history_tv/$', context.get_path())
if is_history:
- yt_context_menu.append_clear_watch_history(context_menu, provider, context)
+ yt_context_menu.append_clear_watch_history(context_menu, context)
# got to [CHANNEL], only if we are not directly in the channel provide a jump to the channel
if (channel_id and channel_name and
utils.create_path('channel', channel_id) != path):
video_item.set_channel_id(channel_id)
- yt_context_menu.append_go_to_channel(context_menu, provider, context, channel_id, channel_name)
+ yt_context_menu.append_go_to_channel(context_menu, context, channel_id, channel_name)
if logged_in:
# subscribe to the channel of the video
video_item.set_subscription_id(channel_id)
- yt_context_menu.append_subscribe_to_channel(context_menu, provider, context, channel_id, channel_name)
+ yt_context_menu.append_subscribe_to_channel(context_menu, context, channel_id, channel_name)
if not video_item.live and play_data:
if not play_data.get('play_count'):
- yt_context_menu.append_mark_watched(context_menu, provider, context, video_id)
+ yt_context_menu.append_mark_watched(context_menu, context, video_id)
else:
- yt_context_menu.append_mark_unwatched(context_menu, provider, context, video_id)
+ yt_context_menu.append_mark_unwatched(context_menu, context, video_id)
if play_data.get('played_percent', 0) > 0 or play_data.get('played_time', 0) > 0:
- yt_context_menu.append_reset_resume_point(context_menu, provider, context, video_id)
+ yt_context_menu.append_reset_resume_point(context_menu, context, video_id)
# more...
refresh_container = (path.startswith('/channel/mine/playlist/LL')
@@ -518,7 +517,7 @@ def update_video_infos(provider, context, video_id_dict,
yt_context_menu.append_play_with_subtitles(context_menu, context, video_id)
yt_context_menu.append_play_audio_only(context_menu, context, video_id)
- yt_context_menu.append_play_ask_for_quality(context_menu, provider, context, video_id)
+ yt_context_menu.append_play_ask_for_quality(context_menu, context, video_id)
if context_menu:
video_item.set_context_menu(context_menu, replace=replace_context_menu)
diff --git a/resources/lib/youtube_plugin/youtube/helper/v3.py b/resources/lib/youtube_plugin/youtube/helper/v3.py
index e7fd3e429..6fe14423b 100644
--- a/resources/lib/youtube_plugin/youtube/helper/v3.py
+++ b/resources/lib/youtube_plugin/youtube/helper/v3.py
@@ -42,7 +42,7 @@ def _process_list_response(provider, context, json_data):
if kind == 'video':
video_id = yt_item['id']
snippet = yt_item['snippet']
- title = snippet.get('title', context.localize(provider.LOCAL_MAP['youtube.untitled']))
+ title = snippet.get('title', context.localize('untitled'))
image = utils.get_thumbnail(thumb_size, snippet.get('thumbnails', {}))
item_params = {'video_id': video_id}
if incognito:
@@ -60,7 +60,7 @@ def _process_list_response(provider, context, json_data):
elif kind == 'channel':
channel_id = yt_item['id']
snippet = yt_item['snippet']
- title = snippet.get('title', context.localize(provider.LOCAL_MAP['youtube.untitled']))
+ title = snippet.get('title', context.localize('untitled'))
image = utils.get_thumbnail(thumb_size, snippet.get('thumbnails', {}))
item_params = {}
if incognito:
@@ -74,14 +74,14 @@ def _process_list_response(provider, context, json_data):
# if logged in => provide subscribing to the channel
if provider.is_logged_in():
context_menu = []
- yt_context_menu.append_subscribe_to_channel(context_menu, provider, context, channel_id)
+ yt_context_menu.append_subscribe_to_channel(context_menu, context, channel_id)
channel_item.set_context_menu(context_menu)
result.append(channel_item)
channel_id_dict[channel_id] = channel_item
elif kind == 'guidecategory':
guide_id = yt_item['id']
snippet = yt_item['snippet']
- title = snippet.get('title', context.localize(provider.LOCAL_MAP['youtube.untitled']))
+ title = snippet.get('title', context.localize('untitled'))
item_params = {'guide_id': guide_id}
if incognito:
item_params['incognito'] = incognito
@@ -93,7 +93,7 @@ def _process_list_response(provider, context, json_data):
result.append(guide_item)
elif kind == 'subscription':
snippet = yt_item['snippet']
- title = snippet.get('title', context.localize(provider.LOCAL_MAP['youtube.untitled']))
+ title = snippet.get('title', context.localize('untitled'))
image = utils.get_thumbnail(thumb_size, snippet.get('thumbnails', {}))
channel_id = snippet['resourceId']['channelId']
item_params = {}
@@ -113,7 +113,7 @@ def _process_list_response(provider, context, json_data):
elif kind == 'playlist':
playlist_id = yt_item['id']
snippet = yt_item['snippet']
- title = snippet.get('title', context.localize(provider.LOCAL_MAP['youtube.untitled']))
+ title = snippet.get('title', context.localize('untitled'))
image = utils.get_thumbnail(thumb_size, snippet.get('thumbnails', {}))
channel_id = snippet['channelId']
@@ -138,7 +138,7 @@ def _process_list_response(provider, context, json_data):
# store the id of the playlistItem - for deleting this item we need this item
playlist_item_id_dict[video_id] = yt_item['id']
- title = snippet.get('title', context.localize(provider.LOCAL_MAP['youtube.untitled']))
+ title = snippet.get('title', context.localize('untitled'))
image = utils.get_thumbnail(thumb_size, snippet.get('thumbnails', {}))
item_params = {'video_id': video_id}
if incognito:
@@ -169,7 +169,7 @@ def _process_list_response(provider, context, json_data):
else:
continue
- title = snippet.get('title', context.localize(provider.LOCAL_MAP['youtube.untitled']))
+ title = snippet.get('title', context.localize('untitled'))
image = utils.get_thumbnail(thumb_size, snippet.get('thumbnails', {}))
item_params = {'video_id': video_id}
if incognito:
@@ -194,10 +194,10 @@ def _process_list_response(provider, context, json_data):
item_uri = context.create_uri(['special', 'child_comments'], item_params)
else:
item_uri = ''
- result.append(utils.make_comment_item(context, provider, snippet, item_uri, total_replies))
+ result.append(utils.make_comment_item(context, snippet, item_uri, total_replies))
elif kind == 'comment':
- result.append(utils.make_comment_item(context, provider, yt_item['snippet'], uri=''))
+ result.append(utils.make_comment_item(context, yt_item['snippet'], uri=''))
elif kind == 'searchresult':
_, kind = _parse_kind(yt_item.get('id', {}))
@@ -206,7 +206,7 @@ def _process_list_response(provider, context, json_data):
if kind == 'video':
video_id = yt_item['id']['videoId']
snippet = yt_item.get('snippet', {})
- title = snippet.get('title', context.localize(provider.LOCAL_MAP['youtube.untitled']))
+ title = snippet.get('title', context.localize('untitled'))
image = utils.get_thumbnail(thumb_size, snippet.get('thumbnails', {}))
item_params = {'video_id': video_id}
if incognito:
@@ -225,7 +225,7 @@ def _process_list_response(provider, context, json_data):
elif kind == 'playlist':
playlist_id = yt_item['id']['playlistId']
snippet = yt_item['snippet']
- title = snippet.get('title', context.localize(provider.LOCAL_MAP['youtube.untitled']))
+ title = snippet.get('title', context.localize('untitled'))
image = utils.get_thumbnail(thumb_size, snippet.get('thumbnails', {}))
channel_id = snippet['channelId']
@@ -246,7 +246,7 @@ def _process_list_response(provider, context, json_data):
elif kind == 'channel':
channel_id = yt_item['id']['channelId']
snippet = yt_item['snippet']
- title = snippet.get('title', context.localize(provider.LOCAL_MAP['youtube.untitled']))
+ title = snippet.get('title', context.localize('untitled'))
image = utils.get_thumbnail(thumb_size, snippet.get('thumbnails', {}))
item_params = {}
if incognito:
@@ -329,7 +329,7 @@ def response_to_items(provider, context, json_data, sort=None, reverse_sort=Fals
return result
-def handle_error(provider, context, json_data):
+def handle_error(context, json_data):
if json_data and 'error' in json_data:
ok_dialog = False
message_timeout = 5000
@@ -342,11 +342,11 @@ def handle_error(provider, context, json_data):
context.log_error('Error reason: |%s| with message: |%s|' % (reason, log_message))
if reason == 'accessNotConfigured':
- message = context.localize(provider.LOCAL_MAP['youtube.key.requirement.notification'])
+ message = context.localize('key.requirement.notification')
ok_dialog = True
if reason == 'keyInvalid' and message == 'Bad Request':
- message = context.localize(provider.LOCAL_MAP['youtube.api.key.incorrect'])
+ message = context.localize('api.key.incorrect')
message_timeout = 7000
if reason in {'quotaExceeded', 'dailyLimitExceeded'}:
diff --git a/resources/lib/youtube_plugin/youtube/helper/video_info.py b/resources/lib/youtube_plugin/youtube/helper/video_info.py
index d042e2970..28637160b 100644
--- a/resources/lib/youtube_plugin/youtube/helper/video_info.py
+++ b/resources/lib/youtube_plugin/youtube/helper/video_info.py
@@ -1285,7 +1285,7 @@ def _get_video_info(self):
manifest_url, main_stream = self._generate_mpd_manifest(
video_data, audio_data, license_info.get('url')
)
-
+
# extract non-adaptive streams
if all_fmts:
stream_list.extend(self._create_stream_list(
@@ -1330,11 +1330,15 @@ def _get_video_info(self):
details['title'].append(' [ASR]')
if main_stream['multi_lang']:
details['title'].extend((
- ' [', self._context.localize(30762), ']'
+ ' [',
+ self._context.localize('stream.multi_language'),
+ ']'
))
if main_stream['multi_audio']:
details['title'].extend((
- ' [', self._context.localize(30763), ']'
+ ' [',
+ self._context.localize('stream.multi_audio'),
+ ']'
))
details['title'] = ''.join(details['title'])
@@ -1441,18 +1445,18 @@ def _process_stream_data(self, stream_data, default_lang_code='und'):
if role_type == 4 or audio_track.get('audioIsDefault'):
role = 'main'
- label = self._context.localize(30744)
+ label = self._context.localize('stream.original')
elif role_type == 3:
role = 'dub'
- label = self._context.localize(30745)
+ label = self._context.localize('stream.dubbed')
elif role_type == 2:
role = 'description'
- label = self._context.localize(30746)
+ label = self._context.localize('stream.descriptive')
# Unsure of what other audio types are actually available
# Role set to "alternate" as default fallback
else:
role = 'alternate'
- label = self._context.localize(30747)
+ label = self._context.localize('stream.alternate')
mime_group = '{0}_{1}.{2}'.format(
mime_type, language_code, role_type
@@ -1471,7 +1475,7 @@ def _process_stream_data(self, stream_data, default_lang_code='und'):
language_code = default_lang_code
role = 'main'
role_type = 4
- label = self._context.localize(30744)
+ label = self._context.localize('stream.original')
mime_group = mime_type
sample_rate = int(stream.get('audioSampleRate', '0'), 10)
@@ -1723,7 +1727,8 @@ def _filter_group(previous_group, previous_stream, item):
if group.startswith(mime_type) and 'auto' in stream_select:
label = '{0} [{1}]'.format(
- stream['langName'] or self._context.localize(30583),
+ stream['langName']
+ or self._context.localize('stream.automatic'),
stream['label']
)
if stream == main_stream[media_type]:
diff --git a/resources/lib/youtube_plugin/youtube/helper/yt_context_menu.py b/resources/lib/youtube_plugin/youtube/helper/yt_context_menu.py
index 941e86e34..d35080dd6 100644
--- a/resources/lib/youtube_plugin/youtube/helper/yt_context_menu.py
+++ b/resources/lib/youtube_plugin/youtube/helper/yt_context_menu.py
@@ -11,7 +11,7 @@
from ... import kodion
-def append_more_for_video(context_menu, provider, context, video_id, is_logged_in=False, refresh_container=False):
+def append_more_for_video(context_menu, context, video_id, is_logged_in=False, refresh_container=False):
_is_logged_in = '0'
if is_logged_in:
_is_logged_in = '1'
@@ -20,195 +20,195 @@ def append_more_for_video(context_menu, provider, context, video_id, is_logged_i
if refresh_container:
_refresh_container = '1'
- context_menu.append((context.localize(provider.LOCAL_MAP['youtube.video.more']),
+ context_menu.append((context.localize('video.more'),
'RunPlugin(%s)' % context.create_uri(['video', 'more'],
{'video_id': video_id,
'logged_in': _is_logged_in,
'refresh_container': _refresh_container})))
-def append_content_from_description(context_menu, provider, context, video_id):
- context_menu.append((context.localize(provider.LOCAL_MAP['youtube.video.description.links']),
+def append_content_from_description(context_menu, context, video_id):
+ context_menu.append((context.localize('video.description.links'),
'Container.Update(%s)' % context.create_uri(['special', 'description_links'],
{'video_id': video_id})))
-def append_play_with(context_menu, provider, context):
- context_menu.append((context.localize(provider.LOCAL_MAP['youtube.video.play_with']), 'Action(SwitchPlayer)'))
+def append_play_with(context_menu, context):
+ context_menu.append((context.localize('video.play.with'), 'Action(SwitchPlayer)'))
-def append_queue_video(context_menu, provider, context):
- context_menu.append((context.localize(provider.LOCAL_MAP['youtube.video.queue']), 'Action(Queue)'))
+def append_queue_video(context_menu, context):
+ context_menu.append((context.localize('video.queue'), 'Action(Queue)'))
-def append_play_all_from_playlist(context_menu, provider, context, playlist_id, video_id=''):
+def append_play_all_from_playlist(context_menu, context, playlist_id, video_id=''):
if video_id:
- context_menu.append((context.localize(provider.LOCAL_MAP['youtube.playlist.play.from_here']),
+ context_menu.append((context.localize('playlist.play.from_here'),
'RunPlugin(%s)' % context.create_uri(['play'],
{'playlist_id': playlist_id,
'video_id': video_id,
'play': '1'})))
else:
- context_menu.append((context.localize(provider.LOCAL_MAP['youtube.playlist.play.all']),
+ context_menu.append((context.localize('playlist.play.all'),
'RunPlugin(%s)' % context.create_uri(['play'],
{'playlist_id': playlist_id,
'play': '1'})))
-def append_add_video_to_playlist(context_menu, provider, context, video_id):
- context_menu.append((context.localize(provider.LOCAL_MAP['youtube.video.add_to_playlist']),
+def append_add_video_to_playlist(context_menu, context, video_id):
+ context_menu.append((context.localize('video.add_to_playlist'),
'RunPlugin(%s)' % context.create_uri(['playlist', 'select', 'playlist'],
{'video_id': video_id})))
-def append_rename_playlist(context_menu, provider, context, playlist_id, playlist_name):
- context_menu.append((context.localize(provider.LOCAL_MAP['youtube.rename']),
+def append_rename_playlist(context_menu, context, playlist_id, playlist_name):
+ context_menu.append((context.localize('rename'),
'RunPlugin(%s)' % context.create_uri(['playlist', 'rename', 'playlist'],
{'playlist_id': playlist_id,
'playlist_name': playlist_name})))
-def append_delete_playlist(context_menu, provider, context, playlist_id, playlist_name):
- context_menu.append((context.localize(provider.LOCAL_MAP['youtube.delete']),
+def append_delete_playlist(context_menu, context, playlist_id, playlist_name):
+ context_menu.append((context.localize('delete'),
'RunPlugin(%s)' % context.create_uri(['playlist', 'remove', 'playlist'],
{'playlist_id': playlist_id,
'playlist_name': playlist_name})))
-def append_remove_as_watchlater(context_menu, provider, context, playlist_id, playlist_name):
- context_menu.append((context.localize(provider.LOCAL_MAP['youtube.remove.as.watchlater']),
+def append_remove_as_watchlater(context_menu, context, playlist_id, playlist_name):
+ context_menu.append((context.localize('watch_later.list.remove'),
'RunPlugin(%s)' % context.create_uri(['playlist', 'remove', 'watchlater'],
{'playlist_id': playlist_id,
'playlist_name': playlist_name})))
-def append_set_as_watchlater(context_menu, provider, context, playlist_id, playlist_name):
- context_menu.append((context.localize(provider.LOCAL_MAP['youtube.set.as.watchlater']),
+def append_set_as_watchlater(context_menu, context, playlist_id, playlist_name):
+ context_menu.append((context.localize('watch_later.list.set'),
'RunPlugin(%s)' % context.create_uri(['playlist', 'set', 'watchlater'],
{'playlist_id': playlist_id,
'playlist_name': playlist_name})))
-def append_remove_as_history(context_menu, provider, context, playlist_id, playlist_name):
- context_menu.append((context.localize(provider.LOCAL_MAP['youtube.remove.as.history']),
+def append_remove_as_history(context_menu, context, playlist_id, playlist_name):
+ context_menu.append((context.localize('history.list.remove'),
'RunPlugin(%s)' % context.create_uri(['playlist', 'remove', 'history'],
{'playlist_id': playlist_id,
'playlist_name': playlist_name})))
-def append_set_as_history(context_menu, provider, context, playlist_id, playlist_name):
- context_menu.append((context.localize(provider.LOCAL_MAP['youtube.set.as.history']),
+def append_set_as_history(context_menu, context, playlist_id, playlist_name):
+ context_menu.append((context.localize('history.list.set'),
'RunPlugin(%s)' % context.create_uri(['playlist', 'set', 'history'],
{'playlist_id': playlist_id,
'playlist_name': playlist_name})))
-def append_remove_my_subscriptions_filter(context_menu, provider, context, channel_name):
+def append_remove_my_subscriptions_filter(context_menu, context, channel_name):
if context.get_settings().get_bool('youtube.folder.my_subscriptions_filtered.show', False):
- context_menu.append((context.localize(provider.LOCAL_MAP['youtube.remove.my_subscriptions.filter']),
+ context_menu.append((context.localize('my_subscriptions.filter.remove'),
'RunPlugin(%s)' % context.create_uri(['my_subscriptions', 'filter'],
{'channel_name': channel_name,
'action': 'remove'})))
-def append_add_my_subscriptions_filter(context_menu, provider, context, channel_name):
+def append_add_my_subscriptions_filter(context_menu, context, channel_name):
if context.get_settings().get_bool('youtube.folder.my_subscriptions_filtered.show', False):
- context_menu.append((context.localize(provider.LOCAL_MAP['youtube.add.my_subscriptions.filter']),
+ context_menu.append((context.localize('my_subscriptions.filter.add'),
'RunPlugin(%s)' % context.create_uri(['my_subscriptions', 'filter'],
{'channel_name': channel_name,
'action': 'add'})))
-def append_rate_video(context_menu, provider, context, video_id, refresh_container=False):
+def append_rate_video(context_menu, context, video_id, refresh_container=False):
refresh_container = '1' if refresh_container else '0'
- context_menu.append((context.localize(provider.LOCAL_MAP['youtube.video.rate']),
+ context_menu.append((context.localize('video.rate'),
'RunPlugin(%s)' % context.create_uri(['video', 'rate'],
{'video_id': video_id,
'refresh_container': refresh_container})))
-def append_watch_later(context_menu, provider, context, playlist_id, video_id):
+def append_watch_later(context_menu, context, playlist_id, video_id):
playlist_path = kodion.utils.create_path('channel', 'mine', 'playlist', playlist_id)
if playlist_id and playlist_path != context.get_path():
- context_menu.append((context.localize(provider.LOCAL_MAP['youtube.watch_later']),
+ context_menu.append((context.localize('watch_later'),
'RunPlugin(%s)' % context.create_uri(['playlist', 'add', 'video'],
{'playlist_id': playlist_id, 'video_id': video_id})))
-def append_go_to_channel(context_menu, provider, context, channel_id, channel_name):
- text = context.localize(provider.LOCAL_MAP['youtube.go_to_channel']) % context.get_ui().bold(channel_name)
+def append_go_to_channel(context_menu, context, channel_id, channel_name):
+ text = context.localize('go_to_channel') % context.get_ui().bold(channel_name)
context_menu.append((text, 'Container.Update(%s)' % context.create_uri(['channel', channel_id])))
-def append_related_videos(context_menu, provider, context, video_id):
- context_menu.append((context.localize(provider.LOCAL_MAP['youtube.related_videos']),
+def append_related_videos(context_menu, context, video_id):
+ context_menu.append((context.localize('related_videos'),
'Container.Update(%s)' % context.create_uri(['special', 'related_videos'],
{'video_id': video_id})))
-def append_clear_watch_history(context_menu, provider, context):
- context_menu.append((context.localize(provider.LOCAL_MAP['youtube.clear_history']),
+def append_clear_watch_history(context_menu, context):
+ context_menu.append((context.localize('clear_history'),
'Container.Update(%s)' % context.create_uri(['history', 'clear'])))
-def append_refresh(context_menu, provider, context):
- context_menu.append((context.localize(provider.LOCAL_MAP['youtube.refresh']), 'Container.Refresh'))
+def append_refresh(context_menu, context):
+ context_menu.append((context.localize('refresh'), 'Container.Refresh'))
-def append_subscribe_to_channel(context_menu, provider, context, channel_id, channel_name=''):
+def append_subscribe_to_channel(context_menu, context, channel_id, channel_name=''):
if channel_name:
- text = context.localize(provider.LOCAL_MAP['youtube.subscribe_to']) % context.get_ui().bold(channel_name)
+ text = context.localize('subscribe_to') % context.get_ui().bold(channel_name)
context_menu.append(
(text, 'RunPlugin(%s)' % context.create_uri(['subscriptions', 'add'], {'subscription_id': channel_id})))
else:
- context_menu.append((context.localize(provider.LOCAL_MAP['youtube.subscribe']),
+ context_menu.append((context.localize('subscribe'),
'RunPlugin(%s)' % context.create_uri(['subscriptions', 'add'],
{'subscription_id': channel_id})))
-def append_unsubscribe_from_channel(context_menu, provider, context, channel_id):
- context_menu.append((context.localize(provider.LOCAL_MAP['youtube.unsubscribe']),
+def append_unsubscribe_from_channel(context_menu, context, channel_id):
+ context_menu.append((context.localize('unsubscribe'),
'RunPlugin(%s)' % context.create_uri(['subscriptions', 'remove'],
{'subscription_id': channel_id})))
-def append_mark_watched(context_menu, provider, context, video_id):
- context_menu.append((context.localize(provider.LOCAL_MAP['youtube.mark.watched']),
+def append_mark_watched(context_menu, context, video_id):
+ context_menu.append((context.localize('mark.watched'),
'RunPlugin(%s)' % context.create_uri(['playback_history'],
{'video_id': video_id,
'action': 'mark_watched'})))
-def append_mark_unwatched(context_menu, provider, context, video_id):
- context_menu.append((context.localize(provider.LOCAL_MAP['youtube.mark.unwatched']),
+def append_mark_unwatched(context_menu, context, video_id):
+ context_menu.append((context.localize('mark.unwatched'),
'RunPlugin(%s)' % context.create_uri(['playback_history'],
{'video_id': video_id,
'action': 'mark_unwatched'})))
-def append_reset_resume_point(context_menu, provider, context, video_id):
- context_menu.append((context.localize(provider.LOCAL_MAP['youtube.reset.resume.point']),
+def append_reset_resume_point(context_menu, context, video_id):
+ context_menu.append((context.localize('reset.resume_point'),
'RunPlugin(%s)' % context.create_uri(['playback_history'],
{'video_id': video_id,
'action': 'reset_resume'})))
-def append_play_with_subtitles(context_menu, provider, context, video_id):
- context_menu.append((context.localize(provider.LOCAL_MAP['youtube.video.play_with_subtitles']),
+def append_play_with_subtitles(context_menu, context, video_id):
+ context_menu.append((context.localize('video.play.with_subtitles'),
'RunPlugin(%s)' % context.create_uri(['play'],
{'video_id': video_id,
'prompt_for_subtitles': '1'})))
-def append_play_audio_only(context_menu, provider, context, video_id):
- context_menu.append((context.localize(provider.LOCAL_MAP['youtube.video.play_audio_only']),
+def append_play_audio_only(context_menu, context, video_id):
+ context_menu.append((context.localize('video.play.audio_only'),
'RunPlugin(%s)' % context.create_uri(['play'],
{'video_id': video_id,
'audio_only': '1'})))
-def append_play_ask_for_quality(context_menu, provider, context, video_id):
- context_menu.append((context.localize(provider.LOCAL_MAP['youtube.video.play_ask_for_quality']),
+def append_play_ask_for_quality(context_menu, context, video_id):
+ context_menu.append((context.localize('video.play.ask_for_quality'),
'RunPlugin(%s)' % context.create_uri(['play'],
{'video_id': video_id,
'ask_for_quality': '1'})))
diff --git a/resources/lib/youtube_plugin/youtube/helper/yt_login.py b/resources/lib/youtube_plugin/youtube/helper/yt_login.py
index f06b86509..fc96c5662 100644
--- a/resources/lib/youtube_plugin/youtube/helper/yt_login.py
+++ b/resources/lib/youtube_plugin/youtube/helper/yt_login.py
@@ -60,12 +60,12 @@ def _do_login(_for_tv=False):
user_code = json_data['user_code']
verification_url = json_data.get('verification_url', 'youtube.com/activate').lstrip('https://www.')
- text = [context.localize(provider.LOCAL_MAP['youtube.sign.go_to']) % context.get_ui().bold(verification_url),
- '[CR]%s %s' % (context.localize(provider.LOCAL_MAP['youtube.sign.enter_code']),
+ text = [context.localize('sign.go_to') % context.get_ui().bold(verification_url),
+ '[CR]%s %s' % (context.localize('sign.enter_code'),
context.get_ui().bold(user_code))]
text = ''.join(text)
dialog = context.get_ui().create_progress_dialog(
- heading=context.localize(provider.LOCAL_MAP['youtube.sign.in']), text=text, background=False)
+ heading=context.localize('sign.in'), text=text, background=False)
steps = ((10 * 60 * 1000) // interval) # 10 Minutes
dialog.set_total(steps)
@@ -115,8 +115,8 @@ def _do_login(_for_tv=False):
context.get_ui().refresh_container()
elif mode == 'in':
- context.get_ui().on_ok(context.localize(provider.LOCAL_MAP['youtube.sign.twice.title']),
- context.localize(provider.LOCAL_MAP['youtube.sign.twice.text']))
+ context.get_ui().on_ok(context.localize('sign.twice.title'),
+ context.localize('sign.twice.text'))
access_token_tv, expires_in_tv, refresh_token_tv = _do_login(_for_tv=True)
# abort tv login
diff --git a/resources/lib/youtube_plugin/youtube/helper/yt_play.py b/resources/lib/youtube_plugin/youtube/helper/yt_play.py
index 3d21507f3..65ada0f74 100644
--- a/resources/lib/youtube_plugin/youtube/helper/yt_play.py
+++ b/resources/lib/youtube_plugin/youtube/helper/yt_play.py
@@ -16,7 +16,6 @@
import xbmcplugin
from ... import kodion
-from ...kodion import constants
from ...kodion.items import VideoItem
from ...kodion.ui.xbmc.xbmc_items import to_playback_item
from ...youtube.youtube_exceptions import YouTubeException
@@ -54,7 +53,7 @@ def play_video(provider, context):
return False
if not video_streams:
- message = context.localize(provider.LOCAL_MAP['youtube.error.no_video_streams_found'])
+ message = context.localize('error.no_video_streams_found')
context.get_ui().show_notification(message, time_milliseconds=5000)
return False
@@ -67,7 +66,7 @@ def play_video(provider, context):
is_live = video_stream.get('Live')
if is_video and video_stream['video'].get('rtmpe', False):
- message = context.localize(provider.LOCAL_MAP['youtube.error.rtmpe_not_supported'])
+ message = context.localize('error.rtmpe_not_supported')
context.get_ui().show_notification(message, time_milliseconds=5000)
return False
@@ -137,17 +136,17 @@ def play_playlist(provider, context):
def _load_videos(_page_token='', _progress_dialog=None):
if _progress_dialog is None:
_progress_dialog = context.get_ui().create_progress_dialog(
- context.localize(provider.LOCAL_MAP['youtube.playlist.progress.updating']),
- context.localize(constants.localize.COMMON_PLEASE_WAIT), background=True)
+ context.localize('playlist.progress.updating'),
+ context.localize('please_wait'), background=True)
json_data = client.get_playlist_items(playlist_id, page_token=_page_token)
- if not v3.handle_error(provider, context, json_data):
+ if not v3.handle_error(context, json_data):
return None
_progress_dialog.set_total(int(json_data.get('pageInfo', {}).get('totalResults', 0)))
result = v3.response_to_items(provider, context, json_data, process_next_page=False)
videos.extend(result)
progress_text = '%s %d/%d' % (
- context.localize(constants.localize.COMMON_PLEASE_WAIT), len(videos), _progress_dialog.get_total())
+ context.localize('please_wait'), len(videos), _progress_dialog.get_total())
_progress_dialog.update(steps=len(result), text=progress_text)
next_page_token = json_data.get('nextPageToken', '')
@@ -166,9 +165,9 @@ def _load_videos(_page_token='', _progress_dialog=None):
order_list.append('shuffle')
items = []
for order in order_list:
- items.append((context.localize(provider.LOCAL_MAP['youtube.playlist.play.%s' % order]), order))
+ items.append((context.localize('playlist.play.%s' % order), order))
- order = context.get_ui().on_select(context.localize(provider.LOCAL_MAP['youtube.playlist.play.select']), items)
+ order = context.get_ui().on_select(context.localize('playlist.play.select'), items)
if order not in order_list:
return False
@@ -234,7 +233,7 @@ def play_channel_live(provider, context):
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)
- if not v3.handle_error(provider, context, json_data):
+ if not v3.handle_error(context, json_data):
return False
video_items = v3.response_to_items(provider, context, json_data, process_next_page=False)
diff --git a/resources/lib/youtube_plugin/youtube/helper/yt_playlist.py b/resources/lib/youtube_plugin/youtube/helper/yt_playlist.py
index 6d1a32d7d..d055238d1 100644
--- a/resources/lib/youtube_plugin/youtube/helper/yt_playlist.py
+++ b/resources/lib/youtube_plugin/youtube/helper/yt_playlist.py
@@ -37,13 +37,13 @@ def _process_add_video(provider, context, keymap_action=False):
if playlist_id != 'HL':
json_data = client.add_video_to_playlist(playlist_id=playlist_id, video_id=video_id)
- if not v3.handle_error(provider, context, json_data):
+ if not v3.handle_error(context, json_data):
return False
if playlist_id == watch_later_id:
- notify_message = context.localize(provider.LOCAL_MAP['youtube.added.to.watch.later'])
+ notify_message = context.localize('watch_later.added_to')
else:
- notify_message = context.localize(provider.LOCAL_MAP['youtube.added.to.playlist'])
+ notify_message = context.localize('playlist.added_to')
context.get_ui().show_notification(
message=notify_message,
@@ -95,13 +95,13 @@ def _process_remove_video(provider, context):
if context.get_ui().on_remove_content(video_name):
json_data = provider.get_client(context).remove_video_from_playlist(playlist_id=playlist_id,
playlist_item_id=video_id)
- if not v3.handle_error(provider, context, json_data):
+ if not v3.handle_error(context, json_data):
return False
context.get_ui().refresh_container()
context.get_ui().show_notification(
- message=context.localize(provider.LOCAL_MAP['youtube.removed.from.playlist']),
+ message=context.localize('playlist.removed_from'),
time_milliseconds=2500,
audible=False
)
@@ -127,7 +127,7 @@ def _process_remove_playlist(provider, context):
if context.get_ui().on_delete_content(playlist_name):
json_data = provider.get_client(context).remove_playlist(playlist_id=playlist_id)
- if not v3.handle_error(provider, context, json_data):
+ if not v3.handle_error(context, json_data):
return False
context.get_ui().refresh_container()
@@ -168,7 +168,7 @@ def _process_select_playlist(provider, context):
items = []
if current_page == 1:
# create playlist
- items.append((ui.bold(context.localize(provider.LOCAL_MAP['youtube.playlist.create'])), '',
+ items.append((ui.bold(context.localize('playlist.create')), '',
'playlist.create', context.create_resource_path('media', 'playlist.png')))
# add the 'Watch Later' playlist
@@ -177,7 +177,7 @@ def _process_select_playlist(provider, context):
if 'watchLater' in my_playlists:
watch_later_playlist_id = context.get_access_manager().get_watch_later_id()
if watch_later_playlist_id:
- items.append((ui.bold(context.localize(provider.LOCAL_MAP['youtube.watch_later'])), '',
+ items.append((ui.bold(context.localize('watch_later')), '',
watch_later_playlist_id, context.create_resource_path('media', 'watch_later.png')))
for playlist in playlists:
@@ -190,16 +190,16 @@ def _process_select_playlist(provider, context):
items.append((title, description, playlist_id, thumbnail))
if page_token:
- items.append((ui.bold(context.localize(provider.LOCAL_MAP['youtube.next_page'])).replace('%d', str(current_page + 1)), '',
+ items.append((ui.bold(context.localize('next_page')).replace('%d', str(current_page + 1)), '',
'playlist.next', 'DefaultFolder.png'))
- result = context.get_ui().on_select(context.localize(provider.LOCAL_MAP['youtube.playlist.select']), items)
+ result = context.get_ui().on_select(context.localize('playlist.select'), items)
if result == 'playlist.create':
result, text = context.get_ui().on_keyboard_input(
- context.localize(provider.LOCAL_MAP['youtube.playlist.create']))
+ context.localize('playlist.create'))
if result and text:
json_data = provider.get_client(context).create_playlist(title=text)
- if not v3.handle_error(provider, context, json_data):
+ if not v3.handle_error(context, json_data):
break
playlist_id = json_data.get('id', '')
@@ -228,11 +228,11 @@ def _process_rename_playlist(provider, context):
raise kodion.KodionException('playlist/rename: missing playlist_id')
current_playlist_name = context.get_param('playlist_name', '')
- result, text = context.get_ui().on_keyboard_input(context.localize(provider.LOCAL_MAP['youtube.rename']),
+ result, text = context.get_ui().on_keyboard_input(context.localize('rename'),
default=current_playlist_name)
if result and text:
json_data = provider.get_client(context).rename_playlist(playlist_id=playlist_id, new_title=text)
- if not v3.handle_error(provider, context, json_data):
+ if not v3.handle_error(context, json_data):
return
context.get_ui().refresh_container()
@@ -247,12 +247,12 @@ def _watchlater_playlist_id_change(context, method):
raise kodion.KodionException('watchlater_list/%s: missing playlist_name' % method)
if method == 'set':
- if context.get_ui().on_yes_no_input(context.get_name(), context.localize(30570) % playlist_name):
+ if context.get_ui().on_yes_no_input(context.get_name(), context.localize('watch_later.list.set.confirm') % playlist_name):
context.get_access_manager().set_watch_later_id(playlist_id)
else:
return
elif method == 'remove':
- if context.get_ui().on_yes_no_input(context.get_name(), context.localize(30569) % playlist_name):
+ if context.get_ui().on_yes_no_input(context.get_name(), context.localize('watch_later.list.remove.confirm') % playlist_name):
context.get_access_manager().set_watch_later_id(' WL')
else:
return
@@ -270,12 +270,12 @@ def _history_playlist_id_change(context, method):
raise kodion.KodionException('history_list/%s: missing playlist_name' % method)
if method == 'set':
- if context.get_ui().on_yes_no_input(context.get_name(), context.localize(30574) % playlist_name):
+ if context.get_ui().on_yes_no_input(context.get_name(), context.localize('history.list.set.confirm') % playlist_name):
context.get_access_manager().set_watch_history_id(playlist_id)
else:
return
elif method == 'remove':
- if context.get_ui().on_yes_no_input(context.get_name(), context.localize(30573) % playlist_name):
+ if context.get_ui().on_yes_no_input(context.get_name(), context.localize('history.list.remove.confirm') % playlist_name):
context.get_access_manager().set_watch_history_id('HL')
else:
return
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 c6fe1d609..372097040 100644
--- a/resources/lib/youtube_plugin/youtube/helper/yt_setup_wizard.py
+++ b/resources/lib/youtube_plugin/youtube/helper/yt_setup_wizard.py
@@ -12,53 +12,53 @@
DEFAULT_LANGUAGES = {'items': [{'snippet': {'name': 'Afrikaans', 'hl': 'af'}, 'id': 'af'}, {'snippet': {'name': 'Azerbaijani', 'hl': 'az'}, 'id': 'az'}, {'snippet': {'name': 'Indonesian', 'hl': 'id'}, 'id': 'id'}, {'snippet': {'name': 'Malay', 'hl': 'ms'}, 'id': 'ms'},
- {'snippet': {'name': 'Catalan', 'hl': 'ca'}, 'id': 'ca'}, {'snippet': {'name': 'Czech', 'hl': 'cs'}, 'id': 'cs'}, {'snippet': {'name': 'Danish', 'hl': 'da'}, 'id': 'da'}, {'snippet': {'name': 'German', 'hl': 'de'}, 'id': 'de'},
- {'snippet': {'name': 'Estonian', 'hl': 'et'}, 'id': 'et'}, {'snippet': {'name': 'English (United Kingdom)', 'hl': 'en-GB'}, 'id': 'en-GB'}, {'snippet': {'name': 'English', 'hl': 'en'}, 'id': 'en'},
- {'snippet': {'name': 'Spanish (Spain)', 'hl': 'es'}, 'id': 'es'}, {'snippet': {'name': 'Spanish (Latin America)', 'hl': 'es-419'}, 'id': 'es-419'}, {'snippet': {'name': 'Basque', 'hl': 'eu'}, 'id': 'eu'},
- {'snippet': {'name': 'Filipino', 'hl': 'fil'}, 'id': 'fil'}, {'snippet': {'name': 'French', 'hl': 'fr'}, 'id': 'fr'}, {'snippet': {'name': 'French (Canada)', 'hl': 'fr-CA'}, 'id': 'fr-CA'}, {'snippet': {'name': 'Galician', 'hl': 'gl'}, 'id': 'gl'},
- {'snippet': {'name': 'Croatian', 'hl': 'hr'}, 'id': 'hr'}, {'snippet': {'name': 'Zulu', 'hl': 'zu'}, 'id': 'zu'}, {'snippet': {'name': 'Icelandic', 'hl': 'is'}, 'id': 'is'}, {'snippet': {'name': 'Italian', 'hl': 'it'}, 'id': 'it'},
- {'snippet': {'name': 'Swahili', 'hl': 'sw'}, 'id': 'sw'}, {'snippet': {'name': 'Latvian', 'hl': 'lv'}, 'id': 'lv'}, {'snippet': {'name': 'Lithuanian', 'hl': 'lt'}, 'id': 'lt'}, {'snippet': {'name': 'Hungarian', 'hl': 'hu'}, 'id': 'hu'},
- {'snippet': {'name': 'Dutch', 'hl': 'nl'}, 'id': 'nl'}, {'snippet': {'name': 'Norwegian', 'hl': 'no'}, 'id': 'no'}, {'snippet': {'name': 'Uzbek', 'hl': 'uz'}, 'id': 'uz'}, {'snippet': {'name': 'Polish', 'hl': 'pl'}, 'id': 'pl'},
- {'snippet': {'name': 'Portuguese (Portugal)', 'hl': 'pt-PT'}, 'id': 'pt-PT'}, {'snippet': {'name': 'Portuguese (Brazil)', 'hl': 'pt'}, 'id': 'pt'}, {'snippet': {'name': 'Romanian', 'hl': 'ro'}, 'id': 'ro'},
- {'snippet': {'name': 'Albanian', 'hl': 'sq'}, 'id': 'sq'}, {'snippet': {'name': 'Slovak', 'hl': 'sk'}, 'id': 'sk'}, {'snippet': {'name': 'Slovenian', 'hl': 'sl'}, 'id': 'sl'}, {'snippet': {'name': 'Finnish', 'hl': 'fi'}, 'id': 'fi'},
- {'snippet': {'name': 'Swedish', 'hl': 'sv'}, 'id': 'sv'}, {'snippet': {'name': 'Vietnamese', 'hl': 'vi'}, 'id': 'vi'}, {'snippet': {'name': 'Turkish', 'hl': 'tr'}, 'id': 'tr'}, {'snippet': {'name': 'Bulgarian', 'hl': 'bg'}, 'id': 'bg'},
- {'snippet': {'name': 'Kyrgyz', 'hl': 'ky'}, 'id': 'ky'}, {'snippet': {'name': 'Kazakh', 'hl': 'kk'}, 'id': 'kk'}, {'snippet': {'name': 'Macedonian', 'hl': 'mk'}, 'id': 'mk'}, {'snippet': {'name': 'Mongolian', 'hl': 'mn'}, 'id': 'mn'},
- {'snippet': {'name': 'Russian', 'hl': 'ru'}, 'id': 'ru'}, {'snippet': {'name': 'Serbian', 'hl': 'sr'}, 'id': 'sr'}, {'snippet': {'name': 'Ukrainian', 'hl': 'uk'}, 'id': 'uk'}, {'snippet': {'name': 'Greek', 'hl': 'el'}, 'id': 'el'},
- {'snippet': {'name': 'Armenian', 'hl': 'hy'}, 'id': 'hy'}, {'snippet': {'name': 'Hebrew', 'hl': 'iw'}, 'id': 'iw'}, {'snippet': {'name': 'Urdu', 'hl': 'ur'}, 'id': 'ur'}, {'snippet': {'name': 'Arabic', 'hl': 'ar'}, 'id': 'ar'},
- {'snippet': {'name': 'Persian', 'hl': 'fa'}, 'id': 'fa'}, {'snippet': {'name': 'Nepali', 'hl': 'ne'}, 'id': 'ne'}, {'snippet': {'name': 'Marathi', 'hl': 'mr'}, 'id': 'mr'}, {'snippet': {'name': 'Hindi', 'hl': 'hi'}, 'id': 'hi'},
- {'snippet': {'name': 'Bengali', 'hl': 'bn'}, 'id': 'bn'}, {'snippet': {'name': 'Punjabi', 'hl': 'pa'}, 'id': 'pa'}, {'snippet': {'name': 'Gujarati', 'hl': 'gu'}, 'id': 'gu'}, {'snippet': {'name': 'Tamil', 'hl': 'ta'}, 'id': 'ta'},
- {'snippet': {'name': 'Telugu', 'hl': 'te'}, 'id': 'te'}, {'snippet': {'name': 'Kannada', 'hl': 'kn'}, 'id': 'kn'}, {'snippet': {'name': 'Malayalam', 'hl': 'ml'}, 'id': 'ml'}, {'snippet': {'name': 'Sinhala', 'hl': 'si'}, 'id': 'si'},
- {'snippet': {'name': 'Thai', 'hl': 'th'}, 'id': 'th'}, {'snippet': {'name': 'Lao', 'hl': 'lo'}, 'id': 'lo'}, {'snippet': {'name': 'Myanmar (Burmese)', 'hl': 'my'}, 'id': 'my'}, {'snippet': {'name': 'Georgian', 'hl': 'ka'}, 'id': 'ka'},
- {'snippet': {'name': 'Amharic', 'hl': 'am'}, 'id': 'am'}, {'snippet': {'name': 'Khmer', 'hl': 'km'}, 'id': 'km'}, {'snippet': {'name': 'Chinese', 'hl': 'zh-CN'}, 'id': 'zh-CN'}, {'snippet': {'name': 'Chinese (Taiwan)', 'hl': 'zh-TW'}, 'id': 'zh-TW'},
- {'snippet': {'name': 'Chinese (Hong Kong)', 'hl': 'zh-HK'}, 'id': 'zh-HK'}, {'snippet': {'name': 'Japanese', 'hl': 'ja'}, 'id': 'ja'}, {'snippet': {'name': 'Korean', 'hl': 'ko'}, 'id': 'ko'}]}
+ {'snippet': {'name': 'Catalan', 'hl': 'ca'}, 'id': 'ca'}, {'snippet': {'name': 'Czech', 'hl': 'cs'}, 'id': 'cs'}, {'snippet': {'name': 'Danish', 'hl': 'da'}, 'id': 'da'}, {'snippet': {'name': 'German', 'hl': 'de'}, 'id': 'de'},
+ {'snippet': {'name': 'Estonian', 'hl': 'et'}, 'id': 'et'}, {'snippet': {'name': 'English (United Kingdom)', 'hl': 'en-GB'}, 'id': 'en-GB'}, {'snippet': {'name': 'English', 'hl': 'en'}, 'id': 'en'},
+ {'snippet': {'name': 'Spanish (Spain)', 'hl': 'es'}, 'id': 'es'}, {'snippet': {'name': 'Spanish (Latin America)', 'hl': 'es-419'}, 'id': 'es-419'}, {'snippet': {'name': 'Basque', 'hl': 'eu'}, 'id': 'eu'},
+ {'snippet': {'name': 'Filipino', 'hl': 'fil'}, 'id': 'fil'}, {'snippet': {'name': 'French', 'hl': 'fr'}, 'id': 'fr'}, {'snippet': {'name': 'French (Canada)', 'hl': 'fr-CA'}, 'id': 'fr-CA'}, {'snippet': {'name': 'Galician', 'hl': 'gl'}, 'id': 'gl'},
+ {'snippet': {'name': 'Croatian', 'hl': 'hr'}, 'id': 'hr'}, {'snippet': {'name': 'Zulu', 'hl': 'zu'}, 'id': 'zu'}, {'snippet': {'name': 'Icelandic', 'hl': 'is'}, 'id': 'is'}, {'snippet': {'name': 'Italian', 'hl': 'it'}, 'id': 'it'},
+ {'snippet': {'name': 'Swahili', 'hl': 'sw'}, 'id': 'sw'}, {'snippet': {'name': 'Latvian', 'hl': 'lv'}, 'id': 'lv'}, {'snippet': {'name': 'Lithuanian', 'hl': 'lt'}, 'id': 'lt'}, {'snippet': {'name': 'Hungarian', 'hl': 'hu'}, 'id': 'hu'},
+ {'snippet': {'name': 'Dutch', 'hl': 'nl'}, 'id': 'nl'}, {'snippet': {'name': 'Norwegian', 'hl': 'no'}, 'id': 'no'}, {'snippet': {'name': 'Uzbek', 'hl': 'uz'}, 'id': 'uz'}, {'snippet': {'name': 'Polish', 'hl': 'pl'}, 'id': 'pl'},
+ {'snippet': {'name': 'Portuguese (Portugal)', 'hl': 'pt-PT'}, 'id': 'pt-PT'}, {'snippet': {'name': 'Portuguese (Brazil)', 'hl': 'pt'}, 'id': 'pt'}, {'snippet': {'name': 'Romanian', 'hl': 'ro'}, 'id': 'ro'},
+ {'snippet': {'name': 'Albanian', 'hl': 'sq'}, 'id': 'sq'}, {'snippet': {'name': 'Slovak', 'hl': 'sk'}, 'id': 'sk'}, {'snippet': {'name': 'Slovenian', 'hl': 'sl'}, 'id': 'sl'}, {'snippet': {'name': 'Finnish', 'hl': 'fi'}, 'id': 'fi'},
+ {'snippet': {'name': 'Swedish', 'hl': 'sv'}, 'id': 'sv'}, {'snippet': {'name': 'Vietnamese', 'hl': 'vi'}, 'id': 'vi'}, {'snippet': {'name': 'Turkish', 'hl': 'tr'}, 'id': 'tr'}, {'snippet': {'name': 'Bulgarian', 'hl': 'bg'}, 'id': 'bg'},
+ {'snippet': {'name': 'Kyrgyz', 'hl': 'ky'}, 'id': 'ky'}, {'snippet': {'name': 'Kazakh', 'hl': 'kk'}, 'id': 'kk'}, {'snippet': {'name': 'Macedonian', 'hl': 'mk'}, 'id': 'mk'}, {'snippet': {'name': 'Mongolian', 'hl': 'mn'}, 'id': 'mn'},
+ {'snippet': {'name': 'Russian', 'hl': 'ru'}, 'id': 'ru'}, {'snippet': {'name': 'Serbian', 'hl': 'sr'}, 'id': 'sr'}, {'snippet': {'name': 'Ukrainian', 'hl': 'uk'}, 'id': 'uk'}, {'snippet': {'name': 'Greek', 'hl': 'el'}, 'id': 'el'},
+ {'snippet': {'name': 'Armenian', 'hl': 'hy'}, 'id': 'hy'}, {'snippet': {'name': 'Hebrew', 'hl': 'iw'}, 'id': 'iw'}, {'snippet': {'name': 'Urdu', 'hl': 'ur'}, 'id': 'ur'}, {'snippet': {'name': 'Arabic', 'hl': 'ar'}, 'id': 'ar'},
+ {'snippet': {'name': 'Persian', 'hl': 'fa'}, 'id': 'fa'}, {'snippet': {'name': 'Nepali', 'hl': 'ne'}, 'id': 'ne'}, {'snippet': {'name': 'Marathi', 'hl': 'mr'}, 'id': 'mr'}, {'snippet': {'name': 'Hindi', 'hl': 'hi'}, 'id': 'hi'},
+ {'snippet': {'name': 'Bengali', 'hl': 'bn'}, 'id': 'bn'}, {'snippet': {'name': 'Punjabi', 'hl': 'pa'}, 'id': 'pa'}, {'snippet': {'name': 'Gujarati', 'hl': 'gu'}, 'id': 'gu'}, {'snippet': {'name': 'Tamil', 'hl': 'ta'}, 'id': 'ta'},
+ {'snippet': {'name': 'Telugu', 'hl': 'te'}, 'id': 'te'}, {'snippet': {'name': 'Kannada', 'hl': 'kn'}, 'id': 'kn'}, {'snippet': {'name': 'Malayalam', 'hl': 'ml'}, 'id': 'ml'}, {'snippet': {'name': 'Sinhala', 'hl': 'si'}, 'id': 'si'},
+ {'snippet': {'name': 'Thai', 'hl': 'th'}, 'id': 'th'}, {'snippet': {'name': 'Lao', 'hl': 'lo'}, 'id': 'lo'}, {'snippet': {'name': 'Myanmar (Burmese)', 'hl': 'my'}, 'id': 'my'}, {'snippet': {'name': 'Georgian', 'hl': 'ka'}, 'id': 'ka'},
+ {'snippet': {'name': 'Amharic', 'hl': 'am'}, 'id': 'am'}, {'snippet': {'name': 'Khmer', 'hl': 'km'}, 'id': 'km'}, {'snippet': {'name': 'Chinese', 'hl': 'zh-CN'}, 'id': 'zh-CN'}, {'snippet': {'name': 'Chinese (Taiwan)', 'hl': 'zh-TW'}, 'id': 'zh-TW'},
+ {'snippet': {'name': 'Chinese (Hong Kong)', 'hl': 'zh-HK'}, 'id': 'zh-HK'}, {'snippet': {'name': 'Japanese', 'hl': 'ja'}, 'id': 'ja'}, {'snippet': {'name': 'Korean', 'hl': 'ko'}, 'id': 'ko'}]}
DEFAULT_REGIONS = {'items': [{'snippet': {'gl': 'DZ', 'name': 'Algeria'}, 'id': 'DZ'}, {'snippet': {'gl': 'AR', 'name': 'Argentina'}, 'id': 'AR'}, {'snippet': {'gl': 'AU', 'name': 'Australia'}, 'id': 'AU'}, {'snippet': {'gl': 'AT', 'name': 'Austria'}, 'id': 'AT'},
- {'snippet': {'gl': 'AZ', 'name': 'Azerbaijan'}, 'id': 'AZ'}, {'snippet': {'gl': 'BH', 'name': 'Bahrain'}, 'id': 'BH'}, {'snippet': {'gl': 'BY', 'name': 'Belarus'}, 'id': 'BY'}, {'snippet': {'gl': 'BE', 'name': 'Belgium'}, 'id': 'BE'},
- {'snippet': {'gl': 'BA', 'name': 'Bosnia and Herzegovina'}, 'id': 'BA'}, {'snippet': {'gl': 'BR', 'name': 'Brazil'}, 'id': 'BR'}, {'snippet': {'gl': 'BG', 'name': 'Bulgaria'}, 'id': 'BG'}, {'snippet': {'gl': 'CA', 'name': 'Canada'}, 'id': 'CA'},
- {'snippet': {'gl': 'CL', 'name': 'Chile'}, 'id': 'CL'}, {'snippet': {'gl': 'CO', 'name': 'Colombia'}, 'id': 'CO'}, {'snippet': {'gl': 'HR', 'name': 'Croatia'}, 'id': 'HR'}, {'snippet': {'gl': 'CZ', 'name': 'Czech Republic'}, 'id': 'CZ'},
- {'snippet': {'gl': 'DK', 'name': 'Denmark'}, 'id': 'DK'}, {'snippet': {'gl': 'EG', 'name': 'Egypt'}, 'id': 'EG'}, {'snippet': {'gl': 'EE', 'name': 'Estonia'}, 'id': 'EE'}, {'snippet': {'gl': 'FI', 'name': 'Finland'}, 'id': 'FI'},
- {'snippet': {'gl': 'FR', 'name': 'France'}, 'id': 'FR'}, {'snippet': {'gl': 'GE', 'name': 'Georgia'}, 'id': 'GE'}, {'snippet': {'gl': 'DE', 'name': 'Germany'}, 'id': 'DE'}, {'snippet': {'gl': 'GH', 'name': 'Ghana'}, 'id': 'GH'},
- {'snippet': {'gl': 'GR', 'name': 'Greece'}, 'id': 'GR'}, {'snippet': {'gl': 'HK', 'name': 'Hong Kong'}, 'id': 'HK'}, {'snippet': {'gl': 'HU', 'name': 'Hungary'}, 'id': 'HU'}, {'snippet': {'gl': 'IS', 'name': 'Iceland'}, 'id': 'IS'},
- {'snippet': {'gl': 'IN', 'name': 'India'}, 'id': 'IN'}, {'snippet': {'gl': 'ID', 'name': 'Indonesia'}, 'id': 'ID'}, {'snippet': {'gl': 'IQ', 'name': 'Iraq'}, 'id': 'IQ'}, {'snippet': {'gl': 'IE', 'name': 'Ireland'}, 'id': 'IE'},
- {'snippet': {'gl': 'IL', 'name': 'Israel'}, 'id': 'IL'}, {'snippet': {'gl': 'IT', 'name': 'Italy'}, 'id': 'IT'}, {'snippet': {'gl': 'JM', 'name': 'Jamaica'}, 'id': 'JM'}, {'snippet': {'gl': 'JP', 'name': 'Japan'}, 'id': 'JP'},
- {'snippet': {'gl': 'JO', 'name': 'Jordan'}, 'id': 'JO'}, {'snippet': {'gl': 'KZ', 'name': 'Kazakhstan'}, 'id': 'KZ'}, {'snippet': {'gl': 'KE', 'name': 'Kenya'}, 'id': 'KE'}, {'snippet': {'gl': 'KW', 'name': 'Kuwait'}, 'id': 'KW'},
- {'snippet': {'gl': 'LV', 'name': 'Latvia'}, 'id': 'LV'}, {'snippet': {'gl': 'LB', 'name': 'Lebanon'}, 'id': 'LB'}, {'snippet': {'gl': 'LY', 'name': 'Libya'}, 'id': 'LY'}, {'snippet': {'gl': 'LT', 'name': 'Lithuania'}, 'id': 'LT'},
- {'snippet': {'gl': 'LU', 'name': 'Luxembourg'}, 'id': 'LU'}, {'snippet': {'gl': 'MK', 'name': 'Macedonia'}, 'id': 'MK'}, {'snippet': {'gl': 'MY', 'name': 'Malaysia'}, 'id': 'MY'}, {'snippet': {'gl': 'MX', 'name': 'Mexico'}, 'id': 'MX'},
- {'snippet': {'gl': 'ME', 'name': 'Montenegro'}, 'id': 'ME'}, {'snippet': {'gl': 'MA', 'name': 'Morocco'}, 'id': 'MA'}, {'snippet': {'gl': 'NP', 'name': 'Nepal'}, 'id': 'NP'}, {'snippet': {'gl': 'NL', 'name': 'Netherlands'}, 'id': 'NL'},
- {'snippet': {'gl': 'NZ', 'name': 'New Zealand'}, 'id': 'NZ'}, {'snippet': {'gl': 'NG', 'name': 'Nigeria'}, 'id': 'NG'}, {'snippet': {'gl': 'NO', 'name': 'Norway'}, 'id': 'NO'}, {'snippet': {'gl': 'OM', 'name': 'Oman'}, 'id': 'OM'},
- {'snippet': {'gl': 'PK', 'name': 'Pakistan'}, 'id': 'PK'}, {'snippet': {'gl': 'PE', 'name': 'Peru'}, 'id': 'PE'}, {'snippet': {'gl': 'PH', 'name': 'Philippines'}, 'id': 'PH'}, {'snippet': {'gl': 'PL', 'name': 'Poland'}, 'id': 'PL'},
- {'snippet': {'gl': 'PT', 'name': 'Portugal'}, 'id': 'PT'}, {'snippet': {'gl': 'PR', 'name': 'Puerto Rico'}, 'id': 'PR'}, {'snippet': {'gl': 'QA', 'name': 'Qatar'}, 'id': 'QA'}, {'snippet': {'gl': 'RO', 'name': 'Romania'}, 'id': 'RO'},
- {'snippet': {'gl': 'RU', 'name': 'Russia'}, 'id': 'RU'}, {'snippet': {'gl': 'SA', 'name': 'Saudi Arabia'}, 'id': 'SA'}, {'snippet': {'gl': 'SN', 'name': 'Senegal'}, 'id': 'SN'}, {'snippet': {'gl': 'RS', 'name': 'Serbia'}, 'id': 'RS'},
- {'snippet': {'gl': 'SG', 'name': 'Singapore'}, 'id': 'SG'}, {'snippet': {'gl': 'SK', 'name': 'Slovakia'}, 'id': 'SK'}, {'snippet': {'gl': 'SI', 'name': 'Slovenia'}, 'id': 'SI'}, {'snippet': {'gl': 'ZA', 'name': 'South Africa'}, 'id': 'ZA'},
- {'snippet': {'gl': 'KR', 'name': 'South Korea'}, 'id': 'KR'}, {'snippet': {'gl': 'ES', 'name': 'Spain'}, 'id': 'ES'}, {'snippet': {'gl': 'LK', 'name': 'Sri Lanka'}, 'id': 'LK'}, {'snippet': {'gl': 'SE', 'name': 'Sweden'}, 'id': 'SE'},
- {'snippet': {'gl': 'CH', 'name': 'Switzerland'}, 'id': 'CH'}, {'snippet': {'gl': 'TW', 'name': 'Taiwan'}, 'id': 'TW'}, {'snippet': {'gl': 'TZ', 'name': 'Tanzania'}, 'id': 'TZ'}, {'snippet': {'gl': 'TH', 'name': 'Thailand'}, 'id': 'TH'},
- {'snippet': {'gl': 'TN', 'name': 'Tunisia'}, 'id': 'TN'}, {'snippet': {'gl': 'TR', 'name': 'Turkey'}, 'id': 'TR'}, {'snippet': {'gl': 'UG', 'name': 'Uganda'}, 'id': 'UG'}, {'snippet': {'gl': 'UA', 'name': 'Ukraine'}, 'id': 'UA'},
- {'snippet': {'gl': 'AE', 'name': 'United Arab Emirates'}, 'id': 'AE'}, {'snippet': {'gl': 'GB', 'name': 'United Kingdom'}, 'id': 'GB'}, {'snippet': {'gl': 'US', 'name': 'United States'}, 'id': 'US'}, {'snippet': {'gl': 'VN', 'name': 'Vietnam'}, 'id': 'VN'},
- {'snippet': {'gl': 'YE', 'name': 'Yemen'}, 'id': 'YE'}, {'snippet': {'gl': 'ZW', 'name': 'Zimbabwe'}, 'id': 'ZW'}]}
+ {'snippet': {'gl': 'AZ', 'name': 'Azerbaijan'}, 'id': 'AZ'}, {'snippet': {'gl': 'BH', 'name': 'Bahrain'}, 'id': 'BH'}, {'snippet': {'gl': 'BY', 'name': 'Belarus'}, 'id': 'BY'}, {'snippet': {'gl': 'BE', 'name': 'Belgium'}, 'id': 'BE'},
+ {'snippet': {'gl': 'BA', 'name': 'Bosnia and Herzegovina'}, 'id': 'BA'}, {'snippet': {'gl': 'BR', 'name': 'Brazil'}, 'id': 'BR'}, {'snippet': {'gl': 'BG', 'name': 'Bulgaria'}, 'id': 'BG'}, {'snippet': {'gl': 'CA', 'name': 'Canada'}, 'id': 'CA'},
+ {'snippet': {'gl': 'CL', 'name': 'Chile'}, 'id': 'CL'}, {'snippet': {'gl': 'CO', 'name': 'Colombia'}, 'id': 'CO'}, {'snippet': {'gl': 'HR', 'name': 'Croatia'}, 'id': 'HR'}, {'snippet': {'gl': 'CZ', 'name': 'Czech Republic'}, 'id': 'CZ'},
+ {'snippet': {'gl': 'DK', 'name': 'Denmark'}, 'id': 'DK'}, {'snippet': {'gl': 'EG', 'name': 'Egypt'}, 'id': 'EG'}, {'snippet': {'gl': 'EE', 'name': 'Estonia'}, 'id': 'EE'}, {'snippet': {'gl': 'FI', 'name': 'Finland'}, 'id': 'FI'},
+ {'snippet': {'gl': 'FR', 'name': 'France'}, 'id': 'FR'}, {'snippet': {'gl': 'GE', 'name': 'Georgia'}, 'id': 'GE'}, {'snippet': {'gl': 'DE', 'name': 'Germany'}, 'id': 'DE'}, {'snippet': {'gl': 'GH', 'name': 'Ghana'}, 'id': 'GH'},
+ {'snippet': {'gl': 'GR', 'name': 'Greece'}, 'id': 'GR'}, {'snippet': {'gl': 'HK', 'name': 'Hong Kong'}, 'id': 'HK'}, {'snippet': {'gl': 'HU', 'name': 'Hungary'}, 'id': 'HU'}, {'snippet': {'gl': 'IS', 'name': 'Iceland'}, 'id': 'IS'},
+ {'snippet': {'gl': 'IN', 'name': 'India'}, 'id': 'IN'}, {'snippet': {'gl': 'ID', 'name': 'Indonesia'}, 'id': 'ID'}, {'snippet': {'gl': 'IQ', 'name': 'Iraq'}, 'id': 'IQ'}, {'snippet': {'gl': 'IE', 'name': 'Ireland'}, 'id': 'IE'},
+ {'snippet': {'gl': 'IL', 'name': 'Israel'}, 'id': 'IL'}, {'snippet': {'gl': 'IT', 'name': 'Italy'}, 'id': 'IT'}, {'snippet': {'gl': 'JM', 'name': 'Jamaica'}, 'id': 'JM'}, {'snippet': {'gl': 'JP', 'name': 'Japan'}, 'id': 'JP'},
+ {'snippet': {'gl': 'JO', 'name': 'Jordan'}, 'id': 'JO'}, {'snippet': {'gl': 'KZ', 'name': 'Kazakhstan'}, 'id': 'KZ'}, {'snippet': {'gl': 'KE', 'name': 'Kenya'}, 'id': 'KE'}, {'snippet': {'gl': 'KW', 'name': 'Kuwait'}, 'id': 'KW'},
+ {'snippet': {'gl': 'LV', 'name': 'Latvia'}, 'id': 'LV'}, {'snippet': {'gl': 'LB', 'name': 'Lebanon'}, 'id': 'LB'}, {'snippet': {'gl': 'LY', 'name': 'Libya'}, 'id': 'LY'}, {'snippet': {'gl': 'LT', 'name': 'Lithuania'}, 'id': 'LT'},
+ {'snippet': {'gl': 'LU', 'name': 'Luxembourg'}, 'id': 'LU'}, {'snippet': {'gl': 'MK', 'name': 'Macedonia'}, 'id': 'MK'}, {'snippet': {'gl': 'MY', 'name': 'Malaysia'}, 'id': 'MY'}, {'snippet': {'gl': 'MX', 'name': 'Mexico'}, 'id': 'MX'},
+ {'snippet': {'gl': 'ME', 'name': 'Montenegro'}, 'id': 'ME'}, {'snippet': {'gl': 'MA', 'name': 'Morocco'}, 'id': 'MA'}, {'snippet': {'gl': 'NP', 'name': 'Nepal'}, 'id': 'NP'}, {'snippet': {'gl': 'NL', 'name': 'Netherlands'}, 'id': 'NL'},
+ {'snippet': {'gl': 'NZ', 'name': 'New Zealand'}, 'id': 'NZ'}, {'snippet': {'gl': 'NG', 'name': 'Nigeria'}, 'id': 'NG'}, {'snippet': {'gl': 'NO', 'name': 'Norway'}, 'id': 'NO'}, {'snippet': {'gl': 'OM', 'name': 'Oman'}, 'id': 'OM'},
+ {'snippet': {'gl': 'PK', 'name': 'Pakistan'}, 'id': 'PK'}, {'snippet': {'gl': 'PE', 'name': 'Peru'}, 'id': 'PE'}, {'snippet': {'gl': 'PH', 'name': 'Philippines'}, 'id': 'PH'}, {'snippet': {'gl': 'PL', 'name': 'Poland'}, 'id': 'PL'},
+ {'snippet': {'gl': 'PT', 'name': 'Portugal'}, 'id': 'PT'}, {'snippet': {'gl': 'PR', 'name': 'Puerto Rico'}, 'id': 'PR'}, {'snippet': {'gl': 'QA', 'name': 'Qatar'}, 'id': 'QA'}, {'snippet': {'gl': 'RO', 'name': 'Romania'}, 'id': 'RO'},
+ {'snippet': {'gl': 'RU', 'name': 'Russia'}, 'id': 'RU'}, {'snippet': {'gl': 'SA', 'name': 'Saudi Arabia'}, 'id': 'SA'}, {'snippet': {'gl': 'SN', 'name': 'Senegal'}, 'id': 'SN'}, {'snippet': {'gl': 'RS', 'name': 'Serbia'}, 'id': 'RS'},
+ {'snippet': {'gl': 'SG', 'name': 'Singapore'}, 'id': 'SG'}, {'snippet': {'gl': 'SK', 'name': 'Slovakia'}, 'id': 'SK'}, {'snippet': {'gl': 'SI', 'name': 'Slovenia'}, 'id': 'SI'}, {'snippet': {'gl': 'ZA', 'name': 'South Africa'}, 'id': 'ZA'},
+ {'snippet': {'gl': 'KR', 'name': 'South Korea'}, 'id': 'KR'}, {'snippet': {'gl': 'ES', 'name': 'Spain'}, 'id': 'ES'}, {'snippet': {'gl': 'LK', 'name': 'Sri Lanka'}, 'id': 'LK'}, {'snippet': {'gl': 'SE', 'name': 'Sweden'}, 'id': 'SE'},
+ {'snippet': {'gl': 'CH', 'name': 'Switzerland'}, 'id': 'CH'}, {'snippet': {'gl': 'TW', 'name': 'Taiwan'}, 'id': 'TW'}, {'snippet': {'gl': 'TZ', 'name': 'Tanzania'}, 'id': 'TZ'}, {'snippet': {'gl': 'TH', 'name': 'Thailand'}, 'id': 'TH'},
+ {'snippet': {'gl': 'TN', 'name': 'Tunisia'}, 'id': 'TN'}, {'snippet': {'gl': 'TR', 'name': 'Turkey'}, 'id': 'TR'}, {'snippet': {'gl': 'UG', 'name': 'Uganda'}, 'id': 'UG'}, {'snippet': {'gl': 'UA', 'name': 'Ukraine'}, 'id': 'UA'},
+ {'snippet': {'gl': 'AE', 'name': 'United Arab Emirates'}, 'id': 'AE'}, {'snippet': {'gl': 'GB', 'name': 'United Kingdom'}, 'id': 'GB'}, {'snippet': {'gl': 'US', 'name': 'United States'}, 'id': 'US'}, {'snippet': {'gl': 'VN', 'name': 'Vietnam'}, 'id': 'VN'},
+ {'snippet': {'gl': 'YE', 'name': 'Yemen'}, 'id': 'YE'}, {'snippet': {'gl': 'ZW', 'name': 'Zimbabwe'}, 'id': 'ZW'}]}
def _process_language(provider, context):
- if not context.get_ui().on_yes_no_input(context.localize(provider.LOCAL_MAP['youtube.setup_wizard.adjust']),
- context.localize(provider.LOCAL_MAP['youtube.setup_wizard.adjust.language_and_region'])):
+ if not context.get_ui().on_yes_no_input(context.localize('setup_wizard.adjust'),
+ context.localize('setup_wizard.adjust.language_and_region')):
return
client = provider.get_client(context)
@@ -79,7 +79,7 @@ def _process_language(provider, context):
language_list.append((language_name, hl))
language_list = sorted(language_list, key=lambda x: x[0])
language_id = context.get_ui().on_select(
- context.localize(provider.LOCAL_MAP['youtube.setup_wizard.select_language']), language_list)
+ context.localize('setup_wizard.select_language'), language_list)
if language_id == -1:
return
@@ -94,7 +94,7 @@ def _process_language(provider, context):
gl = item['snippet']['gl']
region_list.append((region_name, gl))
region_list = sorted(region_list, key=lambda x: x[0])
- region_id = context.get_ui().on_select(context.localize(provider.LOCAL_MAP['youtube.setup_wizard.select_region']),
+ region_id = context.get_ui().on_select(context.localize('setup_wizard.select_region'),
region_list)
if region_id == -1:
return
@@ -105,8 +105,8 @@ def _process_language(provider, context):
provider.reset_client()
-def _process_geo_location(provider, context):
- if not context.get_ui().on_yes_no_input(context.get_name(), context.localize(provider.LOCAL_MAP['youtube.perform.geolocation'])):
+def _process_geo_location(context):
+ if not context.get_ui().on_yes_no_input(context.get_name(), context.localize('perform.geolocation')):
return
locator = Locator()
@@ -118,4 +118,4 @@ def _process_geo_location(provider, context):
def process(provider, context):
_process_language(provider, context)
- _process_geo_location(provider, context)
+ _process_geo_location(context)
diff --git a/resources/lib/youtube_plugin/youtube/helper/yt_specials.py b/resources/lib/youtube_plugin/youtube/helper/yt_specials.py
index 5cd5935da..3d8a56e55 100644
--- a/resources/lib/youtube_plugin/youtube/helper/yt_specials.py
+++ b/resources/lib/youtube_plugin/youtube/helper/yt_specials.py
@@ -22,7 +22,7 @@ def _process_related_videos(provider, context):
video_id = context.get_param('video_id', '')
if video_id:
json_data = provider.get_client(context).get_related_videos(video_id=video_id, page_token=page_token)
- if not v3.handle_error(provider, context, json_data):
+ if not v3.handle_error(context, json_data):
return False
result.extend(v3.response_to_items(provider, context, json_data, process_next_page=False))
@@ -37,7 +37,7 @@ def _process_parent_comments(provider, context):
video_id = context.get_param('video_id', '')
if video_id:
json_data = provider.get_client(context).get_parent_comments(video_id=video_id, page_token=page_token)
- if not v3.handle_error(provider, context, json_data):
+ if not v3.handle_error(context, json_data):
return False
result.extend(v3.response_to_items(provider, context, json_data))
@@ -52,7 +52,7 @@ def _process_child_comments(provider, context):
parent_id = context.get_param('parent_id', '')
if parent_id:
json_data = provider.get_client(context).get_child_comments(parent_id=parent_id, page_token=page_token)
- if not v3.handle_error(provider, context, json_data):
+ if not v3.handle_error(context, json_data):
return False
result.extend(v3.response_to_items(provider, context, json_data))
@@ -65,7 +65,7 @@ def _process_recommendations(provider, context):
page_token = context.get_param('page_token', '')
json_data = provider.get_client(context).get_activities('home', page_token=page_token)
- if not v3.handle_error(provider, context, json_data):
+ if not v3.handle_error(context, json_data):
return False
result.extend(v3.response_to_items(provider, context, json_data))
return result
@@ -77,7 +77,7 @@ def _process_popular_right_now(provider, context):
page_token = context.get_param('page_token', '')
json_data = provider.get_client(context).get_popular_videos(page_token=page_token)
- if not v3.handle_error(provider, context, json_data):
+ if not v3.handle_error(context, json_data):
return False
result.extend(v3.response_to_items(provider, context, json_data))
@@ -94,12 +94,12 @@ def _process_browse_channels(provider, context):
if guide_id:
json_data = client.get_guide_category(guide_id)
- if not v3.handle_error(provider, context, json_data):
+ if not v3.handle_error(context, json_data):
return False
result.extend(v3.response_to_items(provider, context, json_data))
else:
json_data = context.get_function_cache().get(kodion.utils.FunctionCache.ONE_MONTH, client.get_guide_categories)
- if not v3.handle_error(provider, context, json_data):
+ if not v3.handle_error(context, json_data):
return False
result.extend(v3.response_to_items(provider, context, json_data))
@@ -112,7 +112,7 @@ def _process_disliked_videos(provider, context):
page_token = context.get_param('page_token', '')
json_data = provider.get_client(context).get_disliked_videos(page_token=page_token)
- if not v3.handle_error(provider, context, json_data):
+ if not v3.handle_error(context, json_data):
return False
result.extend(v3.response_to_items(provider, context, json_data))
return result
@@ -130,7 +130,7 @@ def _sort(x):
location = context.get_param('location', False)
json_data = provider.get_client(context).get_live_events(event_type=event_type, page_token=page_token, location=location)
- if not v3.handle_error(provider, context, json_data):
+ if not v3.handle_error(context, json_data):
return False
result.extend(v3.response_to_items(provider, context, json_data, sort=_sort, reverse_sort=True))
@@ -148,7 +148,7 @@ def _extract_urls(_video_id):
result = []
progress_dialog = \
- context.get_ui().create_progress_dialog(heading=context.localize(kodion.constants.localize.COMMON_PLEASE_WAIT),
+ context.get_ui().create_progress_dialog(heading=context.localize('please_wait'),
background=False)
resource_manager = provider.get_resource_manager(context)
@@ -185,9 +185,8 @@ def _extract_urls(_video_id):
if not result:
progress_dialog.close()
- context.get_ui().on_ok(title=context.localize(provider.LOCAL_MAP['youtube.video.description.links']),
- text=context.localize(
- provider.LOCAL_MAP['youtube.video.description.links.not_found']))
+ context.get_ui().on_ok(title=context.localize('video.description.links'),
+ text=context.localize('video.description.links.not_found'))
return False
return result
diff --git a/resources/lib/youtube_plugin/youtube/helper/yt_subscriptions.py b/resources/lib/youtube_plugin/youtube/helper/yt_subscriptions.py
index 712308492..fd01629bd 100644
--- a/resources/lib/youtube_plugin/youtube/helper/yt_subscriptions.py
+++ b/resources/lib/youtube_plugin/youtube/helper/yt_subscriptions.py
@@ -19,7 +19,7 @@ def _process_list(provider, context):
page_token = context.get_param('page_token', '')
# no caching
json_data = provider.get_client(context).get_subscription('mine', page_token=page_token)
- if not v3.handle_error(provider, context, json_data):
+ if not v3.handle_error(context, json_data):
return []
result.extend(v3.response_to_items(provider, context, json_data))
@@ -36,11 +36,11 @@ def _process_add(provider, context):
if subscription_id:
json_data = provider.get_client(context).subscribe(subscription_id)
- if not v3.handle_error(provider, context, json_data):
+ if not v3.handle_error(context, json_data):
return False
context.get_ui().show_notification(
- context.localize(provider.LOCAL_MAP['youtube.subscribed.to.channel']),
+ context.localize('subscribed.to.channel'),
time_milliseconds=2500,
audible=False
)
@@ -59,13 +59,13 @@ def _process_remove(provider, context):
if subscription_id:
json_data = provider.get_client(context).unsubscribe(subscription_id)
- if not v3.handle_error(provider, context, json_data):
+ if not v3.handle_error(context, json_data):
return False
context.get_ui().refresh_container()
context.get_ui().show_notification(
- context.localize(provider.LOCAL_MAP['youtube.unsubscribed.from.channel']),
+ context.localize('unsubscribed.from.channel'),
time_milliseconds=2500,
audible=False
)
diff --git a/resources/lib/youtube_plugin/youtube/helper/yt_video.py b/resources/lib/youtube_plugin/youtube/helper/yt_video.py
index 81728600e..b1362406f 100644
--- a/resources/lib/youtube_plugin/youtube/helper/yt_video.py
+++ b/resources/lib/youtube_plugin/youtube/helper/yt_video.py
@@ -39,7 +39,7 @@ def _process_rate_video(provider, context, re_match):
if not current_rating:
client = provider.get_client(context)
json_data = client.get_video_rating(video_id)
- if not v3.handle_error(provider, context, json_data):
+ if not v3.handle_error(context, json_data):
return False
items = json_data.get('items', [])
@@ -50,8 +50,8 @@ def _process_rate_video(provider, context, re_match):
if not rating_param:
for rating in ratings:
if rating != current_rating:
- rating_items.append((context.localize(provider.LOCAL_MAP['youtube.video.rate.%s' % rating]), rating))
- result = context.get_ui().on_select(context.localize(provider.LOCAL_MAP['youtube.video.rate']), rating_items)
+ rating_items.append((context.localize('video.rate.%s' % rating), rating))
+ result = context.get_ui().on_select(context.localize('video.rate'), rating_items)
elif rating_param != current_rating:
result = rating_param
else:
@@ -63,7 +63,7 @@ def _process_rate_video(provider, context, re_match):
response = provider.get_client(context).rate_video(video_id, result)
if response.get('status_code') != 204:
- notify_message = context.localize(provider.LOCAL_MAP['youtube.failed'])
+ notify_message = context.localize('failed')
elif response.get('status_code') == 204:
# this will be set if we are in the 'Liked Video' playlist
@@ -71,11 +71,11 @@ def _process_rate_video(provider, context, re_match):
context.get_ui().refresh_container()
if result == 'none':
- notify_message = context.localize(provider.LOCAL_MAP['youtube.unrated.video'])
+ notify_message = context.localize('unrated.video')
elif result == 'like':
- notify_message = context.localize(provider.LOCAL_MAP['youtube.liked.video'])
+ notify_message = context.localize('liked.video')
elif result == 'dislike':
- notify_message = context.localize(provider.LOCAL_MAP['youtube.disliked.video'])
+ notify_message = context.localize('disliked.video')
if notify_message:
context.get_ui().show_notification(
@@ -87,7 +87,7 @@ def _process_rate_video(provider, context, re_match):
return True
-def _process_more_for_video(provider, context):
+def _process_more_for_video(context):
video_id = context.get_param('video_id', '')
if not video_id:
raise kodion.KodionException('video/more/: missing video_id')
@@ -97,27 +97,26 @@ def _process_more_for_video(provider, context):
is_logged_in = context.get_param('logged_in', '0')
if is_logged_in == '1':
# add video to a playlist
- items.append((context.localize(provider.LOCAL_MAP['youtube.video.add_to_playlist']),
+ items.append((context.localize('video.add_to_playlist'),
'RunPlugin(%s)' % context.create_uri(['playlist', 'select', 'playlist'], {'video_id': video_id})))
-
# default items
- items.extend([(context.localize(provider.LOCAL_MAP['youtube.related_videos']),
+ items.extend([(context.localize('related_videos'),
'Container.Update(%s)' % context.create_uri(['special', 'related_videos'], {'video_id': video_id})),
- (context.localize(provider.LOCAL_MAP['youtube.video.comments']),
+ (context.localize('video.comments'),
'Container.Update(%s)' % context.create_uri(['special', 'parent_comments'], {'video_id': video_id})),
- (context.localize(provider.LOCAL_MAP['youtube.video.description.links']),
+ (context.localize('video.description.links'),
'Container.Update(%s)' % context.create_uri(['special', 'description_links'],
{'video_id': video_id}))])
if is_logged_in == '1':
# rate a video
refresh_container = context.get_param('refresh_container', '0')
- items.append((context.localize(provider.LOCAL_MAP['youtube.video.rate']),
+ items.append((context.localize('video.rate'),
'RunPlugin(%s)' % context.create_uri(['video', 'rate'], {'video_id': video_id,
'refresh_container': refresh_container})))
- result = context.get_ui().on_select(context.localize(provider.LOCAL_MAP['youtube.video.more']), items)
+ result = context.get_ui().on_select(context.localize('video.more'), items)
if result != -1:
context.execute(result)
@@ -126,5 +125,5 @@ def process(method, provider, context, re_match):
if method == 'rate':
return _process_rate_video(provider, context, re_match)
if method == 'more':
- return _process_more_for_video(provider, context)
+ return _process_more_for_video(context)
raise kodion.KodionException("Unknown method '%s'" % method)
diff --git a/resources/lib/youtube_plugin/youtube/provider.py b/resources/lib/youtube_plugin/youtube/provider.py
index a64685dd6..7cedf1085 100644
--- a/resources/lib/youtube_plugin/youtube/provider.py
+++ b/resources/lib/youtube_plugin/youtube/provider.py
@@ -55,157 +55,6 @@
class Provider(AbstractProvider):
- LOCAL_MAP = {'youtube.search': 30102,
- 'youtube.next_page': 30106,
- 'youtube.watch_later': 30107,
- 'youtube.video.rate.none': 30108,
- 'youtube.remove': 30108,
- 'youtube.sign.in': 30111,
- 'youtube.sign.out': 30112,
- 'youtube.rename': 30113,
- 'youtube.delete': 30118,
- 'youtube.api.key': 30201,
- 'youtube.api.id': 30202,
- 'youtube.api.secret': 30203,
- 'youtube.channels': 30500,
- 'youtube.playlists': 30501,
- 'youtube.go_to_channel': 30502,
- 'youtube.subscriptions': 30504,
- 'youtube.unsubscribe': 30505,
- 'youtube.subscribe': 30506,
- 'youtube.my_channel': 30507,
- 'youtube.video.liked': 30508,
- 'youtube.history': 30509,
- 'youtube.my_subscriptions': 30510,
- 'youtube.video.queue': 30511,
- 'youtube.browse_channels': 30512,
- 'youtube.popular_right_now': 30513,
- 'youtube.related_videos': 30514,
- 'youtube.setting.auto_remove_watch_later': 30515,
- 'youtube.subscribe_to': 30517,
- 'youtube.sign.go_to': 30518,
- 'youtube.sign.enter_code': 30519,
- 'youtube.video.add_to_playlist': 30520,
- 'youtube.playlist.select': 30521,
- 'youtube.playlist.create': 30522,
- 'youtube.setup_wizard.select_language': 30524,
- 'youtube.setup_wizard.select_region': 30525,
- 'youtube.setup_wizard.adjust': 30526,
- 'youtube.setup_wizard.adjust.language_and_region': 30527,
- 'youtube.video.rate': 30528,
- 'youtube.video.rate.like': 30529,
- 'youtube.video.rate.dislike': 30530,
- 'youtube.playlist.play.all': 30531,
- 'youtube.playlist.play.default': 30532,
- 'youtube.playlist.play.reverse': 30533,
- 'youtube.playlist.play.shuffle': 30534,
- 'youtube.playlist.play.select': 30535,
- 'youtube.playlist.progress.updating': 30536,
- 'youtube.playlist.play.from_here': 30537,
- 'youtube.video.disliked': 30538,
- 'youtube.live': 30539,
- 'youtube.upcoming': 30766,
- 'youtube.video.play_with': 30540,
- 'youtube.error.rtmpe_not_supported': 30542,
- 'youtube.refresh': 30543,
- 'youtube.video.description.links': 30544,
- 'youtube.video.description.links.not_found': 30545,
- 'youtube.sign.twice.title': 30546,
- 'youtube.sign.twice.text': 30547,
- 'youtube.video.more': 30548,
- 'youtube.error.no_video_streams_found': 30549,
- 'youtube.recommendations': 30551,
- 'youtube.function.cache': 30557,
- 'youtube.search.history': 30558,
- 'youtube.subtitle.language': 30560,
- 'youtube.none': 30561,
- 'youtube.prompt': 30566,
- 'youtube.set.as.watchlater': 30567,
- 'youtube.remove.as.watchlater': 30568,
- 'youtube.set.as.history': 30571,
- 'youtube.remove.as.history': 30572,
- 'youtube.succeeded': 30575,
- 'youtube.failed': 30576,
- 'youtube.settings': 30577,
- 'youtube.mpd.enable.confirm': 30579,
- 'youtube.reset.access.manager.confirm': 30581,
- 'youtube.my_subscriptions_filtered': 30584,
- 'youtube.add.my_subscriptions.filter': 30587,
- 'youtube.remove.my_subscriptions.filter': 30588,
- 'youtube.added.my_subscriptions.filter': 30589,
- 'youtube.removed.my_subscriptions.filter': 30590,
- 'youtube.updated_': 30597,
- 'youtube.api.personal.enabled': 30598,
- 'youtube.api.personal.failed': 30599,
- 'youtube.subtitle._with_fallback': 30601,
- 'youtube.subtitle.no.auto.generated': 30602,
- 'youtube.quick.search': 30605,
- 'youtube.quick.search.incognito': 30606,
- 'youtube.clear_history': 30609,
- 'youtube.clear_history_confirmation': 30610,
- 'youtube.saved.playlists': 30611,
- 'youtube.retry': 30612,
- 'youtube.failed.watch_later.retry': 30614,
- 'youtube.cancel': 30615,
- 'youtube.must.be.signed.in': 30616,
- 'youtube.select.listen.ip': 30644,
- 'youtube.purchases': 30622,
- 'youtube.requires.krypton': 30624,
- 'youtube.inputstreamhelper.is.installed': 30625,
- 'youtube.upcoming.live': 30646,
- 'youtube.completed.live': 30647,
- 'youtube.api.key.incorrect': 30648,
- 'youtube.client.id.incorrect': 30649,
- 'youtube.client.secret.incorrect': 30650,
- 'youtube.perform.geolocation': 30653,
- 'youtube.my_location': 30654,
- 'youtube.switch.user': 30655,
- 'youtube.user.new': 30656,
- 'youtube.user.unnamed': 30657,
- 'youtube.enter.user.name': 30658,
- 'youtube.user.changed': 30659,
- 'youtube.remove.a.user': 30662,
- 'youtube.rename.a.user': 30663,
- 'youtube.switch.user.now': 30665,
- 'youtube.removed': 30666,
- 'youtube.renamed': 30667,
- 'youtube.playback.history': 30673,
- 'youtube.mark.watched': 30670,
- 'youtube.mark.unwatched': 30669,
- 'youtube.reset.resume.point': 30674,
- 'youtube.data.cache': 30687,
- 'youtube.httpd.not.running': 30699,
- 'youtube.client.ip': 30700,
- 'youtube.client.ip.failed': 30701,
- 'youtube.video.play_with_subtitles': 30702,
- 'youtube.are.you.sure': 30703,
- 'youtube.subtitles.download': 30705,
- 'youtube.pre.download.subtitles': 30706,
- 'youtube.untitled': 30707,
- 'youtube.video.play_audio_only': 30708,
- 'youtube.failed.watch_later.retry.2': 30709,
- 'youtube.failed.watch_later.retry.3': 30710,
- 'youtube.added.to.watch.later': 30713,
- 'youtube.added.to.playlist': 30714,
- 'youtube.removed.from.playlist': 30715,
- 'youtube.liked.video': 30716,
- 'youtube.disliked.video': 30717,
- 'youtube.unrated.video': 30718,
- 'youtube.subscribed.to.channel': 30719,
- 'youtube.unsubscribed.from.channel': 30720,
- 'youtube.uploads': 30726,
- 'youtube.video.play_ask_for_quality': 30730,
- 'youtube.key.requirement.notification': 30731,
- 'youtube.video.comments': 30732,
- 'youtube.video.comments.likes': 30733,
- 'youtube.video.comments.replies': 30734,
- 'youtube.video.comments.edited': 30735,
- 'youtube.stats.viewCount': 30767,
- 'youtube.stats.likeCount': 30733,
- # 'youtube.stats.favoriteCount': 30100,
- 'youtube.stats.commentCount': 30732,
- }
-
def __init__(self):
super(Provider, self).__init__()
self._resource_manager = None
@@ -286,12 +135,11 @@ def get_client(self, context):
youtube_config = YouTube.CONFIGS.get('main')
- dev_id = context.get_param('addon_id', None)
+ dev_id = context.get_param('addon_id')
dev_configs = YouTube.CONFIGS.get('developer')
dev_config = self.get_dev_config(context, dev_id, dev_configs)
dev_keys = dev_config.get('main') if dev_config else None
- client = None
refresh_tokens = []
if dev_id:
@@ -429,7 +277,7 @@ def get_fanart(context):
# noinspection PyUnusedLocal
@RegisterProviderPath('^/uri2addon/$')
def on_uri2addon(self, context, re_match):
- uri = context.get_param('uri', '')
+ uri = context.get_param('uri')
if not uri:
return False
@@ -454,7 +302,7 @@ def _on_playlist(self, context, re_match):
# no caching
json_data = self.get_client(context).get_playlist_items(playlist_id=playlist_id, page_token=page_token)
- if not v3.handle_error(self, context, json_data):
+ if not v3.handle_error(context, json_data):
return False
result.extend(v3.response_to_items(self, context, json_data))
@@ -478,7 +326,7 @@ def _on_channel_playlist(self, context, re_match):
# no caching
json_data = client.get_playlist_items(playlist_id=playlist_id, page_token=page_token)
- if not v3.handle_error(self, context, json_data):
+ if not v3.handle_error(context, json_data):
return False
result.extend(v3.response_to_items(self, context, json_data))
@@ -501,8 +349,8 @@ def _on_channel_playlists(self, context, re_match):
resource_manager = self.get_resource_manager(context)
item_params = {}
- incognito = context.get_param('incognito', False)
- addon_id = context.get_param('addon_id', '')
+ incognito = context.get_param('incognito')
+ addon_id = context.get_param('addon_id')
if incognito:
item_params.update({'incognito': incognito})
if addon_id:
@@ -511,7 +359,7 @@ def _on_channel_playlists(self, context, re_match):
playlists = resource_manager.get_related_playlists(channel_id)
uploads_playlist = playlists.get('uploads', '')
if uploads_playlist:
- uploads_item = DirectoryItem(context.get_ui().bold(context.localize(self.LOCAL_MAP['youtube.uploads'])),
+ uploads_item = DirectoryItem(context.get_ui().bold(context.localize('uploads')),
context.create_uri(['channel', channel_id, 'playlist', uploads_playlist],
item_params),
image=context.create_resource_path('media', 'playlist.png'))
@@ -519,7 +367,7 @@ def _on_channel_playlists(self, context, re_match):
# no caching
json_data = self.get_client(context).get_playlists_of_channel(channel_id, page_token)
- if not v3.handle_error(self, context, json_data):
+ if not v3.handle_error(context, json_data):
return False
result.extend(v3.response_to_items(self, context, json_data))
@@ -542,7 +390,7 @@ def _on_channel_live(self, context, re_match):
# no caching
json_data = self.get_client(context).search(q='', search_type='video', event_type='live', channel_id=channel_id, page_token=page_token, safe_search=safe_search)
- if not v3.handle_error(self, context, json_data):
+ if not v3.handle_error(context, json_data):
return False
result.extend(v3.response_to_items(self, context, json_data))
@@ -556,14 +404,21 @@ def _on_channel_live(self, context, re_match):
@RegisterProviderPath('^/(?P(channel|user))/(?P[^/]+)/$')
def _on_channel(self, context, re_match):
- listitem_channel_id = context.get_ui().get_info_label('Container.ListItem(0).Property(channel_id)')
+ localize = context.localize
+ create_path = context.create_resource_path
+ create_uri = context.create_uri
+ function_cache = context.get_function_cache()
+ params = context.get_params()
+ ui = context.get_ui()
+
+ listitem_channel_id = ui.get_info_label('Container.ListItem(0).Property(channel_id)')
method = re_match.group('method')
channel_id = re_match.group('channel_id')
if (method == 'channel' and channel_id and channel_id.lower() == 'property'
and listitem_channel_id and listitem_channel_id.lower().startswith(('mine', 'uc'))):
- context.execute('Container.Update(%s)' % context.create_uri(['channel', listitem_channel_id])) # redirect if keymap, without redirect results in 'invalid handle -1'
+ context.execute('Container.Update(%s)' % create_uri(['channel', listitem_channel_id])) # redirect if keymap, without redirect results in 'invalid handle -1'
if method == 'channel' and not channel_id:
return False
@@ -582,9 +437,9 @@ def _on_channel(self, context, re_match):
if method == 'user' or channel_id == 'mine':
context.log_debug('Trying to get channel id for user "%s"' % channel_id)
- json_data = context.get_function_cache().get(FunctionCache.ONE_DAY,
- self.get_client(context).get_channel_by_username, channel_id)
- if not v3.handle_error(self, context, json_data):
+ json_data = function_cache.get(FunctionCache.ONE_DAY,
+ self.get_client(context).get_channel_by_username, channel_id)
+ if not v3.handle_error(context, json_data):
return False
# we correct the channel id based on the username
@@ -600,52 +455,52 @@ def _on_channel(self, context, re_match):
return False
channel_fanarts = resource_manager.get_fanarts([channel_id])
- page = context.get_param('page', 1)
- page_token = context.get_param('page_token', '')
- incognito = context.get_param('incognito', False)
- addon_id = context.get_param('addon_id', '')
+ page = params.get('page', 1)
+ page_token = params.get('page_token', '')
+ incognito = params.get('incognito')
+ addon_id = params.get('addon_id')
item_params = {}
if incognito:
item_params.update({'incognito': incognito})
if addon_id:
item_params.update({'addon_id': addon_id})
- hide_folders = context.get_param('hide_folders', False)
+ hide_folders = params.get('hide_folders')
if page == 1 and not hide_folders:
- hide_playlists = context.get_param('hide_playlists', False)
- hide_search = context.get_param('hide_search', False)
- hide_live = context.get_param('hide_live', False)
+ hide_playlists = params.get('hide_playlists')
+ hide_search = params.get('hide_search')
+ hide_live = params.get('hide_live')
if not hide_playlists:
- playlists_item = DirectoryItem(context.get_ui().bold(context.localize(self.LOCAL_MAP['youtube.playlists'])),
- context.create_uri(['channel', channel_id, 'playlists'], item_params),
- image=context.create_resource_path('media', 'playlist.png'))
+ playlists_item = DirectoryItem(ui.bold(localize('playlists')),
+ create_uri(['channel', channel_id, 'playlists'], item_params),
+ image=create_path('media', 'playlist.png'))
playlists_item.set_fanart(channel_fanarts.get(channel_id, self.get_fanart(context)))
result.append(playlists_item)
search_live_id = mine_id if mine_id else channel_id
if not hide_search:
- search_item = NewSearchItem(context, alt_name=context.get_ui().bold(context.localize(self.LOCAL_MAP['youtube.search'])),
- image=context.create_resource_path('media', 'search.png'),
+ search_item = NewSearchItem(context, alt_name=ui.bold(localize('search')),
+ image=create_path('media', 'search.png'),
fanart=self.get_fanart(context), channel_id=search_live_id, incognito=incognito, addon_id=addon_id)
search_item.set_fanart(self.get_fanart(context))
result.append(search_item)
if not hide_live:
- live_item = DirectoryItem(context.get_ui().bold(context.localize(self.LOCAL_MAP['youtube.live'])),
- context.create_uri(['channel', search_live_id, 'live'], item_params),
- image=context.create_resource_path('media', 'live.png'))
+ live_item = DirectoryItem(ui.bold(localize('live')),
+ create_uri(['channel', search_live_id, 'live'], item_params),
+ image=create_path('media', 'live.png'))
live_item.set_fanart(self.get_fanart(context))
result.append(live_item)
playlists = resource_manager.get_related_playlists(channel_id)
upload_playlist = playlists.get('uploads', '')
if upload_playlist:
- json_data = context.get_function_cache().get(FunctionCache.ONE_MINUTE * 5,
- self.get_client(context).get_playlist_items, upload_playlist,
- page_token=page_token)
- if not v3.handle_error(self, context, json_data):
+ json_data = function_cache.get(FunctionCache.ONE_MINUTE * 5,
+ self.get_client(context).get_playlist_items, upload_playlist,
+ page_token=page_token)
+ if not v3.handle_error(context, json_data):
return False
result.extend(
@@ -658,34 +513,47 @@ def _on_channel(self, context, re_match):
def _on_my_location(self, context, re_match):
self.set_content_type(context, constants.content_type.FILES)
+ create_path = context.create_resource_path
+ create_uri = context.create_uri
+ localize = context.localize
settings = context.get_settings()
result = []
# search
- search_item = SearchItem(context, image=context.create_resource_path('media', 'search.png'),
- fanart=self.get_fanart(context), location=True)
+ search_item = SearchItem(
+ context,
+ image=create_path('media', 'search.png'),
+ fanart=self.get_fanart(context),
+ location=True
+ )
result.append(search_item)
# completed live events
if settings.get_bool('youtube.folder.completed.live.show', True):
- live_events_item = DirectoryItem(context.localize(self.LOCAL_MAP['youtube.completed.live']),
- context.create_uri(['special', 'completed_live'], params={'location': True}),
- image=context.create_resource_path('media', 'live.png'))
+ live_events_item = DirectoryItem(
+ localize('live.completed'),
+ create_uri(['special', 'completed_live'], params={'location': True}),
+ image=create_path('media', 'live.png')
+ )
live_events_item.set_fanart(self.get_fanart(context))
result.append(live_events_item)
# upcoming live events
if settings.get_bool('youtube.folder.upcoming.live.show', True):
- live_events_item = DirectoryItem(context.localize(self.LOCAL_MAP['youtube.upcoming.live']),
- context.create_uri(['special', 'upcoming_live'], params={'location': True}),
- image=context.create_resource_path('media', 'live.png'))
+ live_events_item = DirectoryItem(
+ localize('live.upcoming'),
+ create_uri(['special', 'upcoming_live'], params={'location': True}),
+ image=create_path('media', 'live.png')
+ )
live_events_item.set_fanart(self.get_fanart(context))
result.append(live_events_item)
# live events
- live_events_item = DirectoryItem(context.localize(self.LOCAL_MAP['youtube.live']),
- context.create_uri(['special', 'live'], params={'location': True}),
- image=context.create_resource_path('media', 'live.png'))
+ live_events_item = DirectoryItem(
+ localize('live'),
+ create_uri(['special', 'live'], params={'location': True}),
+ image=create_path('media', 'live.png')
+ )
live_events_item.set_fanart(self.get_fanart(context))
result.append(live_events_item)
@@ -707,7 +575,8 @@ def _on_my_location(self, context, re_match):
# noinspection PyUnusedLocal
@RegisterProviderPath('^/play/$')
def on_play(self, context, re_match):
- listitem_path = context.get_ui().get_info_label('Container.ListItem(0).FileNameAndPath')
+ ui = context.get_ui()
+ listitem_path = ui.get_info_label('Container.ListItem(0).FileNameAndPath')
redirect = False
params = context.get_params()
@@ -724,21 +593,21 @@ def on_play(self, context, re_match):
else:
return False
- if context.get_ui().get_home_window_property('prompt_for_subtitles') != params.get('video_id'):
- context.get_ui().clear_home_window_property('prompt_for_subtitles')
+ if ui.get_home_window_property('prompt_for_subtitles') != params.get('video_id'):
+ ui.clear_home_window_property('prompt_for_subtitles')
- if context.get_ui().get_home_window_property('audio_only') != params.get('video_id'):
- context.get_ui().clear_home_window_property('audio_only')
+ if ui.get_home_window_property('audio_only') != params.get('video_id'):
+ ui.clear_home_window_property('audio_only')
- if context.get_ui().get_home_window_property('ask_for_quality') != params.get('video_id'):
- context.get_ui().clear_home_window_property('ask_for_quality')
+ if ui.get_home_window_property('ask_for_quality') != params.get('video_id'):
+ ui.clear_home_window_property('ask_for_quality')
if 'prompt_for_subtitles' in params:
prompt_subtitles = params['prompt_for_subtitles']
del params['prompt_for_subtitles']
if prompt_subtitles and 'video_id' in params and 'playlist_id' not in params:
# redirect to builtin after setting home window property, so playback url matches playable listitems
- context.get_ui().set_home_window_property('prompt_for_subtitles', params['video_id'])
+ ui.set_home_window_property('prompt_for_subtitles', params['video_id'])
context.log_debug('Redirecting playback with subtitles')
redirect = True
@@ -747,7 +616,7 @@ def on_play(self, context, re_match):
del params['audio_only']
if audio_only and 'video_id' in params and 'playlist_id' not in params:
# redirect to builtin after setting home window property, so playback url matches playable listitems
- context.get_ui().set_home_window_property('audio_only', params['video_id'])
+ ui.set_home_window_property('audio_only', params['video_id'])
context.log_debug('Redirecting audio only playback')
redirect = True
@@ -756,7 +625,7 @@ def on_play(self, context, re_match):
del params['ask_for_quality']
if ask_for_quality and 'video_id' in params and 'playlist_id' not in params:
# redirect to builtin after setting home window property, so playback url matches playable listitems
- context.get_ui().set_home_window_property('ask_for_quality', params['video_id'])
+ ui.set_home_window_property('ask_for_quality', params['video_id'])
context.log_debug('Redirecting audio only playback')
redirect = True
@@ -819,38 +688,40 @@ def _on_yt_specials(self, context, re_match):
# noinspection PyUnusedLocal
@RegisterProviderPath('^/history/clear/$')
def _on_yt_clear_history(self, context, re_match):
- if context.get_ui().on_yes_no_input(context.get_name(), context.localize(self.LOCAL_MAP['youtube.clear_history_confirmation'])):
+ if context.get_ui().on_yes_no_input(context.get_name(), context.localize('clear_history_confirmation')):
json_data = self.get_client(context).clear_watch_history()
if 'error' not in json_data:
- context.get_ui().show_notification(context.localize(self.LOCAL_MAP['youtube.succeeded']))
+ context.get_ui().show_notification(context.localize('succeeded'))
@RegisterProviderPath('^/users/(?P[^/]+)/$')
def _on_users(self, context, re_match):
action = re_match.group('action')
refresh = context.get_param('refresh')
+ localize = context.localize
access_manager = context.get_access_manager()
ui = context.get_ui()
def add_user(_access_manager_users):
- _results = ui.on_keyboard_input(context.localize(self.LOCAL_MAP['youtube.enter.user.name']))
+ _results = ui.on_keyboard_input(localize('user.enter_name'))
if _results[0] is False:
return None
_new_user_name = _results[1]
if not _new_user_name.strip():
- _new_user_name = context.localize(self.LOCAL_MAP['youtube.user.unnamed'])
- _new_users = {}
- for idx, key in enumerate(list(_access_manager_users.keys())):
- _new_users[str(idx)] = _access_manager_users[key]
+ _new_user_name = localize('user.unnamed')
+ _new_users = {
+ str(idx): user
+ for idx, user in enumerate(_access_manager_users.values())
+ }
_new_users[str(len(_new_users))] = access_manager.get_new_user(_new_user_name)
access_manager.set_users(_new_users)
return str(len(_new_users) - 1)
def switch_to_user(_user):
- _user_name = access_manager.get_users()[_user].get('name', context.localize(self.LOCAL_MAP['youtube.user.unnamed']))
+ _user_name = access_manager.get_users()[_user].get('name', localize('user.unnamed'))
access_manager.set_user(_user, switch_to=True)
- ui.show_notification(context.localize(self.LOCAL_MAP['youtube.user.changed']) % _user_name,
- context.localize(self.LOCAL_MAP['youtube.switch.user']))
+ ui.show_notification(localize('user.changed') % _user_name,
+ localize('user.switch'))
self.get_resource_manager(context).clear()
if refresh:
ui.refresh_container()
@@ -858,23 +729,23 @@ def switch_to_user(_user):
if action == 'switch':
access_manager_users = access_manager.get_users()
current_user = access_manager.get_user()
- users = [ui.bold(context.localize(self.LOCAL_MAP['youtube.user.new']))]
+ users = [ui.bold(localize('user.new'))]
user_index_map = []
- for k in list(access_manager_users.keys()):
- if k == current_user:
- if access_manager_users[k].get('access_token') or access_manager_users[k].get('refresh_token'):
+ for user, details in access_manager_users.items():
+ if user == current_user:
+ if details.get('access_token') or details.get('refresh_token'):
users.append(
ui.color('limegreen',
- ' '.join([access_manager_users[k].get('name', context.localize(self.LOCAL_MAP['youtube.user.unnamed'])), '*']))
+ ' '.join([details.get('name', localize('user.unnamed')), '*']))
)
else:
- users.append(' '.join([access_manager_users[k].get('name', context.localize(self.LOCAL_MAP['youtube.user.unnamed'])), '*']))
- elif access_manager_users[k].get('access_token') or access_manager_users[k].get('refresh_token'):
- users.append(ui.color('limegreen', access_manager_users[k].get('name', context.localize(self.LOCAL_MAP['youtube.user.unnamed']))))
+ users.append(' '.join([details.get('name', localize('user.unnamed')), '*']))
+ elif details.get('access_token') or details.get('refresh_token'):
+ users.append(ui.color('limegreen', details.get('name', localize('user.unnamed'))))
else:
- users.append(access_manager_users[k].get('name', context.localize(self.LOCAL_MAP['youtube.user.unnamed'])))
- user_index_map.append(k)
- result = ui.on_select(context.localize(self.LOCAL_MAP['youtube.switch.user']), users)
+ users.append(details.get('name', localize('user.unnamed')))
+ user_index_map.append(user)
+ result = ui.on_select(localize('user.switch'), users)
if result == -1:
return True
if result == 0:
@@ -888,8 +759,8 @@ def switch_to_user(_user):
elif action == 'add':
user = add_user(access_manager.get_users())
if user:
- user_name = access_manager.get_users()[user].get('name', context.localize(self.LOCAL_MAP['youtube.user.unnamed']))
- result = ui.on_yes_no_input(context.localize(self.LOCAL_MAP['youtube.switch.user']), context.localize(self.LOCAL_MAP['youtube.switch.user.now']) % user_name)
+ user_name = access_manager.get_users()[user].get('name', localize('user.unnamed'))
+ result = ui.on_yes_no_input(localize('user.switch'), localize('user.switch.now') % user_name)
if result:
switch_to_user(user)
@@ -898,81 +769,84 @@ def switch_to_user(_user):
users = []
user_index_map = []
current_user = access_manager.get_user()
- current_user_dict = access_manager_users[current_user]
- for k in list(access_manager_users.keys()):
- if k == current_user:
- if access_manager_users[k].get('access_token') or access_manager_users[k].get('refresh_token'):
+ current_user_idx = '0'
+ for user, details in access_manager_users.items():
+ if user == current_user:
+ current_user_idx = str(len(user_index_map))
+ if details.get('access_token') or details.get('refresh_token'):
users.append(
ui.color('limegreen',
- ' '.join([access_manager_users[k].get('name', context.localize(self.LOCAL_MAP['youtube.user.unnamed'])), '*']))
+ ' '.join([details.get('name', localize('user.unnamed')), '*']))
)
else:
- users.append(' '.join([access_manager_users[k].get('name', context.localize(self.LOCAL_MAP['youtube.user.unnamed'])), '*']))
- elif access_manager_users[k].get('access_token') or access_manager_users[k].get('refresh_token'):
- users.append(ui.color('limegreen', access_manager_users[k].get('name', context.localize(self.LOCAL_MAP['youtube.user.unnamed']))))
+ users.append(' '.join([details.get('name', localize('user.unnamed')), '*']))
+ elif details.get('access_token') or details.get('refresh_token'):
+ users.append(ui.color('limegreen', details.get('name', localize('user.unnamed'))))
else:
- users.append(access_manager_users[k].get('name', context.localize(self.LOCAL_MAP['youtube.user.unnamed'])))
- user_index_map.append(k)
- result = ui.on_select(context.localize(self.LOCAL_MAP['youtube.remove.a.user']), users)
+ users.append(details.get('name', localize('user.unnamed')))
+ user_index_map.append(user)
+ result = ui.on_select(localize('user.remove'), users)
if result == -1:
return True
- else:
- user = user_index_map[result]
- user_name = access_manager_users[user].get('name', context.localize(self.LOCAL_MAP['youtube.user.unnamed']))
- result = ui.on_remove_content(user_name)
- if result:
- if user == current_user:
- access_manager.set_user('0', switch_to=True)
- del access_manager_users[user]
- new_users = {}
- for i, u in enumerate(list(access_manager_users.keys())):
- if access_manager_users[u] == current_user_dict:
- access_manager.set_user(str(i), switch_to=True)
- new_users[str(i)] = access_manager_users[u]
-
- if not new_users.get(access_manager.get_user()):
- access_manager.set_user('0', switch_to=True)
-
- access_manager.set_users(new_users)
- ui.show_notification(context.localize(self.LOCAL_MAP['youtube.removed']) % user_name,
- context.localize(self.LOCAL_MAP['youtube.remove']))
+
+ user = user_index_map[result]
+ user_name = access_manager_users[user].get('name', localize('user.unnamed'))
+ result = ui.on_remove_content(user_name)
+ if result:
+ if user == current_user:
+ access_manager.set_user('0', switch_to=True)
+ del access_manager_users[user]
+
+ new_users = {
+ str(idx): user
+ for idx, user in enumerate(access_manager_users.values())
+ }
+
+ if current_user_idx in new_users:
+ access_manager.set_user(current_user_idx, switch_to=True)
+ else:
+ access_manager.set_user('0', switch_to=True)
+
+ access_manager.set_users(new_users)
+ ui.show_notification(localize('removed') % user_name,
+ localize('remove'))
elif action == 'rename':
access_manager_users = access_manager.get_users()
users = []
user_index_map = []
current_user = access_manager.get_user()
- for k in list(access_manager_users.keys()):
- if k == current_user:
- if access_manager_users[k].get('access_token') or access_manager_users[k].get('refresh_token'):
+ for user, details in access_manager_users.items():
+ if user == current_user:
+ if details.get('access_token') or details.get('refresh_token'):
users.append(
ui.color('limegreen',
- ' '.join([access_manager_users[k].get('name', context.localize(self.LOCAL_MAP['youtube.user.unnamed'])), '*']))
+ ' '.join([details.get('name', localize('user.unnamed')), '*']))
)
else:
- users.append(' '.join([access_manager_users[k].get('name', context.localize(self.LOCAL_MAP['youtube.user.unnamed'])), '*']))
- elif access_manager_users[k].get('access_token') or access_manager_users[k].get('refresh_token'):
- users.append(ui.color('limegreen', access_manager_users[k].get('name', context.localize(self.LOCAL_MAP['youtube.user.unnamed']))))
+ users.append(' '.join([details.get('name', localize('user.unnamed')), '*']))
+ elif details.get('access_token') or details.get('refresh_token'):
+ users.append(ui.color('limegreen', details.get('name', localize('user.unnamed'))))
else:
- users.append(access_manager_users[k].get('name', context.localize(self.LOCAL_MAP['youtube.user.unnamed'])))
- user_index_map.append(k)
- result = ui.on_select(context.localize(self.LOCAL_MAP['youtube.rename.a.user']), users)
+ users.append(details.get('name', localize('user.unnamed')))
+ user_index_map.append(user)
+ result = ui.on_select(localize('user.rename'), users)
if result == -1:
return True
- else:
- user = user_index_map[result]
- old_user_name = access_manager_users[user].get('name', context.localize(self.LOCAL_MAP['youtube.user.unnamed']))
- results = ui.on_keyboard_input(context.localize(self.LOCAL_MAP['youtube.enter.user.name']), default=old_user_name)
- if results[0] is False:
- return True
- new_user_name = results[1]
- if not new_user_name.strip() or (old_user_name == new_user_name):
- return True
-
- access_manager_users[user]['name'] = new_user_name
- access_manager.set_users(access_manager_users)
- ui.show_notification(context.localize(self.LOCAL_MAP['youtube.renamed']) % (old_user_name, new_user_name),
- context.localize(self.LOCAL_MAP['youtube.rename']))
+
+ user = user_index_map[result]
+ old_user_name = access_manager_users[user].get('name', localize('user.unnamed'))
+ results = ui.on_keyboard_input(localize('user.enter_name'), default=old_user_name)
+ if results[0] is False:
+ return True
+ new_user_name = results[1]
+ if not new_user_name.strip() or (old_user_name == new_user_name):
+ return True
+
+ access_manager_users[user]['name'] = new_user_name
+ access_manager.set_users(access_manager_users)
+ ui.show_notification(localize('renamed') % (old_user_name, new_user_name),
+ localize('rename'))
return True
@@ -985,8 +859,8 @@ def _on_sign(self, context, re_match):
if (not sign_out_confirmed and mode == 'out'
and context.get_ui().on_yes_no_input(
- context.localize(self.LOCAL_MAP['youtube.sign.out']),
- context.localize(self.LOCAL_MAP['youtube.are.you.sure']))):
+ context.localize('sign.out'),
+ context.localize('are_you_sure'))):
sign_out_confirmed = True
if mode == 'in' or (mode == 'out' and sign_out_confirmed):
@@ -995,7 +869,7 @@ def _on_sign(self, context, re_match):
@RegisterProviderPath('^/search/$')
def endpoint_search(self, context, re_match):
- query = context.get_param('q', '')
+ query = context.get_param('q')
if not query:
return []
@@ -1011,7 +885,7 @@ def _search_channel_or_playlist(self, context, id_string):
elif re.match(r'[OP]L[0-9a-zA-Z_\-]{30,40}', id_string):
json_data = self.get_client(context).get_playlists(id_string)
- if not json_data or not v3.handle_error(self, context, json_data):
+ if not json_data or not v3.handle_error(context, json_data):
return []
result.extend(v3.response_to_items(self, context, json_data))
@@ -1022,17 +896,18 @@ def on_search(self, search_text, context, re_match):
if result: # found a channel or playlist matching search_text
return result
- channel_id = context.get_param('channel_id', '')
- event_type = context.get_param('event_type', '')
- hide_folders = context.get_param('hide_folders', False)
- location = context.get_param('location', False)
- page = context.get_param('page', 1)
- page_token = context.get_param('page_token', '')
- search_type = context.get_param('search_type', 'video')
+ context.set_param('q', search_text)
- safe_search = context.get_settings().safe_search()
+ params = context.get_params()
+ channel_id = params.get('channel_id')
+ event_type = params.get('event_type')
+ hide_folders = params.get('hide_folders')
+ location = params.get('location')
+ page = params.get('page', 1)
+ page_token = params.get('page_token', '')
+ search_type = params.get('search_type', 'video')
- context.set_param('q', search_text)
+ safe_search = context.get_settings().safe_search()
if search_type == 'video':
self.set_content_type(context, constants.content_type.VIDEOS)
@@ -1042,18 +917,19 @@ def on_search(self, search_text, context, re_match):
if page == 1 and search_type == 'video' and not event_type and not hide_folders:
if not channel_id and not location:
channel_params = {}
- channel_params.update(context.get_params())
+ channel_params.update(params)
channel_params['search_type'] = 'channel'
- channel_item = DirectoryItem(context.get_ui().bold(context.localize(self.LOCAL_MAP['youtube.channels'])),
+ channel_item = DirectoryItem(context.get_ui().bold(context.localize('channels')),
context.create_uri([context.get_path()], channel_params),
image=context.create_resource_path('media', 'channels.png'))
channel_item.set_fanart(self.get_fanart(context))
result.append(channel_item)
+
if not location:
playlist_params = {}
- playlist_params.update(context.get_params())
+ playlist_params.update(params)
playlist_params['search_type'] = 'playlist'
- playlist_item = DirectoryItem(context.get_ui().bold(context.localize(self.LOCAL_MAP['youtube.playlists'])),
+ playlist_item = DirectoryItem(context.get_ui().bold(context.localize('playlists')),
context.create_uri([context.get_path()], playlist_params),
image=context.create_resource_path('media', 'playlist.png'))
playlist_item.set_fanart(self.get_fanart(context))
@@ -1062,10 +938,10 @@ def on_search(self, search_text, context, re_match):
if not channel_id:
# live
live_params = {}
- live_params.update(context.get_params())
+ live_params.update(params)
live_params['search_type'] = 'video'
live_params['event_type'] = 'live'
- live_item = DirectoryItem(context.get_ui().bold(context.localize(self.LOCAL_MAP['youtube.live'])),
+ live_item = DirectoryItem(context.get_ui().bold(context.localize('live')),
context.create_uri([context.get_path().replace('input', 'query')], live_params),
image=context.create_resource_path('media', 'live.png'))
live_item.set_fanart(self.get_fanart(context))
@@ -1074,7 +950,7 @@ def on_search(self, search_text, context, re_match):
json_data = context.get_function_cache().get(FunctionCache.ONE_MINUTE * 10, self.get_client(context).search,
q=search_text, search_type=search_type, event_type=event_type,
safe_search=safe_search, page_token=page_token, channel_id=channel_id, location=location)
- if not v3.handle_error(self, context, json_data):
+ if not v3.handle_error(context, json_data):
return False
result.extend(v3.response_to_items(self, context, json_data))
return result
@@ -1082,66 +958,79 @@ def on_search(self, search_text, context, re_match):
@RegisterProviderPath('^/config/(?P[^/]+)/$')
def configure_addon(self, context, re_match):
switch = re_match.group('switch')
+ localize = context.localize
settings = context.get_settings()
+ ui = context.get_ui()
+
if switch == 'youtube':
context.addon().openSettings()
- context.get_ui().refresh_container()
+ ui.refresh_container()
elif switch == 'isa':
if context.use_inputstream_adaptive():
xbmcaddon.Addon(id='inputstream.adaptive').openSettings()
else:
settings.set_bool('kodion.video.quality.isa', False)
elif switch == 'subtitles':
- yt_language = context.get_settings().get_string('youtube.language', 'en-US')
- sub_setting = context.get_settings().subtitle_languages()
+ yt_language = settings.get_string('youtube.language', 'en-US')
+ sub_setting = settings.subtitle_languages()
if yt_language.startswith('en'):
- sub_opts = [context.localize(self.LOCAL_MAP['youtube.none']), context.localize(self.LOCAL_MAP['youtube.prompt']),
- context.localize(self.LOCAL_MAP['youtube.subtitle._with_fallback']) % ('en', 'en-US/en-GB'), yt_language,
- '%s (%s)' % (yt_language, context.localize(self.LOCAL_MAP['youtube.subtitle.no.auto.generated']))]
+ sub_opts = [localize('none'),
+ localize('prompt'),
+ localize('subtitles.with_fallback') % ('en', 'en-US/en-GB'),
+ yt_language,
+ '%s (%s)' % (yt_language, localize('subtitles.no_auto_generated'))]
else:
- sub_opts = [context.localize(self.LOCAL_MAP['youtube.none']), context.localize(self.LOCAL_MAP['youtube.prompt']),
- context.localize(self.LOCAL_MAP['youtube.subtitle._with_fallback']) % (yt_language, 'en'), yt_language,
- '%s (%s)' % (yt_language, context.localize(self.LOCAL_MAP['youtube.subtitle.no.auto.generated']))]
+ sub_opts = [localize('none'),
+ localize('prompt'),
+ localize('subtitles.with_fallback') % (yt_language, 'en'),
+ yt_language,
+ '%s (%s)' % (yt_language, localize('subtitles.no_auto_generated'))]
- sub_opts[sub_setting] = context.get_ui().bold(sub_opts[sub_setting])
+ sub_opts[sub_setting] = ui.bold(sub_opts[sub_setting])
- result = context.get_ui().on_select(context.localize(self.LOCAL_MAP['youtube.subtitle.language']), sub_opts)
+ result = ui.on_select(localize('subtitles.language'), sub_opts)
if result > -1:
- context.get_settings().set_subtitle_languages(result)
+ settings.set_subtitle_languages(result)
- result = context.get_ui().on_yes_no_input(
- context.localize(self.LOCAL_MAP['youtube.subtitles.download']),
- context.localize(self.LOCAL_MAP['youtube.pre.download.subtitles'])
+ result = ui.on_yes_no_input(
+ localize('subtitles.download'),
+ localize('subtitles.download.pre')
)
if result > -1:
- context.get_settings().set_subtitle_download(result == 1)
+ settings.set_subtitle_download(result == 1)
elif switch == 'listen_ip':
local_ranges = ('10.', '172.16.', '192.168.')
- addresses = [iface[4][0] for iface in socket.getaddrinfo(socket.gethostname(), None) if iface[4][0].startswith(local_ranges)] + ['127.0.0.1', '0.0.0.0']
- selected_address = context.get_ui().on_select(context.localize(self.LOCAL_MAP['youtube.select.listen.ip']), addresses)
+ addresses = [iface[4][0]
+ for iface in socket.getaddrinfo(socket.gethostname(), None)
+ if iface[4][0].startswith(local_ranges)]
+ addresses += ['127.0.0.1', '0.0.0.0']
+ selected_address = ui.on_select(localize('select.listen.ip'), addresses)
if selected_address != -1:
- context.get_settings().set_httpd_listen(addresses[selected_address])
+ settings.set_httpd_listen(addresses[selected_address])
return False
# noinspection PyUnusedLocal
@RegisterProviderPath('^/my_subscriptions/filter/$')
def manage_my_subscription_filter(self, context, re_match):
params = context.get_params()
+ settings = context.get_settings()
+ ui = context.get_ui()
+
action = params.get('action')
channel = params.get('channel_name')
if (not channel) or (not action):
return
- filter_enabled = context.get_settings().get_bool('youtube.folder.my_subscriptions_filtered.show', False)
+ filter_enabled = settings.get_bool('youtube.folder.my_subscriptions_filtered.show', False)
if not filter_enabled:
return
channel_name = channel.lower()
channel_name = channel_name.replace(',', '')
- filter_string = context.get_settings().get_string('youtube.filter.my_subscriptions_filtered.list', '')
+ filter_string = settings.get_string('youtube.filter.my_subscriptions_filtered.list', '')
filter_string = filter_string.replace(', ', ',')
filter_list = filter_string.split(',')
filter_list = [x.lower() for x in filter_list]
@@ -1154,39 +1043,42 @@ def manage_my_subscription_filter(self, context, re_match):
modified_string = ','.join(filter_list).lstrip(',')
if filter_string != modified_string:
- context.get_settings().set_string('youtube.filter.my_subscriptions_filtered.list', modified_string)
+ settings.set_string('youtube.filter.my_subscriptions_filtered.list', modified_string)
message = ''
if action == 'add':
- message = context.localize(self.LOCAL_MAP['youtube.added.my_subscriptions.filter'])
+ message = context.localize('my_subscriptions.filter.added')
elif action == 'remove':
- message = context.localize(self.LOCAL_MAP['youtube.removed.my_subscriptions.filter'])
+ message = context.localize('my_subscriptions.filter.removed')
if message:
- context.get_ui().show_notification(message=message)
- context.get_ui().refresh_container()
+ ui.show_notification(message=message)
+ ui.refresh_container()
@RegisterProviderPath('^/maintain/(?P[^/]+)/(?P[^/]+)/$')
def maintenance_actions(self, context, re_match):
maint_type = re_match.group('maint_type')
action = re_match.group('action')
+
+ ui = context.get_ui()
+ localize = context.localize
+
if action == 'clear':
if maint_type == 'function_cache':
- if context.get_ui().on_remove_content(context.localize(self.LOCAL_MAP['youtube.function.cache'])):
+ if ui.on_remove_content(localize('cache.function')):
context.get_function_cache().clear()
- context.get_ui().show_notification(context.localize(self.LOCAL_MAP['youtube.succeeded']))
+ ui.show_notification(localize('succeeded'))
elif maint_type == 'data_cache':
- if context.get_ui().on_remove_content(context.localize(self.LOCAL_MAP['youtube.data.cache'])):
+ if ui.on_remove_content(localize('cache.data')):
context.get_data_cache().clear()
- context.get_ui().show_notification(context.localize(self.LOCAL_MAP['youtube.succeeded']))
+ ui.show_notification(localize('succeeded'))
elif maint_type == 'search_cache':
- if context.get_ui().on_remove_content(context.localize(self.LOCAL_MAP['youtube.search.history'])):
+ if ui.on_remove_content(localize('search.history')):
context.get_search_history().clear()
- context.get_ui().show_notification(context.localize(self.LOCAL_MAP['youtube.succeeded']))
- elif maint_type == 'playback_history':
- if context.get_ui().on_remove_content(context.localize(self.LOCAL_MAP['youtube.playback.history'])):
- context.get_playback_history().clear()
- context.get_ui().show_notification(context.localize(self.LOCAL_MAP['youtube.succeeded']))
+ ui.show_notification(localize('succeeded'))
+ elif maint_type == 'playback_history' and ui.on_remove_content(localize('playback.history')):
+ context.get_playback_history().clear()
+ ui.show_notification(localize('succeeded'))
elif action == 'reset':
- if maint_type == 'access_manager' and context.get_ui().on_yes_no_input(context.get_name(), context.localize(self.LOCAL_MAP['youtube.reset.access.manager.confirm'])):
+ if maint_type == 'access_manager' and ui.on_yes_no_input(context.get_name(), localize('reset.access_manager.confirm')):
try:
context.get_function_cache().clear()
access_manager = context.get_access_manager()
@@ -1200,10 +1092,10 @@ def maintenance_actions(self, context, re_match):
pass
self.reset_client()
access_manager.update_access_token(access_token='', refresh_token='')
- context.get_ui().refresh_container()
- context.get_ui().show_notification(context.localize(self.LOCAL_MAP['youtube.succeeded']))
+ ui.refresh_container()
+ ui.show_notification(localize('succeeded'))
except:
- context.get_ui().show_notification(context.localize(self.LOCAL_MAP['youtube.failed']))
+ ui.show_notification(localize('failed'))
elif action == 'delete':
_maint_files = {'function_cache': 'cache.sqlite',
'search_cache': 'search.sqlite',
@@ -1225,7 +1117,7 @@ def maintenance_actions(self, context, re_match):
_file_w_path = os.path.join(os.path.join(context.get_data_path(), 'playback'), _file)
else:
_file_w_path = os.path.join(context.get_data_path(), _file)
- if context.get_ui().on_delete_content(_file):
+ if ui.on_delete_content(_file):
if maint_type == 'temp_files':
_trans_path = xbmc.translatePath(_file_w_path)
try:
@@ -1241,45 +1133,49 @@ def maintenance_actions(self, context, re_match):
elif _file_w_path:
success = xbmcvfs.delete(_file_w_path)
if success:
- context.get_ui().show_notification(context.localize(self.LOCAL_MAP['youtube.succeeded']))
+ ui.show_notification(localize('succeeded'))
else:
- context.get_ui().show_notification(context.localize(self.LOCAL_MAP['youtube.failed']))
+ ui.show_notification(localize('failed'))
elif action == 'install' and maint_type == 'inputstreamhelper':
if context.get_system_version().get_version()[0] >= 17:
try:
xbmcaddon.Addon('script.module.inputstreamhelper')
- context.get_ui().show_notification(context.localize(self.LOCAL_MAP['youtube.inputstreamhelper.is.installed']))
+ ui.show_notification(localize('inputstreamhelper.is_installed'))
except RuntimeError:
context.execute('InstallAddon(script.module.inputstreamhelper)')
else:
- context.get_ui().show_notification(context.localize(self.LOCAL_MAP['youtube.requires.krypton']))
+ ui.show_notification(localize('requires.krypton'))
# noinspection PyUnusedLocal
@RegisterProviderPath('^/api/update/$')
def api_key_update(self, context, re_match):
- settings = context.get_settings()
+ localize = context.localize
params = context.get_params()
+ settings = context.get_settings()
+ ui = context.get_ui()
+
+ api_key = params.get('api_key')
client_id = params.get('client_id')
client_secret = params.get('client_secret')
- api_key = params.get('api_key')
enable = params.get('enable')
+
updated_list = []
log_list = []
if api_key:
settings.set_string('youtube.api.key', api_key)
- updated_list.append(context.localize(self.LOCAL_MAP['youtube.api.key']))
+ updated_list.append(localize('api.key'))
log_list.append('Key')
if client_id:
settings.set_string('youtube.api.id', client_id)
- updated_list.append(context.localize(self.LOCAL_MAP['youtube.api.id']))
+ updated_list.append(localize('api.id'))
log_list.append('Id')
if client_secret:
settings.set_string('youtube.api.secret', client_secret)
- updated_list.append(context.localize(self.LOCAL_MAP['youtube.api.secret']))
+ updated_list.append(localize('api.secret'))
log_list.append('Secret')
if updated_list:
- context.get_ui().show_notification(context.localize(self.LOCAL_MAP['youtube.updated_']) % ', '.join(updated_list))
+ ui.show_notification(localize('updated_') % ', '.join(updated_list))
context.log_debug('Updated API keys: %s' % ', '.join(log_list))
client_id = settings.get_string('youtube.api.id', '')
@@ -1289,19 +1185,19 @@ def api_key_update(self, context, re_match):
log_list = []
if enable and client_id and client_secret and api_key:
- context.get_ui().show_notification(context.localize(self.LOCAL_MAP['youtube.api.personal.enabled']))
+ ui.show_notification(localize('api.personal.enabled'))
context.log_debug('Personal API keys enabled')
elif enable:
if not api_key:
- missing_list.append(context.localize(self.LOCAL_MAP['youtube.api.key']))
+ missing_list.append(localize('api.key'))
log_list.append('Key')
if not client_id:
- missing_list.append(context.localize(self.LOCAL_MAP['youtube.api.id']))
+ missing_list.append(localize('api.id'))
log_list.append('Id')
if not client_secret:
- missing_list.append(context.localize(self.LOCAL_MAP['youtube.api.secret']))
+ missing_list.append(localize('api.secret'))
log_list.append('Secret')
- context.get_ui().show_notification(context.localize(self.LOCAL_MAP['youtube.api.personal.failed']) % ', '.join(missing_list))
+ ui.show_notification(localize('api.personal.failed') % ', '.join(missing_list))
context.log_debug('Failed to enable personal API keys. Missing: %s' % ', '.join(log_list))
# noinspection PyUnusedLocal
@@ -1312,11 +1208,11 @@ def show_client_ip(self, context, re_match):
if is_httpd_live(port=port):
client_ip = get_client_ip_address(port=port)
if client_ip:
- context.get_ui().on_ok(context.get_name(), context.localize(self.LOCAL_MAP['youtube.client.ip']) % client_ip)
+ context.get_ui().on_ok(context.get_name(), context.localize('client.ip') % client_ip)
else:
- context.get_ui().show_notification(context.localize(self.LOCAL_MAP['youtube.client.ip.failed']))
+ context.get_ui().show_notification(context.localize('client.ip.failed'))
else:
- context.get_ui().show_notification(context.localize(self.LOCAL_MAP['youtube.httpd.not.running']))
+ context.get_ui().show_notification(context.localize('httpd.not.running'))
# noinspection PyUnusedLocal
@RegisterProviderPath('^/playback_history/$')
@@ -1362,11 +1258,16 @@ def on_root(self, context, re_match):
"""
Support old YouTube url calls, but also log a deprecation warnings.
"""
- old_action = context.get_param('action', '')
+ old_action = context.get_param('action')
if old_action:
return yt_old_actions.process_old_action(self, context, re_match)
+ create_path = context.create_resource_path
+ create_uri = context.create_uri
+ localize = context.localize
settings = context.get_settings()
+ ui = context.get_ui()
+
_ = self.get_client(context) # required for self.is_logged_in()
self.set_content_type(context, constants.content_type.FILES)
@@ -1375,9 +1276,9 @@ def on_root(self, context, re_match):
# sign in
if not self.is_logged_in() and settings.get_bool('youtube.folder.sign.in.show', True):
- sign_in_item = DirectoryItem(context.get_ui().bold(context.localize(self.LOCAL_MAP['youtube.sign.in'])),
- context.create_uri(['sign', 'in']),
- image=context.create_resource_path('media', 'sign_in.png'))
+ sign_in_item = DirectoryItem(ui.bold(localize('sign.in')),
+ create_uri(['sign', 'in']),
+ image=create_path('media', 'sign_in.png'))
sign_in_item.set_action(True)
sign_in_item.set_fanart(self.get_fanart(context))
result.append(sign_in_item)
@@ -1385,23 +1286,23 @@ def on_root(self, context, re_match):
if self.is_logged_in() and settings.get_bool('youtube.folder.my_subscriptions.show', True):
# my subscription
- #clear cache
+ # clear cache
cache = context.get_data_cache()
cache.set_item('my-subscriptions-items', '[]')
my_subscriptions_item = DirectoryItem(
- context.get_ui().bold(context.localize(self.LOCAL_MAP['youtube.my_subscriptions'])),
- context.create_uri(['special', 'new_uploaded_videos_tv']),
- context.create_resource_path('media', 'new_uploads.png'))
+ ui.bold(localize('my_subscriptions')),
+ create_uri(['special', 'new_uploaded_videos_tv']),
+ create_path('media', 'new_uploads.png'))
my_subscriptions_item.set_fanart(self.get_fanart(context))
result.append(my_subscriptions_item)
if self.is_logged_in() and settings.get_bool('youtube.folder.my_subscriptions_filtered.show', True):
# my subscriptions filtered
my_subscriptions_filtered_item = DirectoryItem(
- context.localize(self.LOCAL_MAP['youtube.my_subscriptions_filtered']),
- context.create_uri(['special', 'new_uploaded_videos_tv_filtered']),
- context.create_resource_path('media', 'new_uploads.png'))
+ localize('my_subscriptions.filtered'),
+ create_uri(['special', 'new_uploaded_videos_tv_filtered']),
+ create_path('media', 'new_uploads.png'))
my_subscriptions_filtered_item.set_fanart(self.get_fanart(context))
result.append(my_subscriptions_filtered_item)
@@ -1412,44 +1313,46 @@ def on_root(self, context, re_match):
watch_history_playlist_id = access_manager.get_watch_history_id()
if watch_history_playlist_id != 'HL':
recommendations_item = DirectoryItem(
- context.localize(self.LOCAL_MAP['youtube.recommendations']),
- context.create_uri(['special', 'recommendations']),
- context.create_resource_path('media', 'popular.png'))
+ localize('recommendations'),
+ create_uri(['special', 'recommendations']),
+ create_path('media', 'popular.png'))
recommendations_item.set_fanart(self.get_fanart(context))
result.append(recommendations_item)
# what to watch
if settings.get_bool('youtube.folder.popular_right_now.show', True):
what_to_watch_item = DirectoryItem(
- context.localize(self.LOCAL_MAP['youtube.popular_right_now']),
- context.create_uri(['special', 'popular_right_now']),
- context.create_resource_path('media', 'popular.png'))
+ localize('popular_right_now'),
+ create_uri(['special', 'popular_right_now']),
+ create_path('media', 'popular.png'))
what_to_watch_item.set_fanart(self.get_fanart(context))
result.append(what_to_watch_item)
# search
if settings.get_bool('youtube.folder.search.show', True):
- search_item = SearchItem(context, image=context.create_resource_path('media', 'search.png'),
+ search_item = SearchItem(context, image=create_path('media', 'search.png'),
fanart=self.get_fanart(context))
result.append(search_item)
if settings.get_bool('youtube.folder.quick_search.show', True):
- quick_search_item = NewSearchItem(context, alt_name=context.localize(self.LOCAL_MAP['youtube.quick.search']),
+ quick_search_item = NewSearchItem(context,
+ alt_name=localize('search.quick'),
fanart=self.get_fanart(context))
result.append(quick_search_item)
if settings.get_bool('youtube.folder.quick_search_incognito.show', True):
- quick_search_incognito_item = NewSearchItem(context, alt_name=context.localize(self.LOCAL_MAP['youtube.quick.search.incognito']),
- image=context.create_resource_path('media', 'search.png'),
+ quick_search_incognito_item = NewSearchItem(context,
+ alt_name=localize('search.quick.incognito'),
+ image=create_path('media', 'search.png'),
fanart=self.get_fanart(context),
incognito=True)
result.append(quick_search_incognito_item)
# my location
if settings.get_bool('youtube.folder.my_location.show', True) and settings.get_location():
- my_location_item = DirectoryItem(context.localize(self.LOCAL_MAP['youtube.my_location']),
- context.create_uri(['location', 'mine']),
- image=context.create_resource_path('media', 'channel.png'))
+ my_location_item = DirectoryItem(localize('my_location'),
+ create_uri(['location', 'mine']),
+ image=create_path('media', 'channel.png'))
my_location_item.set_fanart(self.get_fanart(context))
result.append(my_location_item)
@@ -1457,22 +1360,21 @@ def on_root(self, context, re_match):
if self.is_logged_in():
# my channel
if settings.get_bool('youtube.folder.my_channel.show', True):
- my_channel_item = DirectoryItem(context.localize(self.LOCAL_MAP['youtube.my_channel']),
- context.create_uri(['channel', 'mine']),
- image=context.create_resource_path('media', 'channel.png'))
+ my_channel_item = DirectoryItem(localize('my_channel'),
+ create_uri(['channel', 'mine']),
+ image=create_path('media', 'channel.png'))
my_channel_item.set_fanart(self.get_fanart(context))
result.append(my_channel_item)
# watch later
watch_later_playlist_id = access_manager.get_watch_later_id()
if settings.get_bool('youtube.folder.watch_later.show', True) and watch_later_playlist_id:
- watch_later_item = DirectoryItem(context.localize(self.LOCAL_MAP['youtube.watch_later']),
- context.create_uri(
- ['channel', 'mine', 'playlist', watch_later_playlist_id]),
- context.create_resource_path('media', 'watch_later.png'))
+ watch_later_item = DirectoryItem(localize('watch_later'),
+ create_uri(['channel', 'mine', 'playlist', watch_later_playlist_id]),
+ create_path('media', 'watch_later.png'))
watch_later_item.set_fanart(self.get_fanart(context))
context_menu = []
- yt_context_menu.append_play_all_from_playlist(context_menu, self, context, watch_later_playlist_id)
+ yt_context_menu.append_play_all_from_playlist(context_menu, context, watch_later_playlist_id)
watch_later_item.set_context_menu(context_menu)
result.append(watch_later_item)
@@ -1481,21 +1383,20 @@ def on_root(self, context, re_match):
resource_manager = self.get_resource_manager(context)
playlists = resource_manager.get_related_playlists(channel_id='mine')
if 'likes' in playlists:
- liked_videos_item = DirectoryItem(context.localize(self.LOCAL_MAP['youtube.video.liked']),
- context.create_uri(
- ['channel', 'mine', 'playlist', playlists['likes']]),
- context.create_resource_path('media', 'likes.png'))
+ liked_videos_item = DirectoryItem(localize('video.liked'),
+ create_uri(['channel', 'mine', 'playlist', playlists['likes']]),
+ create_path('media', 'likes.png'))
liked_videos_item.set_fanart(self.get_fanart(context))
context_menu = []
- yt_context_menu.append_play_all_from_playlist(context_menu, self, context, playlists['likes'])
+ yt_context_menu.append_play_all_from_playlist(context_menu, context, playlists['likes'])
liked_videos_item.set_context_menu(context_menu)
result.append(liked_videos_item)
# disliked videos
if settings.get_bool('youtube.folder.disliked_videos.show', True):
- disliked_videos_item = DirectoryItem(context.localize(self.LOCAL_MAP['youtube.video.disliked']),
- context.create_uri(['special', 'disliked_videos']),
- context.create_resource_path('media', 'dislikes.png'))
+ disliked_videos_item = DirectoryItem(localize('video.disliked'),
+ create_uri(['special', 'disliked_videos']),
+ create_path('media', 'dislikes.png'))
disliked_videos_item.set_fanart(self.get_fanart(context))
result.append(disliked_videos_item)
@@ -1503,95 +1404,94 @@ def on_root(self, context, re_match):
if settings.get_bool('youtube.folder.history.show', False):
watch_history_playlist_id = access_manager.get_watch_history_id()
if watch_history_playlist_id != 'HL':
- watch_history_item = DirectoryItem(context.localize(self.LOCAL_MAP['youtube.history']),
- context.create_uri(
- ['channel', 'mine', 'playlist', watch_history_playlist_id]),
- context.create_resource_path('media', 'history.png'))
+ watch_history_item = DirectoryItem(localize('history'),
+ create_uri(['channel', 'mine', 'playlist', watch_history_playlist_id]),
+ create_path('media', 'history.png'))
watch_history_item.set_fanart(self.get_fanart(context))
context_menu = []
- yt_context_menu.append_play_all_from_playlist(context_menu, self, context, watch_history_playlist_id)
+ yt_context_menu.append_play_all_from_playlist(context_menu, context, watch_history_playlist_id)
watch_history_item.set_context_menu(context_menu)
result.append(watch_history_item)
# (my) playlists
if settings.get_bool('youtube.folder.playlists.show', True):
- playlists_item = DirectoryItem(context.localize(self.LOCAL_MAP['youtube.playlists']),
- context.create_uri(['channel', 'mine', 'playlists']),
- context.create_resource_path('media', 'playlist.png'))
+ playlists_item = DirectoryItem(localize('playlists'),
+ create_uri(['channel', 'mine', 'playlists']),
+ create_path('media', 'playlist.png'))
playlists_item.set_fanart(self.get_fanart(context))
result.append(playlists_item)
# saved playlists
if settings.get_bool('youtube.folder.saved.playlists.show', True):
- playlists_item = DirectoryItem(context.localize(self.LOCAL_MAP['youtube.saved.playlists']),
- context.create_uri(['special', 'saved_playlists']),
- context.create_resource_path('media', 'playlist.png'))
+ playlists_item = DirectoryItem(localize('saved.playlists'),
+ create_uri(['special', 'saved_playlists']),
+ create_path('media', 'playlist.png'))
playlists_item.set_fanart(self.get_fanart(context))
result.append(playlists_item)
# subscriptions
if settings.get_bool('youtube.folder.subscriptions.show', True):
- subscriptions_item = DirectoryItem(context.localize(self.LOCAL_MAP['youtube.subscriptions']),
- context.create_uri(['subscriptions', 'list']),
- image=context.create_resource_path('media', 'channels.png'))
+ subscriptions_item = DirectoryItem(localize('subscriptions'),
+ create_uri(['subscriptions', 'list']),
+ image=create_path('media', 'channels.png'))
subscriptions_item.set_fanart(self.get_fanart(context))
result.append(subscriptions_item)
# browse channels
if settings.get_bool('youtube.folder.browse_channels.show', True):
- browse_channels_item = DirectoryItem(context.localize(self.LOCAL_MAP['youtube.browse_channels']),
- context.create_uri(['special', 'browse_channels']),
- image=context.create_resource_path('media', 'browse_channels.png'))
+ browse_channels_item = DirectoryItem(localize('browse_channels'),
+ create_uri(['special', 'browse_channels']),
+ image=create_path('media', 'browse_channels.png'))
browse_channels_item.set_fanart(self.get_fanart(context))
result.append(browse_channels_item)
# completed live events
if settings.get_bool('youtube.folder.completed.live.show', True):
- live_events_item = DirectoryItem(context.localize(self.LOCAL_MAP['youtube.completed.live']),
- context.create_uri(['special', 'completed_live']),
- image=context.create_resource_path('media', 'live.png'))
+ live_events_item = DirectoryItem(localize('live.completed'),
+ create_uri(['special', 'completed_live']),
+ image=create_path('media', 'live.png'))
live_events_item.set_fanart(self.get_fanart(context))
result.append(live_events_item)
# upcoming live events
if settings.get_bool('youtube.folder.upcoming.live.show', True):
- live_events_item = DirectoryItem(context.localize(self.LOCAL_MAP['youtube.upcoming.live']),
- context.create_uri(['special', 'upcoming_live']),
- image=context.create_resource_path('media', 'live.png'))
+ live_events_item = DirectoryItem(localize('live.upcoming'),
+ create_uri(['special', 'upcoming_live']),
+ image=create_path('media', 'live.png'))
live_events_item.set_fanart(self.get_fanart(context))
result.append(live_events_item)
# live events
if settings.get_bool('youtube.folder.live.show', True):
- live_events_item = DirectoryItem(context.localize(self.LOCAL_MAP['youtube.live']),
- context.create_uri(['special', 'live']),
- image=context.create_resource_path('media', 'live.png'))
+ live_events_item = DirectoryItem(localize('live'),
+ create_uri(['special', 'live']),
+ image=create_path('media', 'live.png'))
live_events_item.set_fanart(self.get_fanart(context))
result.append(live_events_item)
# switch user
if settings.get_bool('youtube.folder.switch.user.show', True):
- switch_user_item = DirectoryItem(context.localize(self.LOCAL_MAP['youtube.switch.user']),
- context.create_uri(['users', 'switch']),
- image=context.create_resource_path('media', 'channel.png'))
+ switch_user_item = DirectoryItem(localize('user.switch'),
+ create_uri(['users', 'switch']),
+ image=create_path('media', 'channel.png'))
switch_user_item.set_action(True)
switch_user_item.set_fanart(self.get_fanart(context))
result.append(switch_user_item)
# sign out
if self.is_logged_in() and settings.get_bool('youtube.folder.sign.out.show', True):
- sign_out_item = DirectoryItem(context.localize(self.LOCAL_MAP['youtube.sign.out']),
- context.create_uri(['sign', 'out']),
- image=context.create_resource_path('media', 'sign_out.png'))
+ sign_out_item = DirectoryItem(localize('sign.out'),
+ create_uri(['sign', 'out']),
+ image=create_path('media', 'sign_out.png'))
sign_out_item.set_action(True)
sign_out_item.set_fanart(self.get_fanart(context))
result.append(sign_out_item)
if settings.get_bool('youtube.folder.settings.show', True):
- settings_menu_item = DirectoryItem(context.localize(self.LOCAL_MAP['youtube.settings']),
- context.create_uri(['config', 'youtube']),
- image=context.create_resource_path('media', 'settings.png'))
+ settings_menu_item = DirectoryItem(localize('settings'),
+ create_uri(['config', 'youtube']),
+ image=create_path('media', 'settings.png'))
settings_menu_item.set_action(True)
settings_menu_item.set_fanart(self.get_fanart(context))
result.append(settings_menu_item)
@@ -1647,16 +1547,16 @@ def handle_exception(self, context, exception_to_handle):
context.log_error('%s: %s' % (title, log_message))
if error == 'deleted_client':
- message = context.localize(self.LOCAL_MAP['youtube.key.requirement.notification'])
+ message = context.localize('key.requirement.notification')
context.get_access_manager().update_access_token(access_token='', refresh_token='')
ok_dialog = True
if error == 'invalid_client':
if message == 'The OAuth client was not found.':
- message = context.localize(self.LOCAL_MAP['youtube.client.id.incorrect'])
+ message = context.localize('client.id.incorrect')
message_timeout = 7000
elif message == 'Unauthorized':
- message = context.localize(self.LOCAL_MAP['youtube.client.secret.incorrect'])
+ message = context.localize('client.secret.incorrect')
message_timeout = 7000
if ok_dialog:
From 4e4e2e6e1b0912f5a9be80b321784b921ecf7ce9 Mon Sep 17 00:00:00 2001
From: MoojMidge <56883549+MoojMidge@users.noreply.github.com>
Date: Sat, 25 Nov 2023 18:30:12 +1100
Subject: [PATCH 040/141] Remove references to xbmc.translatePath
- Also remove unnecessary decode to unicode
- Future backports for Kodi 18 to use six/future
---
.../kodion/context/xbmc/xbmc_context.py | 13 ++-----------
.../youtube_plugin/kodion/json_store/json_store.py | 14 ++------------
.../youtube_plugin/kodion/network/http_server.py | 10 +---------
.../lib/youtube_plugin/kodion/utils/methods.py | 9 +--------
.../lib/youtube_plugin/kodion/utils/monitor.py | 11 +----------
resources/lib/youtube_plugin/youtube/provider.py | 9 +--------
6 files changed, 8 insertions(+), 58 deletions(-)
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 0ab4d9236..1e51d39c2 100644
--- a/resources/lib/youtube_plugin/kodion/context/xbmc/xbmc_context.py
+++ b/resources/lib/youtube_plugin/kodion/context/xbmc/xbmc_context.py
@@ -28,12 +28,6 @@
from ... import utils
-try:
- xbmc.translatePath = xbmcvfs.translatePath
-except AttributeError:
- pass
-
-
class XbmcContext(AbstractContext):
LOCAL_MAP = {
'api.id': 30202,
@@ -286,16 +280,13 @@ def __init__(self, path='/', params=None, plugin_name='', plugin_id='', override
self._plugin_id = plugin_id or self._addon.getAddonInfo('id')
self._plugin_name = plugin_name or self._addon.getAddonInfo('name')
self._version = self._addon.getAddonInfo('version')
- self._native_path = xbmc.translatePath(self._addon.getAddonInfo('path'))
+ self._native_path = xbmcvfs.translatePath(self._addon.getAddonInfo('path'))
self._settings = XbmcPluginSettings(self._addon)
"""
Set the data path for this addon and create the folder
"""
- try:
- self._data_path = xbmc.translatePath(self._addon.getAddonInfo('profile')).decode('utf-8')
- except AttributeError:
- self._data_path = xbmc.translatePath(self._addon.getAddonInfo('profile'))
+ self._data_path = xbmcvfs.translatePath(self._addon.getAddonInfo('profile'))
if not xbmcvfs.exists(self._data_path):
xbmcvfs.mkdir(self._data_path)
diff --git a/resources/lib/youtube_plugin/kodion/json_store/json_store.py b/resources/lib/youtube_plugin/kodion/json_store/json_store.py
index 001397737..e1e0b196f 100644
--- a/resources/lib/youtube_plugin/kodion/json_store/json_store.py
+++ b/resources/lib/youtube_plugin/kodion/json_store/json_store.py
@@ -7,32 +7,22 @@
See LICENSES/GPL-2.0-only for more information.
"""
-import os
import json
+import os
import xbmcaddon
import xbmcvfs
-import xbmc
from ..logger import log_debug, log_error
from ..utils import make_dirs
-try:
- xbmc.translatePath = xbmcvfs.translatePath
-except AttributeError:
- pass
-
-
class JSONStore(object):
def __init__(self, filename):
addon_id = 'plugin.video.youtube'
addon = xbmcaddon.Addon(addon_id)
- try:
- self.base_path = xbmc.translatePath(addon.getAddonInfo('profile')).decode('utf-8')
- except AttributeError:
- self.base_path = xbmc.translatePath(addon.getAddonInfo('profile'))
+ self.base_path = xbmcvfs.translatePath(addon.getAddonInfo('profile'))
if not xbmcvfs.exists(self.base_path) and not make_dirs(self.base_path):
log_error('JSONStore.__init__ |{path}| invalid path'.format(
diff --git a/resources/lib/youtube_plugin/kodion/network/http_server.py b/resources/lib/youtube_plugin/kodion/network/http_server.py
index 591810d84..910fb9a89 100644
--- a/resources/lib/youtube_plugin/kodion/network/http_server.py
+++ b/resources/lib/youtube_plugin/kodion/network/http_server.py
@@ -25,11 +25,6 @@
from ..settings import Settings
-try:
- xbmc.translatePath = xbmcvfs.translatePath
-except AttributeError:
- pass
-
_addon_id = 'plugin.video.youtube'
_settings = Settings(Addon(id=_addon_id))
@@ -42,10 +37,7 @@ def __init__(self, request, client_address, server):
self.whitelist_ips = whitelist_ips.split(',')
self.local_ranges = ('10.', '172.16.', '192.168.', '127.0.0.1', 'localhost', '::1')
self.chunk_size = 1024 * 64
- try:
- self.base_path = xbmc.translatePath('special://temp/%s' % _addon_id).decode('utf-8')
- except AttributeError:
- self.base_path = xbmc.translatePath('special://temp/%s' % _addon_id)
+ self.base_path = xbmcvfs.translatePath('special://temp/%s' % _addon_id)
super(YouTubeProxyRequestHandler, self).__init__(request, client_address, server)
def connection_allowed(self):
diff --git a/resources/lib/youtube_plugin/kodion/utils/methods.py b/resources/lib/youtube_plugin/kodion/utils/methods.py
index e3fc6d72d..92b695431 100644
--- a/resources/lib/youtube_plugin/kodion/utils/methods.py
+++ b/resources/lib/youtube_plugin/kodion/utils/methods.py
@@ -14,7 +14,6 @@
from math import floor, log
from urllib.parse import quote
-import xbmc
import xbmcvfs
@@ -35,12 +34,6 @@
)
-try:
- xbmc.translatePath = xbmcvfs.translatePath
-except AttributeError:
- pass
-
-
def loose_version(v):
filled = []
for point in v.split("."):
@@ -242,7 +235,7 @@ def print_items(items):
def make_dirs(path):
if not path.endswith('/'):
path = ''.join([path, '/'])
- path = xbmc.translatePath(path)
+ path = xbmcvfs.translatePath(path)
if not xbmcvfs.exists(path):
try:
_ = xbmcvfs.mkdirs(path)
diff --git a/resources/lib/youtube_plugin/kodion/utils/monitor.py b/resources/lib/youtube_plugin/kodion/utils/monitor.py
index 363109250..96cb77bea 100644
--- a/resources/lib/youtube_plugin/kodion/utils/monitor.py
+++ b/resources/lib/youtube_plugin/kodion/utils/monitor.py
@@ -21,12 +21,6 @@
from .. import logger
-try:
- xbmc.translatePath = xbmcvfs.translatePath
-except AttributeError:
- pass
-
-
class YouTubeMonitor(xbmc.Monitor):
# noinspection PyUnusedLocal,PyMissingConstructor
@@ -150,10 +144,7 @@ def ping_httpd(self):
return is_httpd_live(port=self.httpd_port())
def remove_temp_dir(self):
- try:
- path = xbmc.translatePath('special://temp/%s' % self.addon_id).decode('utf-8')
- except AttributeError:
- path = xbmc.translatePath('special://temp/%s' % self.addon_id)
+ path = xbmcvfs.translatePath('special://temp/%s' % self._addon_id)
if os.path.isdir(path):
try:
diff --git a/resources/lib/youtube_plugin/youtube/provider.py b/resources/lib/youtube_plugin/youtube/provider.py
index 7cedf1085..6379f30d8 100644
--- a/resources/lib/youtube_plugin/youtube/provider.py
+++ b/resources/lib/youtube_plugin/youtube/provider.py
@@ -41,19 +41,12 @@
from ..kodion.utils import find_video_id, strip_html_from_text, FunctionCache
from ..youtube.helper import yt_subscriptions
-import xbmc
import xbmcaddon
import xbmcvfs
import xbmcgui
import xbmcplugin
-try:
- xbmc.translatePath = xbmcvfs.translatePath
-except AttributeError:
- pass
-
-
class Provider(AbstractProvider):
def __init__(self):
super(Provider, self).__init__()
@@ -1119,7 +1112,7 @@ def maintenance_actions(self, context, re_match):
_file_w_path = os.path.join(context.get_data_path(), _file)
if ui.on_delete_content(_file):
if maint_type == 'temp_files':
- _trans_path = xbmc.translatePath(_file_w_path)
+ _trans_path = xbmcvfs.translatePath(_file_w_path)
try:
xbmcvfs.rmdir(_trans_path, force=True)
except:
From c9851f62703495413de86edaef2577ae72e1a062 Mon Sep 17 00:00:00 2001
From: MoojMidge <56883549+MoojMidge@users.noreply.github.com>
Date: Mon, 27 Nov 2023 23:51:19 +1100
Subject: [PATCH 041/141] Update make_comment_item
- Use ui label formatting methods
- Fix date and dateadded
---
.../youtube_plugin/youtube/helper/utils.py | 103 +++++++++++-------
1 file changed, 65 insertions(+), 38 deletions(-)
diff --git a/resources/lib/youtube_plugin/youtube/helper/utils.py b/resources/lib/youtube_plugin/youtube/helper/utils.py
index 62d5790f5..615ef7cc9 100644
--- a/resources/lib/youtube_plugin/youtube/helper/utils.py
+++ b/resources/lib/youtube_plugin/youtube/helper/utils.py
@@ -47,53 +47,80 @@ def get_thumb_timestamp(minutes=15):
def make_comment_item(context, snippet, uri, total_replies=0):
- author = '[B]{}[/B]'.format(utils.to_str(snippet['authorDisplayName']))
- body = utils.to_str(snippet['textOriginal'])
-
- label_props = None
- plot_props = None
- is_edited = (snippet['publishedAt'] != snippet['updatedAt'])
-
- str_likes = ('%.1fK' % (snippet['likeCount'] / 1000.0)) if snippet['likeCount'] > 1000 else str(snippet['likeCount'])
- str_replies = ('%.1fK' % (total_replies / 1000.0)) if total_replies > 1000 else str(total_replies)
-
- if snippet['likeCount'] and total_replies:
- label_props = '[COLOR lime][B]+%s[/B][/COLOR]|[COLOR cyan][B]%s[/B][/COLOR]' % (str_likes, str_replies)
- plot_props = '[COLOR lime][B]%s %s[/B][/COLOR]|[COLOR cyan][B]%s %s[/B][/COLOR]' % (str_likes,
- context.localize('video.comments.likes'), str_replies,
- context.localize('video.comments.replies'))
- elif snippet['likeCount']:
- label_props = '[COLOR lime][B]+%s[/B][/COLOR]' % str_likes
- plot_props = '[COLOR lime][B]%s %s[/B][/COLOR]' % (str_likes,
- context.localize('video.comments.likes'))
- elif total_replies:
- label_props = '[COLOR cyan][B]%s[/B][/COLOR]' % str_replies
- plot_props = '[COLOR cyan][B]%s %s[/B][/COLOR]' % (str_replies,
- context.localize('video.comments.replies'))
- else:
- pass # The comment has no likes or replies.
+ ui = context.get_ui()
+
+ author = ui.bold(snippet['authorDisplayName'])
+ body = snippet['textOriginal']
+
+ label_props = []
+ plot_props = []
+
+ like_count = snippet['likeCount']
+ if like_count:
+ like_count = utils.friendly_number(like_count)
+ label_likes = ui.color('lime', ui.bold(like_count))
+ plot_likes = ui.color('lime', ui.bold(' '.join((
+ like_count, context.localize('video.comments.likes')
+ ))))
+ label_props.append(label_likes)
+ plot_props.append(plot_likes)
+
+ if total_replies:
+ total_replies = utils.friendly_number(total_replies)
+ label_replies = ui.color('cyan', ui.bold(total_replies))
+ plot_replies = ui.color('cyan', ui.bold(' '.join((
+ total_replies, context.localize('video.comments.replies')
+ ))))
+ label_props.append(label_replies)
+ plot_props.append(plot_replies)
+
+ published_at = snippet['publishedAt']
+ updated_at = snippet['updatedAt']
+ edited = published_at != updated_at
+ if edited:
+ label_props.append('*')
+ plot_props.append(context.localize('video.comments.edited'))
# Format the label of the comment item.
- edited = '[B]*[/B]' if is_edited else ''
if label_props:
- label = '{author} ({props}){edited} {body}'.format(author=author, props=label_props, edited=edited,
- body=body.replace('\n', ' '))
+ label = '{author} ({props}) {body}'.format(
+ author=author,
+ props='|'.join(label_props),
+ body=body.replace('\n', ' ')
+ )
else:
- label = '{author}{edited} {body}'.format(author=author, edited=edited, body=body.replace('\n', ' '))
+ label = '{author} {body}'.format(
+ author=author, body=body.replace('\n', ' ')
+ )
# Format the plot of the comment item.
- edited = ' (%s)' % context.localize('video.comments.edited') if is_edited else ''
if plot_props:
- plot = '{author} ({props}){edited}[CR][CR]{body}'.format(author=author, props=plot_props,
- edited=edited, body=body)
+ plot = '{author} ({props}){body}'.format(
+ author=author,
+ props='|'.join(plot_props),
+ body=ui.new_line(body, cr_before=2)
+ )
else:
- plot = '{author}{edited}[CR][CR]{body}'.format(author=author, edited=edited, body=body)
+ plot = '{author}{body}'.format(
+ author=author, body=ui.new_line(body, cr_before=2)
+ )
comment_item = DirectoryItem(label, uri)
comment_item.set_plot(plot)
- comment_item.set_date_from_datetime(utils.datetime_parser.parse(snippet['publishedAt']))
+
+ datetime = utils.datetime_parser.parse(published_at, as_utc=True)
+ comment_item.set_added_utc(datetime)
+ local_datetime = utils.datetime_parser.utc_to_local(datetime)
+ comment_item.set_dateadded_from_datetime(local_datetime)
+ if edited:
+ datetime = utils.datetime_parser.parse(updated_at, as_utc=True)
+ local_datetime = utils.datetime_parser.utc_to_local(datetime)
+ comment_item.set_date_from_datetime(local_datetime)
+
if not uri:
- comment_item.set_action(True) # Cosmetic, makes the item not a folder.
+ # Cosmetic, makes the item not a folder.
+ comment_item.set_action(True)
+
return comment_item
@@ -456,7 +483,7 @@ def update_video_infos(provider, context, video_id_dict,
# 'play with...' (external player)
if alternate_player:
- yt_context_menu.append_play_with(context_menu, provider, context)
+ yt_context_menu.append_play_with(context_menu, context)
if logged_in:
# add 'Watch Later' only if we are not in my 'Watch Later' list
@@ -466,7 +493,7 @@ def update_video_infos(provider, context, video_id_dict,
# provide 'remove' for videos in my playlists
if video_id in playlist_item_id_dict:
- playlist_match = re.match('^/channel/mine/playlist/(?P[^/]+)/$', context.get_path())
+ playlist_match = re.match('^/channel/mine/playlist/(?P[^/]+)/$', path)
if playlist_match:
playlist_id = playlist_match.group('playlist_id')
# we support all playlist except 'Watch History'
@@ -482,7 +509,7 @@ def update_video_infos(provider, context, video_id_dict,
'video_name': video_item.get_name()}
)))
- is_history = re.match('^/special/watch_history_tv/$', context.get_path())
+ is_history = re.match('^/special/watch_history_tv/$', path)
if is_history:
yt_context_menu.append_clear_watch_history(context_menu, context)
From 6d20795d4b07737678f683af32634f11a2260d7e Mon Sep 17 00:00:00 2001
From: MoojMidge <56883549+MoojMidge@users.noreply.github.com>
Date: Mon, 27 Nov 2023 23:58:46 +1100
Subject: [PATCH 042/141] Update provider._on_users
- Remove duplicated code
- Change to int user indexing rather than integers cast as str
- Fix issues with incorrect sorting of user (dict retaining insertion ordering requires Python 3.7+)
---
.../kodion/context/xbmc/xbmc_context.py | 1 +
.../kodion/json_store/login_tokens.py | 54 ++++--
.../kodion/utils/access_manager.py | 73 ++++++-
.../youtube/client/__config__.py | 2 +-
.../lib/youtube_plugin/youtube/provider.py | 178 +++++++-----------
5 files changed, 172 insertions(+), 136 deletions(-)
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 1e51d39c2..ae87b3d1a 100644
--- a/resources/lib/youtube_plugin/kodion/context/xbmc/xbmc_context.py
+++ b/resources/lib/youtube_plugin/kodion/context/xbmc/xbmc_context.py
@@ -195,6 +195,7 @@ class XbmcContext(AbstractContext):
'updated_': 30597,
'uploads': 30726,
'user.changed': 30659,
+ 'user.default': 30532,
'user.enter_name': 30658,
'user.new': 30656,
'user.remove': 30662,
diff --git a/resources/lib/youtube_plugin/kodion/json_store/login_tokens.py b/resources/lib/youtube_plugin/kodion/json_store/login_tokens.py
index e6eb27745..dedd70a94 100644
--- a/resources/lib/youtube_plugin/kodion/json_store/login_tokens.py
+++ b/resources/lib/youtube_plugin/kodion/json_store/login_tokens.py
@@ -13,22 +13,37 @@
# noinspection PyTypeChecker
class LoginTokenStore(JSONStore):
+ DEFAULT_NEW_USER = {
+ 'access_token': '',
+ 'refresh_token': '',
+ 'token_expires': -1,
+ 'last_key_hash': '',
+ 'name': 'Default',
+ 'watch_later': ' WL',
+ 'watch_history': 'HL'
+ }
+
def __init__(self):
super(LoginTokenStore, self).__init__('access_manager.json')
def set_defaults(self, reset=False):
data = {} if reset else self.get_data()
if 'access_manager' not in data:
- data = {'access_manager': {'users': {'0': {'access_token': '', 'refresh_token': '', 'token_expires': -1,
- 'last_key_hash': '', 'name': 'Default', 'watch_later': ' WL', 'watch_history': 'HL'}}}}
+ data = {
+ 'access_manager': {
+ 'users': {
+ 0: self.DEFAULT_NEW_USER.copy()
+ }
+ }
+ }
if 'users' not in data['access_manager']:
- data['access_manager']['users'] = {'0': {'access_token': '', 'refresh_token': '', 'token_expires': -1,
- 'last_key_hash': '', 'name': 'Default', 'watch_later': ' WL', 'watch_history': 'HL'}}
- if '0' not in data['access_manager']['users']:
- data['access_manager']['users']['0'] = {'access_token': '', 'refresh_token': '', 'token_expires': -1,
- 'last_key_hash': '', 'name': 'Default', 'watch_later': ' WL', 'watch_history': 'HL'}
+ data['access_manager']['users'] = {
+ 0: self.DEFAULT_NEW_USER.copy()
+ }
+ if 0 not in data['access_manager']['users']:
+ data['access_manager']['users'][0] = self.DEFAULT_NEW_USER.copy()
if 'current_user' not in data['access_manager']:
- data['access_manager']['current_user'] = '0'
+ data['access_manager']['current_user'] = 0
if 'last_origin' not in data['access_manager']:
data['access_manager']['last_origin'] = 'plugin.video.youtube'
if 'developers' not in data['access_manager']:
@@ -36,7 +51,7 @@ def set_defaults(self, reset=False):
# clean up
if data['access_manager']['current_user'] == 'default':
- data['access_manager']['current_user'] = '0'
+ data['access_manager']['current_user'] = 0
if 'access_token' in data['access_manager']:
del data['access_manager']['access_token']
if 'refresh_token' in data['access_manager']:
@@ -44,13 +59,13 @@ def set_defaults(self, reset=False):
if 'token_expires' in data['access_manager']:
del data['access_manager']['token_expires']
if 'default' in data['access_manager']:
- if (data['access_manager']['default'].get('access_token') or
- data['access_manager']['default'].get('refresh_token')) and \
- (not data['access_manager']['users']['0'].get('access_token') and
- not data['access_manager']['users']['0'].get('refresh_token')):
+ if ((data['access_manager']['default'].get('access_token')
+ or data['access_manager']['default'].get('refresh_token'))
+ and not data['access_manager']['users'][0].get('access_token')
+ and not data['access_manager']['users'][0].get('refresh_token')):
if 'name' not in data['access_manager']['default']:
data['access_manager']['default']['name'] = 'Default'
- data['access_manager']['users']['0'] = data['access_manager']['default']
+ data['access_manager']['users'][0] = data['access_manager']['default']
del data['access_manager']['default']
# end clean up
@@ -71,3 +86,14 @@ def set_defaults(self, reset=False):
# end uuid check
self.save(data)
+
+ def get_data(self):
+ data = super(LoginTokenStore, self).get_data()
+ # process users, change str keys to int
+ users = data['access_manager']['users']
+ if '0' in users:
+ data['access_manager']['users'] = {
+ int(key): value
+ for key, value in users.items()
+ }
+ return data
diff --git a/resources/lib/youtube_plugin/kodion/utils/access_manager.py b/resources/lib/youtube_plugin/kodion/utils/access_manager.py
index f542aa747..c2f2d4d34 100644
--- a/resources/lib/youtube_plugin/kodion/utils/access_manager.py
+++ b/resources/lib/youtube_plugin/kodion/utils/access_manager.py
@@ -23,7 +23,7 @@ def __init__(self, context):
self._settings = context.get_settings()
self._jstore = LoginTokenStore()
self._json = self._jstore.get_data()
- self._user = self._json['access_manager'].get('current_user', '0')
+ self._user = self._json['access_manager'].get('current_user', 0)
self._last_origin = self._json['access_manager'].get('last_origin', 'plugin.video.youtube')
def get_current_user_id(self):
@@ -34,9 +34,9 @@ def get_current_user_id(self):
self._json = self._jstore.get_data()
return self._json['access_manager']['users'][self.get_user()]['id']
- def get_new_user(self, user_name=''):
+ def get_new_user(self, username=''):
"""
- :param user_name: string, users name
+ :param username: string, users name
:return: a new user dict
"""
uuids = [
@@ -46,9 +46,16 @@ def get_new_user(self, user_name=''):
new_uuid = None
while not new_uuid or new_uuid in uuids:
new_uuid = uuid.uuid4().hex
-
- return {'access_token': '', 'refresh_token': '', 'token_expires': -1, 'last_key_hash': '',
- 'name': user_name, 'id': new_uuid, 'watch_later': ' WL', 'watch_history': 'HL'}
+ return {
+ 'access_token': '',
+ 'refresh_token': '',
+ 'token_expires': -1,
+ 'last_key_hash': '',
+ 'name': username,
+ 'id': new_uuid,
+ 'watch_later': ' WL',
+ 'watch_history': 'HL'
+ }
def get_users(self):
"""
@@ -57,9 +64,36 @@ def get_users(self):
"""
return self._json['access_manager'].get('users', {})
+ def add_user(self, username='', user=None):
+ """
+ Add single new user to users collection
+ :param username: str, chosen name of new user
+ :param user: int, optional index for new user
+ :return: tuple, (index, details) of newly added user
+ """
+ users = self._json['access_manager'].get('users', {})
+ new_user_details = self.get_new_user(username)
+ new_user = max(users) + 1 if users and user is None else user or 0
+ users[new_user] = new_user_details
+ self._json['access_manager']['users'] = users
+ self._jstore.save(self._json)
+ return new_user, new_user_details
+
+ def remove_user(self, user):
+ """
+ Remove user from collection of current users
+ :param user: int, user index
+ :return:
+ """
+ users = self._json['access_manager'].get('users', {})
+ if user in users:
+ del users[user]
+ self._json['access_manager']['users'] = users
+ self._jstore.save(self._json)
+
def set_users(self, users):
"""
- Updates the users
+ Updates all users
:param users: dict, users
:return:
"""
@@ -87,6 +121,31 @@ def get_user(self):
"""
return self._user
+ def get_username(self, user=None):
+ """
+ Returns the username of the current or nominated user
+ :return: username
+ """
+ if user is None:
+ user = self._user
+ users = self._json['access_manager'].get('users', {})
+ if user in users:
+ return users[user].get('name')
+ return ''
+
+ def set_username(self, user, username):
+ """
+ Sets the username of the nominated user
+ :return: True if username was set, false otherwise
+ """
+ users = self._json['access_manager'].get('users', {})
+ if user in users:
+ users[user]['name'] = username
+ self._json['access_manager']['users'] = users
+ self._jstore.save(self._json)
+ return True
+ return False
+
def get_watch_later_id(self):
"""
Returns the current users watch later playlist id
diff --git a/resources/lib/youtube_plugin/youtube/client/__config__.py b/resources/lib/youtube_plugin/youtube/client/__config__.py
index a7ee4818b..ed780b264 100644
--- a/resources/lib/youtube_plugin/youtube/client/__config__.py
+++ b/resources/lib/youtube_plugin/youtube/client/__config__.py
@@ -114,7 +114,7 @@ def get_current_switch(self):
def get_current_user(self):
self._json_am = self._am_jstore.get_data()
- return self._json_am['access_manager'].get('current_user', '0')
+ return self._json_am['access_manager'].get('current_user', 0)
def has_own_api_keys(self):
self._json_api = self._api_jstore.get_data()
diff --git a/resources/lib/youtube_plugin/youtube/provider.py b/resources/lib/youtube_plugin/youtube/provider.py
index 6379f30d8..59acb5d3c 100644
--- a/resources/lib/youtube_plugin/youtube/provider.py
+++ b/resources/lib/youtube_plugin/youtube/provider.py
@@ -695,151 +695,101 @@ def _on_users(self, context, re_match):
access_manager = context.get_access_manager()
ui = context.get_ui()
- def add_user(_access_manager_users):
- _results = ui.on_keyboard_input(localize('user.enter_name'))
- if _results[0] is False:
- return None
- _new_user_name = _results[1]
- if not _new_user_name.strip():
- _new_user_name = localize('user.unnamed')
- _new_users = {
- str(idx): user
- for idx, user in enumerate(_access_manager_users.values())
- }
- _new_users[str(len(_new_users))] = access_manager.get_new_user(_new_user_name)
- access_manager.set_users(_new_users)
- return str(len(_new_users) - 1)
-
- def switch_to_user(_user):
- _user_name = access_manager.get_users()[_user].get('name', localize('user.unnamed'))
- access_manager.set_user(_user, switch_to=True)
- ui.show_notification(localize('user.changed') % _user_name,
- localize('user.switch'))
+ def select_user(reason, new_user=False):
+ current_users = access_manager.get_users()
+ current_user = access_manager.get_user()
+ usernames = []
+ for user, details in sorted(current_users.items()):
+ username = details.get('name') or localize('user.unnamed')
+ if user == current_user:
+ username = '> ' + ui.bold(username)
+ if details.get('access_token') or details.get('refresh_token'):
+ username = ui.color('limegreen', username)
+ usernames.append(username)
+ if new_user:
+ usernames.append(ui.italic(localize('user.new')))
+ return ui.on_select(reason, usernames), sorted(current_users.keys())
+
+ def add_user():
+ results = ui.on_keyboard_input(localize('user.enter_name'))
+ if results[0] is False:
+ return None, None
+ new_username = results[1].strip()
+ if not new_username:
+ new_username = localize('user.unnamed')
+ return access_manager.add_user(new_username)
+
+ def switch_to_user(user):
+ access_manager.set_user(user, switch_to=True)
+ ui.show_notification(
+ localize('user.changed') % access_manager.get_username(user),
+ localize('user.switch')
+ )
self.get_resource_manager(context).clear()
if refresh:
ui.refresh_container()
if action == 'switch':
- access_manager_users = access_manager.get_users()
- current_user = access_manager.get_user()
- users = [ui.bold(localize('user.new'))]
- user_index_map = []
- for user, details in access_manager_users.items():
- if user == current_user:
- if details.get('access_token') or details.get('refresh_token'):
- users.append(
- ui.color('limegreen',
- ' '.join([details.get('name', localize('user.unnamed')), '*']))
- )
- else:
- users.append(' '.join([details.get('name', localize('user.unnamed')), '*']))
- elif details.get('access_token') or details.get('refresh_token'):
- users.append(ui.color('limegreen', details.get('name', localize('user.unnamed'))))
- else:
- users.append(details.get('name', localize('user.unnamed')))
- user_index_map.append(user)
- result = ui.on_select(localize('user.switch'), users)
+ result, user_index_map = select_user(localize('user.switch'),
+ new_user=True)
if result == -1:
return True
- if result == 0:
- user = add_user(access_manager_users)
+ if result == len(user_index_map):
+ user, _ = add_user()
else:
- user = user_index_map[result - 1]
+ user = user_index_map[result]
- if user and (user != access_manager.get_user()):
+ if user is not None and user != access_manager.get_user():
switch_to_user(user)
elif action == 'add':
- user = add_user(access_manager.get_users())
- if user:
- user_name = access_manager.get_users()[user].get('name', localize('user.unnamed'))
- result = ui.on_yes_no_input(localize('user.switch'), localize('user.switch.now') % user_name)
+ user, details = add_user()
+ if user is not None:
+ result = ui.on_yes_no_input(
+ localize('user.switch'),
+ localize('user.switch.now') % details.get('name')
+ )
if result:
switch_to_user(user)
elif action == 'remove':
- access_manager_users = access_manager.get_users()
- users = []
- user_index_map = []
- current_user = access_manager.get_user()
- current_user_idx = '0'
- for user, details in access_manager_users.items():
- if user == current_user:
- current_user_idx = str(len(user_index_map))
- if details.get('access_token') or details.get('refresh_token'):
- users.append(
- ui.color('limegreen',
- ' '.join([details.get('name', localize('user.unnamed')), '*']))
- )
- else:
- users.append(' '.join([details.get('name', localize('user.unnamed')), '*']))
- elif details.get('access_token') or details.get('refresh_token'):
- users.append(ui.color('limegreen', details.get('name', localize('user.unnamed'))))
- else:
- users.append(details.get('name', localize('user.unnamed')))
- user_index_map.append(user)
- result = ui.on_select(localize('user.remove'), users)
+ result, user_index_map = select_user(localize('user.remove'))
if result == -1:
return True
user = user_index_map[result]
- user_name = access_manager_users[user].get('name', localize('user.unnamed'))
- result = ui.on_remove_content(user_name)
- if result:
- if user == current_user:
- access_manager.set_user('0', switch_to=True)
- del access_manager_users[user]
-
- new_users = {
- str(idx): user
- for idx, user in enumerate(access_manager_users.values())
- }
-
- if current_user_idx in new_users:
- access_manager.set_user(current_user_idx, switch_to=True)
- else:
- access_manager.set_user('0', switch_to=True)
-
- access_manager.set_users(new_users)
- ui.show_notification(localize('removed') % user_name,
+ username = access_manager.get_username(user)
+ if ui.on_remove_content(username):
+ access_manager.remove_user(user)
+ if user == 0:
+ access_manager.add_user(username=localize('user.default'),
+ user=0)
+ if user == access_manager.get_user():
+ access_manager.set_user(0, switch_to=True)
+ ui.show_notification(localize('removed') % username,
localize('remove'))
elif action == 'rename':
- access_manager_users = access_manager.get_users()
- users = []
- user_index_map = []
- current_user = access_manager.get_user()
- for user, details in access_manager_users.items():
- if user == current_user:
- if details.get('access_token') or details.get('refresh_token'):
- users.append(
- ui.color('limegreen',
- ' '.join([details.get('name', localize('user.unnamed')), '*']))
- )
- else:
- users.append(' '.join([details.get('name', localize('user.unnamed')), '*']))
- elif details.get('access_token') or details.get('refresh_token'):
- users.append(ui.color('limegreen', details.get('name', localize('user.unnamed'))))
- else:
- users.append(details.get('name', localize('user.unnamed')))
- user_index_map.append(user)
- result = ui.on_select(localize('user.rename'), users)
+ result, user_index_map = select_user(localize('user.rename'))
if result == -1:
return True
user = user_index_map[result]
- old_user_name = access_manager_users[user].get('name', localize('user.unnamed'))
- results = ui.on_keyboard_input(localize('user.enter_name'), default=old_user_name)
+ old_username = access_manager.get_username(user)
+ results = ui.on_keyboard_input(localize('user.enter_name'),
+ default=old_username)
if results[0] is False:
return True
- new_user_name = results[1]
- if not new_user_name.strip() or (old_user_name == new_user_name):
+ new_username = results[1].strip()
+ if not new_username:
+ new_username = localize('user.unnamed')
+ if old_username == new_username:
return True
- access_manager_users[user]['name'] = new_user_name
- access_manager.set_users(access_manager_users)
- ui.show_notification(localize('renamed') % (old_user_name, new_user_name),
- localize('rename'))
+ if access_manager.set_username(user, new_username):
+ ui.show_notification(localize('renamed') % (old_username,
+ new_username),
+ localize('rename'))
return True
From af09b7bec91b1b0034a4bcdecf4b6a8bdec13c8a Mon Sep 17 00:00:00 2001
From: MoojMidge <56883549+MoojMidge@users.noreply.github.com>
Date: Tue, 28 Nov 2023 16:36:56 +1100
Subject: [PATCH 043/141] Fix issues with (non)playable items
- Fix UriItem always using setResolvedUril rather than RunPlugin
- Ensure ListItem is created with path
- Standardise naming of ListItem creation methods
- Shorten name of (get|set|clear)_home_window_property to (get|set|clear)_property
- Use string type return rather than None
- Fix setting isPlayable based on item type
- Fix filtering shorts based on duration of playable items
- Remove filtering shorts on list of non playable items
- Fix routing of /play/ with playlist_id/playlist_ids params
- Fixes #115 and #480
- Possibly fixes #485
---
.../kodion/context/xbmc/xbmc_context.py | 2 +-
.../youtube_plugin/kodion/items/__init__.py | 18 +-
.../youtube_plugin/kodion/items/audio_item.py | 4 +-
.../youtube_plugin/kodion/items/base_item.py | 6 +
.../youtube_plugin/kodion/items/uri_item.py | 4 +-
.../youtube_plugin/kodion/items/video_item.py | 4 +-
.../kodion/player/xbmc/xbmc_playlist.py | 2 +-
.../kodion/plugin/xbmc/xbmc_runner.py | 58 ++--
.../lib/youtube_plugin/kodion/service.py | 4 +-
.../kodion/ui/xbmc/xbmc_context_ui.py | 8 +-
.../kodion/ui/xbmc/xbmc_items.py | 197 ++++++-----
.../lib/youtube_plugin/kodion/utils/player.py | 7 +-
.../youtube/helper/subtitles.py | 7 +-
.../lib/youtube_plugin/youtube/helper/tv.py | 2 -
.../youtube/helper/url_to_item_converter.py | 26 +-
.../youtube_plugin/youtube/helper/utils.py | 20 +-
.../youtube_plugin/youtube/helper/yt_play.py | 320 +++++++++---------
.../lib/youtube_plugin/youtube/provider.py | 93 +++--
18 files changed, 410 insertions(+), 372 deletions(-)
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 ae87b3d1a..66fdf6a94 100644
--- a/resources/lib/youtube_plugin/kodion/context/xbmc/xbmc_context.py
+++ b/resources/lib/youtube_plugin/kodion/context/xbmc/xbmc_context.py
@@ -556,4 +556,4 @@ def inputstream_adaptive_auto_stream_selection():
return False
def abort_requested(self):
- return str(self.get_ui().get_home_window_property('abort_requested')).lower() == 'true'
+ return self.get_ui().get_property('abort_requested').lower() == 'true'
diff --git a/resources/lib/youtube_plugin/kodion/items/__init__.py b/resources/lib/youtube_plugin/kodion/items/__init__.py
index 536a4ce38..be32dbd74 100644
--- a/resources/lib/youtube_plugin/kodion/items/__init__.py
+++ b/resources/lib/youtube_plugin/kodion/items/__init__.py
@@ -24,6 +24,18 @@
from .image_item import ImageItem
-__all__ = ['BaseItem', 'AudioItem', 'DirectoryItem', 'VideoItem', 'ImageItem', 'WatchLaterItem', 'FavoritesItem',
- 'SearchItem', 'NewSearchItem', 'SearchHistoryItem', 'NextPageItem', 'UriItem',
- 'from_json', 'to_json', 'to_jsons']
+__all__ = ('AudioItem',
+ 'BaseItem',
+ 'DirectoryItem',
+ 'FavoritesItem',
+ 'ImageItem',
+ 'NewSearchItem',
+ 'NextPageItem',
+ 'SearchHistoryItem',
+ 'SearchItem',
+ 'UriItem',
+ 'VideoItem',
+ 'WatchLaterItem',
+ 'from_json',
+ 'to_json',
+ 'to_jsons')
diff --git a/resources/lib/youtube_plugin/kodion/items/audio_item.py b/resources/lib/youtube_plugin/kodion/items/audio_item.py
index bd0c674e5..a7170fc5a 100644
--- a/resources/lib/youtube_plugin/kodion/items/audio_item.py
+++ b/resources/lib/youtube_plugin/kodion/items/audio_item.py
@@ -14,9 +14,11 @@
class AudioItem(BaseItem):
+ _playable = True
+
def __init__(self, name, uri, image='', fanart=''):
super(AudioItem, self).__init__(name, uri, image, fanart)
- self._duration = None
+ self._duration = -1
self._track_number = None
self._year = None
self._genre = None
diff --git a/resources/lib/youtube_plugin/kodion/items/base_item.py b/resources/lib/youtube_plugin/kodion/items/base_item.py
index 02c96ff79..abd5adb8e 100644
--- a/resources/lib/youtube_plugin/kodion/items/base_item.py
+++ b/resources/lib/youtube_plugin/kodion/items/base_item.py
@@ -19,6 +19,8 @@ class BaseItem(object):
VERSION = 3
INFO_DATE = 'date' # (string) iso 8601
+ _playable = False
+
def __init__(self, name, uri, image='', fanart=''):
self._version = BaseItem.VERSION
@@ -150,3 +152,7 @@ def next_page(self):
@next_page.setter
def next_page(self, value):
self._next_page = bool(value)
+
+ @property
+ def playable(cls):
+ return cls._playable
diff --git a/resources/lib/youtube_plugin/kodion/items/uri_item.py b/resources/lib/youtube_plugin/kodion/items/uri_item.py
index a3a80b702..2c3e2141d 100644
--- a/resources/lib/youtube_plugin/kodion/items/uri_item.py
+++ b/resources/lib/youtube_plugin/kodion/items/uri_item.py
@@ -12,5 +12,7 @@
class UriItem(BaseItem):
- def __init__(self, uri):
+ def __init__(self, uri, playable=None):
super(UriItem, self).__init__(name='', uri=uri)
+ if playable is not None:
+ self._playable = playable
diff --git a/resources/lib/youtube_plugin/kodion/items/video_item.py b/resources/lib/youtube_plugin/kodion/items/video_item.py
index 5aca523a6..c605d3d4d 100644
--- a/resources/lib/youtube_plugin/kodion/items/video_item.py
+++ b/resources/lib/youtube_plugin/kodion/items/video_item.py
@@ -19,12 +19,14 @@
class VideoItem(BaseItem):
+ _playable = True
+
def __init__(self, name, uri, image='', fanart=''):
super(VideoItem, self).__init__(name, uri, image, fanart)
self._genre = None
self._aired = None
self._scheduled_start_utc = None
- self._duration = None
+ self._duration = -1
self._director = None
self._premiered = None
self._episode = None
diff --git a/resources/lib/youtube_plugin/kodion/player/xbmc/xbmc_playlist.py b/resources/lib/youtube_plugin/kodion/player/xbmc/xbmc_playlist.py
index 7eb8a5a1e..69c603e04 100644
--- a/resources/lib/youtube_plugin/kodion/player/xbmc/xbmc_playlist.py
+++ b/resources/lib/youtube_plugin/kodion/player/xbmc/xbmc_playlist.py
@@ -30,7 +30,7 @@ def clear(self):
self._playlist.clear()
def add(self, base_item):
- item = xbmc_items.to_video_item(self._context, base_item)
+ item = xbmc_items.video_listitem(self._context, base_item)
if item:
self._playlist.add(base_item.get_uri(), listitem=item)
diff --git a/resources/lib/youtube_plugin/kodion/plugin/xbmc/xbmc_runner.py b/resources/lib/youtube_plugin/kodion/plugin/xbmc/xbmc_runner.py
index 4b84d1cab..37fe89365 100644
--- a/resources/lib/youtube_plugin/kodion/plugin/xbmc/xbmc_runner.py
+++ b/resources/lib/youtube_plugin/kodion/plugin/xbmc/xbmc_runner.py
@@ -14,7 +14,6 @@
from ..abstract_provider_runner import AbstractProviderRunner
from ...exceptions import KodionException
from ...items import AudioItem, DirectoryItem, ImageItem, UriItem, VideoItem
-from ... import AbstractProvider
from ...ui.xbmc import info_labels, xbmc_items
@@ -43,9 +42,9 @@ def run(self, provider, context):
options = {}
options.update(results[1])
- if isinstance(result, bool) and not result:
- xbmcplugin.endOfDirectory(self.handle, succeeded=False)
- return False
+ if isinstance(result, bool):
+ xbmcplugin.endOfDirectory(self.handle, succeeded=result)
+ return result
if isinstance(result, (VideoItem, AudioItem, UriItem)):
return self._set_resolved_url(context, result)
@@ -75,27 +74,28 @@ def run(self, provider, context):
xbmcplugin.endOfDirectory(
self.handle,
succeeded=succeeded,
- updateListing=options.get(AbstractProvider.RESULT_UPDATE_LISTING, False),
- cacheToDisc=options.get(AbstractProvider.RESULT_CACHE_TO_DISC, True)
+ updateListing=options.get(provider.RESULT_UPDATE_LISTING, False),
+ cacheToDisc=options.get(provider.RESULT_CACHE_TO_DISC, True)
)
return succeeded
- def _set_resolved_url(self, context, base_item, succeeded=True):
- item = xbmc_items.to_playback_item(context, base_item)
- item.setPath(base_item.get_uri())
- xbmcplugin.setResolvedUrl(self.handle, succeeded=succeeded, listitem=item)
- """
- # just to be sure :)
- if not isLiveStream:
- tries = 100
- while tries>0:
- xbmc.sleep(50)
- if xbmc.Player().isPlaying() and xbmc.getCondVisibility("Player.Paused"):
- xbmc.Player().pause()
- break
- tries-=1
- """
- return succeeded
+ def _set_resolved_url(self, context, base_item):
+ if base_item.playable:
+ item = xbmc_items.to_playback_item(context, base_item)
+ xbmcplugin.setResolvedUrl(self.handle,
+ succeeded=True,
+ listitem=item)
+ return True
+
+ uri = base_item.get_uri()
+ if uri.startswith('plugin://'):
+ context.log_debug('Redirecting to |{0}|'.format(uri))
+ context.execute('RunPlugin({0})'.format(uri))
+ xbmcplugin.endOfDirectory(self.handle,
+ succeeded=False,
+ updateListing=False,
+ cacheToDisc=False)
+ return False
@staticmethod
def _add_directory(directory_item, show_fanart=False):
@@ -103,9 +103,8 @@ def _add_directory(directory_item, show_fanart=False):
'thumb': directory_item.get_image()}
item = xbmcgui.ListItem(label=directory_item.get_name(), offscreen=True)
-
- info = info_labels.create_from_item(directory_item)
- xbmc_items.set_info_tag(item, info, 'video')
+ item_info = info_labels.create_from_item(directory_item)
+ xbmc_items.set_info_tag(item, item_info, 'video')
# only set fanart is enabled
if show_fanart:
@@ -121,10 +120,7 @@ def _add_directory(directory_item, show_fanart=False):
item.setPath(directory_item.get_uri())
- is_folder = True
- if directory_item.is_action():
- is_folder = False
- item.setProperty('isPlayable', 'false')
+ is_folder = not directory_item.is_action()
if directory_item.next_page:
item.setProperty('specialSort', 'bottom')
@@ -136,7 +132,7 @@ def _add_directory(directory_item, show_fanart=False):
@staticmethod
def _add_video(context, video_item):
- item = xbmc_items.to_video_item(context, video_item)
+ item = xbmc_items.video_listitem(context, video_item)
item.setPath(video_item.get_uri())
return video_item.get_uri(), item, False
@@ -165,6 +161,6 @@ def _add_image(image_item, show_fanart=False):
@staticmethod
def _add_audio(context, audio_item):
- item = xbmc_items.to_audio_item(context, audio_item)
+ item = xbmc_items.audio_listitem(context, audio_item)
item.setPath(audio_item.get_uri())
return audio_item.get_uri(), item, False
diff --git a/resources/lib/youtube_plugin/kodion/service.py b/resources/lib/youtube_plugin/kodion/service.py
index 5c75b7d3b..29d2a90e3 100644
--- a/resources/lib/youtube_plugin/kodion/service.py
+++ b/resources/lib/youtube_plugin/kodion/service.py
@@ -66,7 +66,7 @@ def run():
# prevent service to failing due to cache related issues
pass
- context.get_ui().clear_home_window_property('abort_requested')
+ context.get_ui().clear_property('abort_requested')
while not monitor.abortRequested():
@@ -84,7 +84,7 @@ def run():
if monitor.waitForAbort(sleep_time):
break
- context.get_ui().set_home_window_property('abort_requested', 'true')
+ context.get_ui().set_property('abort_requested', 'true')
player.cleanup_threads(only_ended=False) # clean up any/all playback monitoring threads
diff --git a/resources/lib/youtube_plugin/kodion/ui/xbmc/xbmc_context_ui.py b/resources/lib/youtube_plugin/kodion/ui/xbmc/xbmc_context_ui.py
index 460683607..fb47f7684 100644
--- a/resources/lib/youtube_plugin/kodion/ui/xbmc/xbmc_context_ui.py
+++ b/resources/lib/youtube_plugin/kodion/ui/xbmc/xbmc_context_ui.py
@@ -153,17 +153,17 @@ def get_info_label(value):
return xbmc.getInfoLabel(value)
@staticmethod
- def set_home_window_property(property_id, value):
+ def set_property(property_id, value):
property_id = ''.join(['plugin.video.youtube-', property_id])
xbmcgui.Window(10000).setProperty(property_id, value)
@staticmethod
- def get_home_window_property(property_id):
+ def get_property(property_id):
property_id = ''.join(['plugin.video.youtube-', property_id])
- return xbmcgui.Window(10000).getProperty(property_id) or None
+ return xbmcgui.Window(10000).getProperty(property_id)
@staticmethod
- def clear_home_window_property(property_id):
+ def clear_property(property_id):
property_id = ''.join(['plugin.video.youtube-', property_id])
xbmcgui.Window(10000).clearProperty(property_id)
diff --git a/resources/lib/youtube_plugin/kodion/ui/xbmc/xbmc_items.py b/resources/lib/youtube_plugin/kodion/ui/xbmc/xbmc_items.py
index e58372b02..95c51a123 100644
--- a/resources/lib/youtube_plugin/kodion/ui/xbmc/xbmc_items.py
+++ b/resources/lib/youtube_plugin/kodion/ui/xbmc/xbmc_items.py
@@ -10,15 +10,20 @@
from xbmcgui import ListItem
+from . import info_labels
+from ...items import VideoItem, AudioItem, UriItem
+from ...utils import datetime_parser
+
try:
from infotagger.listitem import set_info_tag
except ImportError:
- def set_info_tag(listitem, info, tag_type, *_args, **_kwargs):
- listitem.setInfo(tag_type, info)
+ def set_info_tag(listitem, infolabels, tag_type, *_args, **_kwargs):
+ listitem.setInfo(tag_type, infolabels)
return ListItemInfoTag(listitem, tag_type)
+
class ListItemInfoTag(object):
- __slots__ = ('__li__', )
+ __slots__ = ('__li__',)
def __init__(self, listitem, *_args, **_kwargs):
self.__li__ = listitem
@@ -26,51 +31,57 @@ def __init__(self, listitem, *_args, **_kwargs):
def add_stream_info(self, *args, **kwargs):
return self.__li__.addStreamInfo(*args, **kwargs)
- def set_resume_point(self, *_args, **_kwargs):
- pass
+ def set_resume_point(self,
+ infoproperties,
+ *_args,
+ resume_key='ResumeTime',
+ total_key='TotalTime',
+ **_kwargs):
+ if resume_key in infoproperties:
+ infoproperties[resume_key] = str(infoproperties[resume_key])
+ if total_key in infoproperties:
+ infoproperties[total_key] = str(infoproperties[total_key])
-from . import info_labels
-from ...items import VideoItem, AudioItem, UriItem
-from ...utils import datetime_parser
-
-def to_play_item(context, play_item):
- uri = play_item.get_uri()
- context.log_debug('Converting PlayItem |%s|' % uri)
+def video_playback_item(context, video_item):
+ uri = video_item.get_uri()
+ context.log_debug('Converting VideoItem |%s|' % uri)
settings = context.get_settings()
- headers = play_item.get_headers()
- license_key = play_item.get_license_key()
+ headers = video_item.get_headers()
+ license_key = video_item.get_license_key()
alternative_player = settings.is_support_alternative_player_enabled()
is_strm = context.get_param('strm')
mime_type = None
kwargs = {
'label': (None if is_strm
- else (play_item.get_title() or play_item.get_name())),
- 'label2': None if is_strm else play_item.get_short_details(),
+ else (video_item.get_title() or video_item.get_name())),
+ 'label2': None if is_strm else video_item.get_short_details(),
+ 'path': uri,
'offscreen': True,
}
props = {
- 'isPlayable': 'true',
+ 'isPlayable': str(video_item.playable).lower(),
}
if (alternative_player
and settings.alternative_player_web_urls()
and not license_key):
- play_item.set_uri('https://www.youtube.com/watch?v={video_id}'.format(
- video_id=play_item.video_id
+ video_item.set_uri('https://www.youtube.com/watch?v={video_id}'.format(
+ video_id=video_item.video_id
))
-
- elif (play_item.use_isa_video()
+ elif (video_item.use_isa_video()
and context.addon_enabled('inputstream.adaptive')):
- if play_item.use_mpd_video():
+ if video_item.use_mpd_video():
manifest_type = 'mpd'
mime_type = 'application/xml+dash'
+ """
# MPD manifest update is currently broken
- # Following line will force a full update but restart live stream from start
- # if play_item.live:
- # props['inputstream.adaptive.manifest_update_parameter'] = 'full'
+ # Following line will force a full update but restart live stream
+ if video_item.live:
+ props['inputstream.adaptive.manifest_update_parameter'] = 'full'
+ """
if 'auto' in settings.stream_select():
props['inputstream.adaptive.stream_selection_type'] = 'adaptive'
else:
@@ -90,10 +101,11 @@ def to_play_item(context, play_item):
else:
if 'mime=' in uri:
- mime_type = uri.partition('mime=')[2].partition('&')[0].replace('%2F', '/')
+ mime_type = uri.split('mime=', 1)[1].split('&', 1)[0]
+ mime_type = mime_type.replace('%2F', '/')
if not alternative_player and headers and uri.startswith('http'):
- play_item.set_uri('|'.join([uri, headers]))
+ video_item.set_uri('|'.join([uri, headers]))
list_item = ListItem(**kwargs)
if mime_type:
@@ -107,40 +119,86 @@ def to_play_item(context, play_item):
if 'ResumeTime' in props:
del props['ResumeTime']
- prop_value = play_item.get_duration()
+ prop_value = video_item.get_duration()
if prop_value:
- props['TotalTime'] = str(prop_value)
+ props['TotalTime'] = prop_value
- fanart = settings.show_fanart() and play_item.get_fanart() or ''
- thumb = play_item.get_image() or 'DefaultVideo.png'
+ fanart = settings.show_fanart() and video_item.get_fanart() or ''
+ thumb = video_item.get_image() or 'DefaultVideo.png'
list_item.setArt({'icon': thumb, 'thumb': thumb, 'fanart': fanart})
- if play_item.subtitles:
- list_item.setSubtitles(play_item.subtitles)
+ if video_item.subtitles:
+ list_item.setSubtitles(video_item.subtitles)
- info = info_labels.create_from_item(play_item)
- info_tag = set_info_tag(list_item, info, 'video')
+ item_info = info_labels.create_from_item(video_item)
+ info_tag = set_info_tag(list_item, item_info, 'video')
info_tag.set_resume_point(props)
# This should work for all versions of XBMC/KODI.
- if 'duration' in info:
- info_tag.add_stream_info('video', {'duration': info['duration']})
+ if 'duration' in item_info:
+ info_tag.add_stream_info('video', {'duration': item_info['duration']})
list_item.setProperties(props)
return list_item
-def to_video_item(context, video_item):
- context.log_debug('Converting VideoItem |%s|' % video_item.get_uri())
+def audio_listitem(context, audio_item):
+ uri = audio_item.get_uri()
+ context.log_debug('Converting AudioItem |%s|' % uri)
+
+ kwargs = {
+ 'label': audio_item.get_title() or audio_item.get_name(),
+ 'label2': audio_item.get_short_details(),
+ 'path': uri,
+ 'offscreen': True,
+ }
+ props = {
+ 'isPlayable': str(audio_item.playable).lower(),
+ }
+
+ list_item = ListItem(**kwargs)
+
+ fanart = (context.get_settings().show_fanart()
+ and audio_item.get_fanart()
+ or '')
+ thumb = audio_item.get_image() or 'DefaultAudio.png'
+ list_item.setArt({'icon': thumb, 'thumb': thumb, 'fanart': fanart})
+
+ item_info = info_labels.create_from_item(audio_item)
+ set_info_tag(list_item, item_info, 'music')
+
+ list_item.setProperties(props)
+
+ context_menu = audio_item.get_context_menu()
+ if context_menu:
+ list_item.addContextMenuItems(
+ context_menu, replaceItems=audio_item.replace_context_menu()
+ )
+
+ return list_item
+
+
+def uri_listitem(context, base_item):
+ uri = base_item.get_uri()
+ context.log_debug('Converting UriItem |%s|' % uri)
+ item = ListItem(path=uri, offscreen=True)
+ item.setProperty('IsPlayable', str(base_item.playable).lower())
+ return item
+
+
+def video_listitem(context, video_item):
+ uri = video_item.get_uri()
+ context.log_debug('Converting VideoItem |%s|' % uri)
kwargs = {
'label': video_item.get_title() or video_item.get_name(),
'label2': video_item.get_short_details(),
+ 'path': uri,
'offscreen': True,
}
props = {
- 'isPlayable': 'true',
+ 'isPlayable': str(video_item.playable).lower(),
}
list_item = ListItem(**kwargs)
@@ -161,11 +219,11 @@ def to_video_item(context, video_item):
prop_value = video_item.get_start_time()
if prop_value:
- props['ResumeTime'] = str(prop_value)
+ props['ResumeTime'] = prop_value
prop_value = video_item.get_duration()
if prop_value:
- props['TotalTime'] = str(prop_value)
+ props['TotalTime'] = prop_value
# make channel_id property available for keymapping
prop_value = video_item.get_channel_id()
@@ -196,13 +254,13 @@ def to_video_item(context, video_item):
if video_item.subtitles:
list_item.setSubtitles(video_item.subtitles)
- info = info_labels.create_from_item(video_item)
- info_tag = set_info_tag(list_item, info, 'video')
+ item_info = info_labels.create_from_item(video_item)
+ info_tag = set_info_tag(list_item, item_info, 'video')
info_tag.set_resume_point(props)
# This should work for all versions of XBMC/KODI.
- if 'duration' in info:
- info_tag.add_stream_info('video', {'duration': info['duration']})
+ if 'duration' in item_info:
+ info_tag.add_stream_info('video', {'duration': item_info['duration']})
list_item.setProperties(props)
@@ -215,55 +273,14 @@ def to_video_item(context, video_item):
return list_item
-def to_audio_item(context, audio_item):
- context.log_debug('Converting AudioItem |%s|' % audio_item.get_uri())
-
- kwargs = {
- 'label': audio_item.get_title() or audio_item.get_name(),
- 'label2': audio_item.get_short_details(),
- 'offscreen': True,
- }
- props = {
- 'isPlayable': 'true',
- }
-
- list_item = ListItem(**kwargs)
-
- fanart = (context.get_settings().show_fanart()
- and audio_item.get_fanart()
- or '')
- thumb = audio_item.get_image() or 'DefaultAudio.png'
- list_item.setArt({'icon': thumb, 'thumb': thumb, 'fanart': fanart})
-
- info = info_labels.create_from_item(audio_item)
- set_info_tag(list_item, info, 'music')
-
- list_item.setProperties(props)
-
- context_menu = audio_item.get_context_menu()
- if context_menu:
- list_item.addContextMenuItems(
- context_menu, replaceItems=audio_item.replace_context_menu()
- )
-
- return list_item
-
-
-def to_uri_item(context, base_item):
- context.log_debug('Converting UriItem')
- item = ListItem(path=base_item.get_uri(), offscreen=True)
- item.setProperty('IsPlayable', 'true')
- return item
-
-
def to_playback_item(context, base_item):
if isinstance(base_item, UriItem):
- return to_uri_item(context, base_item)
+ return uri_listitem(context, base_item)
if isinstance(base_item, AudioItem):
- return to_audio_item(context, base_item)
+ return audio_listitem(context, base_item)
if isinstance(base_item, VideoItem):
- return to_play_item(context, base_item)
+ return video_playback_item(context, base_item)
return None
diff --git a/resources/lib/youtube_plugin/kodion/utils/player.py b/resources/lib/youtube_plugin/kodion/utils/player.py
index 0374d98da..de21377ea 100644
--- a/resources/lib/youtube_plugin/kodion/utils/player.py
+++ b/resources/lib/youtube_plugin/kodion/utils/player.py
@@ -399,9 +399,10 @@ def cleanup_threads(self, only_ended=True):
self.threads = active_threads
def onPlayBackStarted(self):
- if self.ui.get_home_window_property('playback_json'):
- playback_json = json.loads(self.ui.get_home_window_property('playback_json'))
- self.ui.clear_home_window_property('playback_json')
+ playback_json = self.ui.get_property('playback_json')
+ if playback_json:
+ playback_json = json.loads(playback_json)
+ self.ui.clear_property('playback_json')
self.cleanup_threads()
self.threads.append(PlaybackMonitorThread(self.provider, self.context, playback_json))
diff --git a/resources/lib/youtube_plugin/youtube/helper/subtitles.py b/resources/lib/youtube_plugin/youtube/helper/subtitles.py
index 0e3c767be..af604d8d6 100644
--- a/resources/lib/youtube_plugin/youtube/helper/subtitles.py
+++ b/resources/lib/youtube_plugin/youtube/helper/subtitles.py
@@ -40,10 +40,9 @@ def __init__(self, context, video_id, captions, headers=None):
self.headers = headers
ui = self.context.get_ui()
- self.prompt_override = (
- ui.get_home_window_property('prompt_for_subtitles') == video_id
- )
- ui.clear_home_window_property('prompt_for_subtitles')
+ self.prompt_override = (ui.get_property('prompt_for_subtitles')
+ == video_id)
+ ui.clear_property('prompt_for_subtitles')
self.renderer = captions.get('playerCaptionsTracklistRenderer', {})
self.caption_tracks = self.renderer.get('captionTracks', [])
diff --git a/resources/lib/youtube_plugin/youtube/helper/tv.py b/resources/lib/youtube_plugin/youtube/helper/tv.py
index a78b39dde..c280beb54 100644
--- a/resources/lib/youtube_plugin/youtube/helper/tv.py
+++ b/resources/lib/youtube_plugin/youtube/helper/tv.py
@@ -148,8 +148,6 @@ def saved_playlists_to_items(provider, context, json_data):
utils.update_playlist_infos(provider, context, playlist_id_dict, channel_items_dict)
utils.update_fanarts(provider, context, channel_items_dict)
- result = utils.filter_short_videos(context, result)
-
# next page
next_page_token = json_data.get('next_page_token', '')
if next_page_token or json_data.get('continue', False):
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 192c7c225..20f0d4452 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
@@ -9,11 +9,10 @@
"""
import re
-from urllib.parse import urlparse
-from urllib.parse import parse_qsl
+from urllib.parse import parse_qsl, urlparse
-from ...kodion.items import VideoItem, DirectoryItem
from . import utils
+from ...kodion.items import DirectoryItem, UriItem, VideoItem
class UrlToItemConverter(object):
@@ -128,8 +127,9 @@ def get_items(self, provider, context, title_required=True):
channels_item = DirectoryItem(
context.get_ui().bold(context.localize('channels')),
- context.create_uri(['special', 'description_links'],
- {'channel_ids': ','.join(self._channel_ids)}),
+ context.create_uri(['special', 'description_links'], {
+ 'channel_ids': ','.join(self._channel_ids),
+ }),
context.create_resource_path('media', 'playlist.png')
)
channels_item.set_fanart(provider.get_fanart(context))
@@ -139,11 +139,19 @@ def get_items(self, provider, context, title_required=True):
# remove duplicates
self._playlist_ids = list(set(self._playlist_ids))
- playlists_item = DirectoryItem(
+ playlists_item = UriItem(
+ context.create_uri(['play'], {
+ 'playlist_ids': ','.join(self._playlist_ids),
+ 'play': True,
+ }),
+ playable=True
+ ) if context.get_param('uri') else DirectoryItem(
context.get_ui().bold(context.localize('playlists')),
- context.create_uri(['special', 'description_links'],
- {'playlist_ids': ','.join(self._playlist_ids)}),
- context.create_resource_path('media', 'playlist.png'))
+ context.create_uri(['special', 'description_links'], {
+ 'playlist_ids': ','.join(self._playlist_ids),
+ }),
+ context.create_resource_path('media', 'playlist.png')
+ )
playlists_item.set_fanart(provider.get_fanart(context))
result.append(playlists_item)
diff --git a/resources/lib/youtube_plugin/youtube/helper/utils.py b/resources/lib/youtube_plugin/youtube/helper/utils.py
index 615ef7cc9..90b19f3c5 100644
--- a/resources/lib/youtube_plugin/youtube/helper/utils.py
+++ b/resources/lib/youtube_plugin/youtube/helper/utils.py
@@ -589,8 +589,8 @@ def update_play_info(provider, context, video_id, video_item, video_stream,
ISHelper('mpd', drm='com.widevine.alpha').check_inputstream()
video_item.set_license_key(license_proxy)
- ui.set_home_window_property('license_url', license_url)
- ui.set_home_window_property('license_token', license_token)
+ ui.set_property('license_url', license_url)
+ ui.set_property('license_token', license_token)
def update_fanarts(provider, context, channel_items_dict, data=None):
@@ -694,15 +694,9 @@ def add_related_video_to_playlist(provider, context, client, v3, video_id):
def filter_short_videos(context, items):
if context.get_settings().hide_short_videos():
- shorts_filtered = []
-
- for item in items:
- if hasattr(item, '_duration'):
- item_duration = 0 if item.get_duration() is None else item.get_duration()
- if 0 < item_duration <= 60:
- continue
- shorts_filtered += [item]
-
- return shorts_filtered
-
+ items = [
+ item
+ for item in items
+ if item.playable and not 0 <= item.get_duration() <= 60
+ ]
return items
diff --git a/resources/lib/youtube_plugin/youtube/helper/yt_play.py b/resources/lib/youtube_plugin/youtube/helper/yt_play.py
index 65ada0f74..1df2af008 100644
--- a/resources/lib/youtube_plugin/youtube/helper/yt_play.py
+++ b/resources/lib/youtube_plugin/youtube/helper/yt_play.py
@@ -10,193 +10,193 @@
import json
import random
-import re
import traceback
-import xbmcplugin
-
from ... import kodion
from ...kodion.items import VideoItem
-from ...kodion.ui.xbmc.xbmc_items import to_playback_item
-from ...youtube.youtube_exceptions import YouTubeException
from ...youtube.helper import utils, v3
+from ...youtube.youtube_exceptions import YouTubeException
def play_video(provider, context):
+ params = context.get_params()
+ video_id = params.get('video_id')
+
+ client = provider.get_client(context)
+ settings = context.get_settings()
+ ui = context.get_ui()
+
+ ask_for_quality = None
+ if video_id and ui.get_property('ask_for_quality') == video_id:
+ ask_for_quality = True
+ ui.clear_property('ask_for_quality')
+
+ screensaver = False
+ if params.get('screensaver'):
+ ask_for_quality = False
+ screensaver = True
+
+ audio_only = None
+ if video_id and ui.get_property('audio_only') == video_id:
+ ask_for_quality = False
+ audio_only = True
+ ui.clear_property('audio_only')
+
try:
- video_id = context.get_param('video_id')
+ video_streams = client.get_video_streams(context, video_id)
+ except YouTubeException as e:
+ ui.show_notification(message=e.get_message())
+ context.log_error(traceback.print_exc())
+ return False
- client = provider.get_client(context)
- settings = context.get_settings()
+ if not video_streams:
+ message = context.localize('error.no_video_streams_found')
+ ui.show_notification(message, time_milliseconds=5000)
+ return False
- ask_for_quality = None
- if video_id and context.get_ui().get_home_window_property('ask_for_quality') == video_id:
- ask_for_quality = True
- context.get_ui().clear_home_window_property('ask_for_quality')
+ video_stream = kodion.utils.select_stream(
+ context,
+ video_streams,
+ ask_for_quality=ask_for_quality,
+ audio_only=audio_only
+ )
- screensaver = False
- if context.get_param('screensaver'):
- ask_for_quality = False
- screensaver = True
+ if video_stream is None:
+ return False
- audio_only = None
- if video_id and context.get_ui().get_home_window_property('audio_only') == video_id:
- ask_for_quality = False
- audio_only = True
- context.get_ui().clear_home_window_property('audio_only')
+ is_video = video_stream.get('video')
+ is_live = video_stream.get('Live')
- try:
- video_streams = client.get_video_streams(context, video_id)
- except YouTubeException as e:
- context.get_ui().show_notification(message=e.get_message())
- context.log_error(traceback.print_exc())
- return False
-
- if not video_streams:
- message = context.localize('error.no_video_streams_found')
- context.get_ui().show_notification(message, time_milliseconds=5000)
- return False
-
- video_stream = kodion.utils.select_stream(context, video_streams, ask_for_quality=ask_for_quality, audio_only=audio_only)
-
- if video_stream is None:
- return False
-
- is_video = video_stream.get('video')
- is_live = video_stream.get('Live')
-
- if is_video and video_stream['video'].get('rtmpe', False):
- message = context.localize('error.rtmpe_not_supported')
- context.get_ui().show_notification(message, time_milliseconds=5000)
- return False
-
- play_suggested = settings.get_bool('youtube.suggested_videos', False)
- if play_suggested and not screensaver:
- utils.add_related_video_to_playlist(provider, context, client, v3, video_id)
-
- metadata = video_stream.get('meta', {})
-
- title = metadata.get('video', {}).get('title', '')
- video_item = VideoItem(title, video_stream['url'])
-
- incognito = context.get_param('incognito', False)
- use_history = not is_live and not screensaver and not incognito
- use_remote_history = use_history and settings.use_remote_history()
- use_play_data = use_history and settings.use_local_history()
-
- utils.update_play_info(provider, context, video_id, video_item,
- video_stream, use_play_data=use_play_data)
-
- seek_time = 0.0
- play_count = 0
- playback_stats = video_stream.get('playback_stats')
-
- if not context.get_param('resume'):
- try:
- seek_time = context.get_param('seek', 0.0)
- except (ValueError, TypeError):
- pass
-
- if use_play_data:
- play_count = video_item.get_play_count() or 0
-
- item = to_playback_item(context, video_item)
- item.setPath(video_item.get_uri())
-
- playback_json = {
- "video_id": video_id,
- "channel_id": metadata.get('channel', {}).get('id', ''),
- "video_status": metadata.get('video', {}).get('status', {}),
- "playing_file": video_item.get_uri(),
- "play_count": play_count,
- "use_remote_history": use_remote_history,
- "use_local_history": use_play_data,
- "playback_stats": playback_stats,
- "seek_time": seek_time,
- "refresh_only": screensaver
- }
-
- context.get_ui().set_home_window_property('playback_json', json.dumps(playback_json))
- context.send_notification('PlaybackInit', {
- 'video_id': video_id,
- 'channel_id': playback_json.get('channel_id', ''),
- 'status': playback_json.get('video_status', {})
- })
- xbmcplugin.setResolvedUrl(handle=context.get_handle(), succeeded=True, listitem=item)
-
- except YouTubeException as ex:
- message = ex.get_message()
- message = kodion.utils.strip_html_from_text(message)
- context.get_ui().show_notification(message, time_milliseconds=15000)
+ if is_video and video_stream['video'].get('rtmpe', False):
+ message = context.localize('error.rtmpe_not_supported')
+ ui.show_notification(message, time_milliseconds=5000)
+ return False
+ play_suggested = settings.get_bool('youtube.suggested_videos', False)
+ if play_suggested and not screensaver:
+ utils.add_related_video_to_playlist(provider,
+ context,
+ client,
+ v3,
+ video_id)
-def play_playlist(provider, context):
- videos = []
+ metadata = video_stream.get('meta', {})
- def _load_videos(_page_token='', _progress_dialog=None):
- if _progress_dialog is None:
- _progress_dialog = context.get_ui().create_progress_dialog(
- context.localize('playlist.progress.updating'),
- context.localize('please_wait'), background=True)
- json_data = client.get_playlist_items(playlist_id, page_token=_page_token)
- if not v3.handle_error(context, json_data):
- return None
- _progress_dialog.set_total(int(json_data.get('pageInfo', {}).get('totalResults', 0)))
+ title = metadata.get('video', {}).get('title', '')
+ video_item = VideoItem(title, video_stream['url'])
- result = v3.response_to_items(provider, context, json_data, process_next_page=False)
- videos.extend(result)
- progress_text = '%s %d/%d' % (
- context.localize('please_wait'), len(videos), _progress_dialog.get_total())
- _progress_dialog.update(steps=len(result), text=progress_text)
+ incognito = params.get('incognito', False)
+ use_history = not is_live and not screensaver and not incognito
+ use_remote_history = use_history and settings.use_remote_history()
+ use_play_data = use_history and settings.use_local_history()
- next_page_token = json_data.get('nextPageToken', '')
- if next_page_token:
- _load_videos(_page_token=next_page_token, _progress_dialog=_progress_dialog)
+ utils.update_play_info(provider, context, video_id, video_item,
+ video_stream, use_play_data=use_play_data)
- return _progress_dialog
+ seek_time = 0.0
+ play_count = 0
+ playback_stats = video_stream.get('playback_stats')
- # select order
- video_id = context.get_param('video_id', '')
- order = context.get_param('order', '')
- if not order:
- order_list = ['default', 'reverse']
- # we support shuffle only without a starting video position
- if not video_id:
- order_list.append('shuffle')
- items = []
- for order in order_list:
- items.append((context.localize('playlist.play.%s' % order), order))
-
- order = context.get_ui().on_select(context.localize('playlist.play.select'), items)
- if order not in order_list:
- return False
+ if not params.get('resume'):
+ try:
+ seek_time = params.get('seek', 0.0)
+ except (ValueError, TypeError):
+ pass
+
+ if use_play_data:
+ play_count = video_item.get_play_count() or 0
+
+ playback_json = {
+ "video_id": video_id,
+ "channel_id": metadata.get('channel', {}).get('id', ''),
+ "video_status": metadata.get('video', {}).get('status', {}),
+ "playing_file": video_item.get_uri(),
+ "play_count": play_count,
+ "use_remote_history": use_remote_history,
+ "use_local_history": use_play_data,
+ "playback_stats": playback_stats,
+ "seek_time": seek_time,
+ "refresh_only": screensaver
+ }
+
+ ui.set_property('playback_json', json.dumps(playback_json))
+ context.send_notification('PlaybackInit', {
+ 'video_id': video_id,
+ 'channel_id': playback_json.get('channel_id', ''),
+ 'status': playback_json.get('video_status', {})
+ })
+ return video_item
+
+
+def play_playlist(provider, context):
+ videos = []
+ params = context.get_params()
player = context.get_video_player()
player.stop()
- playlist_id = context.get_param('playlist_id')
+ playlist_ids = params.get('playlist_ids')
+ if not playlist_ids:
+ playlist_ids = [params.get('playlist_id')]
+
client = provider.get_client(context)
+ ui = context.get_ui()
+
+ progress_dialog = ui.create_progress_dialog(
+ context.localize('playlist.progress.updating'),
+ context.localize('please_wait'),
+ background=True
+ )
# start the loop and fill the list with video items
- progress_dialog = _load_videos()
+ total = 0
+ for playlist_id in playlist_ids:
+ page_token = 0
+ while page_token is not None:
+ json_data = client.get_playlist_items(playlist_id, page_token)
+ if not v3.handle_error(context, json_data):
+ return None
+
+ if page_token == 0:
+ total += int(json_data.get('pageInfo', {})
+ .get('totalResults', 0))
+ progress_dialog.set_total(total)
+
+ result = v3.response_to_items(provider,
+ context,
+ json_data,
+ process_next_page=False)
+ videos.extend(result)
+
+ progress_dialog.update(
+ steps=len(result),
+ text='{wait} {current}/{total}'.format(
+ wait=context.localize('please_wait'),
+ current=len(videos),
+ total=total
+ )
+ )
+
+ page_token = json_data.get('nextPageToken') or None
+
+ # select order
+ order = params.get('order', '')
+ if not order:
+ order_list = ['default', 'reverse', 'shuffle']
+ items = [(context.localize('playlist.play.%s' % order), order)
+ for order in order_list]
+ order = ui.on_select(context.localize('playlist.play.select'), items)
+ if order not in order_list:
+ order = 'default'
# reverse the list
if order == 'reverse':
videos = videos[::-1]
elif order == 'shuffle':
- # we have to shuffle the playlist by our self. The implementation of XBMC/KODI is quite weak :(
+ # we have to shuffle the playlist by our self.
+ # The implementation of XBMC/KODI is quite weak :(
random.shuffle(videos)
- playlist_position = 0
- # check if we have a video as starting point for the playlist
- if video_id:
- find_video_id = re.compile(r'video_id=(?P[^&]+)')
- for video in videos:
- video_id_match = find_video_id.search(video.get_uri())
- if video_id_match and video_id_match.group('video_id') == video_id:
- break
- playlist_position += 1
-
# clear the playlist
playlist = context.get_video_playlist()
playlist.clear()
@@ -205,9 +205,14 @@ def _load_videos(_page_token='', _progress_dialog=None):
if order == 'shuffle':
playlist.unshuffle()
+ # check if we have a video as starting point for the playlist
+ video_id = params.get('video_id', '')
# add videos to playlist
- for video in videos:
+ playlist_position = 0
+ for idx, video in enumerate(videos):
playlist.add(video)
+ if video_id and not playlist_position and video_id in video.get_uri():
+ playlist_position = idx
# we use the shuffle implementation of the playlist
"""
@@ -218,13 +223,12 @@ def _load_videos(_page_token='', _progress_dialog=None):
if progress_dialog:
progress_dialog.close()
- if context.get_param('play') and context.get_handle() == -1:
+ if not params.get('play'):
+ return videos
+ if context.get_handle() == -1:
player.play(playlist_index=playlist_position)
return False
- if context.get_param('play'):
- return videos[playlist_position]
-
- return True
+ return videos[playlist_position]
def play_channel_live(provider, context):
diff --git a/resources/lib/youtube_plugin/youtube/provider.py b/resources/lib/youtube_plugin/youtube/provider.py
index 59acb5d3c..a945f673b 100644
--- a/resources/lib/youtube_plugin/youtube/provider.py
+++ b/resources/lib/youtube_plugin/youtube/provider.py
@@ -69,11 +69,11 @@ def is_logged_in(self):
@staticmethod
def get_dev_config(context, addon_id, dev_configs):
- _dev_config = context.get_ui().get_home_window_property('configs')
- context.get_ui().clear_home_window_property('configs')
+ _dev_config = context.get_ui().get_property('configs')
+ context.get_ui().clear_property('configs')
dev_config = {}
- if _dev_config is not None:
+ if _dev_config:
context.log_debug('Using window property for developer keys is deprecated, instead use the youtube_registration module.')
try:
dev_config = json.loads(_dev_config)
@@ -569,15 +569,15 @@ def _on_my_location(self, context, re_match):
@RegisterProviderPath('^/play/$')
def on_play(self, context, re_match):
ui = context.get_ui()
- listitem_path = ui.get_info_label('Container.ListItem(0).FileNameAndPath')
+ path = ui.get_info_label('Container.ListItem(0).FileNameAndPath')
redirect = False
params = context.get_params()
- if 'video_id' not in params and 'playlist_id' not in params and \
- 'channel_id' not in params and 'live' not in params:
- if context.is_plugin_path(listitem_path, 'play'):
- video_id = find_video_id(listitem_path)
+ if ({'channel_id', 'live', 'playlist_id', 'playlist_ids', 'video_id'}
+ .isdisjoint(params.keys())):
+ if context.is_plugin_path(path, 'play'):
+ video_id = find_video_id(path)
if video_id:
context.set_param('video_id', video_id)
params = context.get_params()
@@ -586,61 +586,58 @@ def on_play(self, context, re_match):
else:
return False
- if ui.get_home_window_property('prompt_for_subtitles') != params.get('video_id'):
- ui.clear_home_window_property('prompt_for_subtitles')
+ video_id = params.get('video_id')
+ playlist_id = params.get('playlist_id')
+
+ if ui.get_property('prompt_for_subtitles') != video_id:
+ ui.clear_property('prompt_for_subtitles')
- if ui.get_home_window_property('audio_only') != params.get('video_id'):
- ui.clear_home_window_property('audio_only')
+ if ui.get_property('audio_only') != video_id:
+ ui.clear_property('audio_only')
- if ui.get_home_window_property('ask_for_quality') != params.get('video_id'):
- ui.clear_home_window_property('ask_for_quality')
+ if ui.get_property('ask_for_quality') != video_id:
+ ui.clear_property('ask_for_quality')
- if 'prompt_for_subtitles' in params:
- prompt_subtitles = params['prompt_for_subtitles']
- del params['prompt_for_subtitles']
- if prompt_subtitles and 'video_id' in params and 'playlist_id' not in params:
- # redirect to builtin after setting home window property, so playback url matches playable listitems
- ui.set_home_window_property('prompt_for_subtitles', params['video_id'])
+ if video_id and not playlist_id:
+ if params.pop('prompt_for_subtitles', None):
+ # redirect to builtin after setting home window property,
+ # so playback url matches playable listitems
+ ui.set_property('prompt_for_subtitles', video_id)
context.log_debug('Redirecting playback with subtitles')
redirect = True
- elif 'audio_only' in params:
- audio_only = params['audio_only']
- del params['audio_only']
- if audio_only and 'video_id' in params and 'playlist_id' not in params:
- # redirect to builtin after setting home window property, so playback url matches playable listitems
- ui.set_home_window_property('audio_only', params['video_id'])
+ if params.pop('audio_only', None):
+ # redirect to builtin after setting home window property,
+ # so playback url matches playable listitems
+ ui.set_property('audio_only', video_id)
context.log_debug('Redirecting audio only playback')
redirect = True
- elif 'ask_for_quality' in params:
- ask_for_quality = params['ask_for_quality']
- del params['ask_for_quality']
- if ask_for_quality and 'video_id' in params and 'playlist_id' not in params:
- # redirect to builtin after setting home window property, so playback url matches playable listitems
- ui.set_home_window_property('ask_for_quality', params['video_id'])
- context.log_debug('Redirecting audio only playback')
+ if params.pop('ask_for_quality', None):
+ # redirect to builtin after setting home window property,
+ # so playback url matches playable listitems
+ ui.set_property('ask_for_quality', video_id)
+ context.log_debug('Redirecting ask quality playback')
redirect = True
- if 'playlist_id' not in params and 'video_id' in params and (context.get_handle() == -1 or redirect):
- builtin = 'PlayMedia(%s)' if context.get_handle() == -1 else 'RunPlugin(%s)'
- if not redirect:
+ builtin = None
+ if context.get_handle() == -1:
+ builtin = 'PlayMedia({0})'
context.log_debug('Redirecting playback, handle is -1')
- context.execute(builtin % context.create_uri(['play'], {'video_id': params['video_id']}))
- return False
+ elif redirect:
+ builtin = 'RunPlugin({0})'
- if 'playlist_id' in params and (context.get_handle() != -1):
- builtin = 'RunPlugin(%s)'
- stream_url = context.create_uri(['play'], params)
- xbmcplugin.setResolvedUrl(handle=context.get_handle(), succeeded=False, listitem=xbmcgui.ListItem(path=stream_url))
- context.execute(builtin % context.create_uri(['play'], params))
- return False
-
- if 'video_id' in params and 'playlist_id' not in params:
+ if builtin:
+ context.execute(builtin.format(
+ context.create_uri(['play'], {'video_id': video_id})
+ ))
+ return False
return yt_play.play_video(self, context)
- if 'playlist_id' in params:
+
+ if playlist_id or 'playlist_ids' in params:
return yt_play.play_playlist(self, context)
- if 'channel_id' in params and 'live' in params and params['live'] > 0:
+
+ if 'channel_id' in params and params.get('live', 0) > 0:
return yt_play.play_channel_live(self, context)
return False
From a5eef47ceac8b621e57f29f0da836c2df1e04f3e Mon Sep 17 00:00:00 2001
From: MoojMidge <56883549+MoojMidge@users.noreply.github.com>
Date: Tue, 28 Nov 2023 17:00:08 +1100
Subject: [PATCH 044/141] Misc tidy up
- No (intended) functional changes
---
.../lib/youtube_plugin/kodion/__init__.py | 11 +-
.../youtube_plugin/kodion/context/__init__.py | 2 +-
.../youtube_plugin/kodion/items/base_item.py | 1 -
.../kodion/json_store/__init__.py | 3 +-
.../kodion/json_store/json_store.py | 2 +-
.../youtube_plugin/kodion/player/__init__.py | 2 +-
.../youtube_plugin/kodion/plugin/__init__.py | 2 +-
.../kodion/settings/__init__.py | 2 +-
.../kodion/settings/abstract_settings.py | 5 +-
.../lib/youtube_plugin/kodion/ui/__init__.py | 2 +-
.../youtube_plugin/kodion/utils/__init__.py | 2 +-
.../lib/youtube_plugin/youtube/__init__.py | 3 +-
.../youtube_plugin/youtube/client/__init__.py | 3 +-
.../youtube/helper/ratebypass/__init__.py | 3 +-
.../youtube/helper/resource_manager.py | 20 +--
.../youtube/helper/signature/__init__.py | 3 +-
.../youtube/helper/signature/cipher.py | 2 +-
.../youtube_plugin/youtube/helper/utils.py | 114 +++++++++++++-----
.../youtube/helper/video_info.py | 2 +-
resources/settings.xml | 2 +-
20 files changed, 126 insertions(+), 60 deletions(-)
diff --git a/resources/lib/youtube_plugin/kodion/__init__.py b/resources/lib/youtube_plugin/kodion/__init__.py
index 869364a9b..9df0afa43 100644
--- a/resources/lib/youtube_plugin/kodion/__init__.py
+++ b/resources/lib/youtube_plugin/kodion/__init__.py
@@ -22,6 +22,15 @@
from . import logger
-__all__ = ['KodionException', 'RegisterProviderPath', 'AbstractProvider', 'Context', 'utils', 'json_store', 'logger']
+
+__all__ = (
+ 'AbstractProvider',
+ 'Context',
+ 'KodionException',
+ 'RegisterProviderPath',
+ 'json_store',
+ 'logger',
+ 'utils',
+)
__version__ = '1.5.4'
diff --git a/resources/lib/youtube_plugin/kodion/context/__init__.py b/resources/lib/youtube_plugin/kodion/context/__init__.py
index 9b2b1d889..89d69a6ec 100644
--- a/resources/lib/youtube_plugin/kodion/context/__init__.py
+++ b/resources/lib/youtube_plugin/kodion/context/__init__.py
@@ -10,4 +10,4 @@
from .xbmc.xbmc_context import XbmcContext as Context
-__all__ = ('Context', )
\ No newline at end of file
+__all__ = ('Context',)
diff --git a/resources/lib/youtube_plugin/kodion/items/base_item.py b/resources/lib/youtube_plugin/kodion/items/base_item.py
index abd5adb8e..832d0fa6b 100644
--- a/resources/lib/youtube_plugin/kodion/items/base_item.py
+++ b/resources/lib/youtube_plugin/kodion/items/base_item.py
@@ -8,7 +8,6 @@
See LICENSES/GPL-2.0-only for more information.
"""
-
import hashlib
import datetime
diff --git a/resources/lib/youtube_plugin/kodion/json_store/__init__.py b/resources/lib/youtube_plugin/kodion/json_store/__init__.py
index 278ded74a..1b8044317 100644
--- a/resources/lib/youtube_plugin/kodion/json_store/__init__.py
+++ b/resources/lib/youtube_plugin/kodion/json_store/__init__.py
@@ -11,4 +11,5 @@
from .api_keys import APIKeyStore
from .login_tokens import LoginTokenStore
-__all__ = ['JSONStore', 'APIKeyStore', 'LoginTokenStore']
+
+__all__ = ('APIKeyStore', 'JSONStore', 'LoginTokenStore',)
diff --git a/resources/lib/youtube_plugin/kodion/json_store/json_store.py b/resources/lib/youtube_plugin/kodion/json_store/json_store.py
index e1e0b196f..577f431d1 100644
--- a/resources/lib/youtube_plugin/kodion/json_store/json_store.py
+++ b/resources/lib/youtube_plugin/kodion/json_store/json_store.py
@@ -41,7 +41,7 @@ def set_defaults(self, reset=False):
def save(self, data):
if data == self._data:
log_debug('JSONStore.save |{filename}| data unchanged'.format(
- filename=self.filename
+ filename=self.filename
))
return
log_debug('JSONStore.save |{filename}|'.format(
diff --git a/resources/lib/youtube_plugin/kodion/player/__init__.py b/resources/lib/youtube_plugin/kodion/player/__init__.py
index 705b491c8..e30f5d82f 100644
--- a/resources/lib/youtube_plugin/kodion/player/__init__.py
+++ b/resources/lib/youtube_plugin/kodion/player/__init__.py
@@ -11,4 +11,4 @@
from .xbmc.xbmc_playlist import XbmcPlaylist as Playlist
-__all__ = ('Player', 'Playlist', )
\ No newline at end of file
+__all__ = ('Player', 'Playlist',)
diff --git a/resources/lib/youtube_plugin/kodion/plugin/__init__.py b/resources/lib/youtube_plugin/kodion/plugin/__init__.py
index 37fe21cbe..19ec2ef97 100644
--- a/resources/lib/youtube_plugin/kodion/plugin/__init__.py
+++ b/resources/lib/youtube_plugin/kodion/plugin/__init__.py
@@ -10,4 +10,4 @@
from .xbmc.xbmc_runner import XbmcRunner as Runner
-__all__ = ('Runner', )
+__all__ = ('Runner',)
diff --git a/resources/lib/youtube_plugin/kodion/settings/__init__.py b/resources/lib/youtube_plugin/kodion/settings/__init__.py
index 61a1c1394..06ed1a6ba 100644
--- a/resources/lib/youtube_plugin/kodion/settings/__init__.py
+++ b/resources/lib/youtube_plugin/kodion/settings/__init__.py
@@ -10,4 +10,4 @@
from .xbmc.xbmc_plugin_settings import XbmcPluginSettings as Settings
-__all__ = ('Settings', )
+__all__ = ('Settings',)
diff --git a/resources/lib/youtube_plugin/kodion/settings/abstract_settings.py b/resources/lib/youtube_plugin/kodion/settings/abstract_settings.py
index 5a461224f..f2b6cd36c 100644
--- a/resources/lib/youtube_plugin/kodion/settings/abstract_settings.py
+++ b/resources/lib/youtube_plugin/kodion/settings/abstract_settings.py
@@ -288,9 +288,8 @@ def get_mpd_video_qualities(self):
return []
selected = self.get_int(SETTINGS.MPD_QUALITY_SELECTION, 4)
return [quality
- for key, quality in sorted(
- self._QUALITY_SELECTIONS.items(), reverse=True
- )
+ for key, quality in sorted(self._QUALITY_SELECTIONS.items(),
+ reverse=True)
if selected >= key]
def stream_features(self):
diff --git a/resources/lib/youtube_plugin/kodion/ui/__init__.py b/resources/lib/youtube_plugin/kodion/ui/__init__.py
index 20669bc59..b173ebd14 100644
--- a/resources/lib/youtube_plugin/kodion/ui/__init__.py
+++ b/resources/lib/youtube_plugin/kodion/ui/__init__.py
@@ -10,4 +10,4 @@
from .xbmc.xbmc_context_ui import XbmcContextUI as ContextUI
-__all__ = ('ContextUI', )
+__all__ = ('ContextUI',)
diff --git a/resources/lib/youtube_plugin/kodion/utils/__init__.py b/resources/lib/youtube_plugin/kodion/utils/__init__.py
index fe5c07d99..ff5c0cc44 100644
--- a/resources/lib/youtube_plugin/kodion/utils/__init__.py
+++ b/resources/lib/youtube_plugin/kodion/utils/__init__.py
@@ -56,5 +56,5 @@
'SystemVersion',
'WatchLaterList',
'YouTubeMonitor',
- 'YouTubePlayer'
+ 'YouTubePlayer',
)
diff --git a/resources/lib/youtube_plugin/youtube/__init__.py b/resources/lib/youtube_plugin/youtube/__init__.py
index 76a20e1cf..597a18f32 100644
--- a/resources/lib/youtube_plugin/youtube/__init__.py
+++ b/resources/lib/youtube_plugin/youtube/__init__.py
@@ -10,4 +10,5 @@
from .provider import Provider
-__all__ = ['Provider']
+
+__all__ = ('Provider',)
diff --git a/resources/lib/youtube_plugin/youtube/client/__init__.py b/resources/lib/youtube_plugin/youtube/client/__init__.py
index 804d1f7ab..a97c2a226 100644
--- a/resources/lib/youtube_plugin/youtube/client/__init__.py
+++ b/resources/lib/youtube_plugin/youtube/client/__init__.py
@@ -10,4 +10,5 @@
from .youtube import YouTube
-__all__ = ['YouTube']
+
+__all__ = ('YouTube',)
diff --git a/resources/lib/youtube_plugin/youtube/helper/ratebypass/__init__.py b/resources/lib/youtube_plugin/youtube/helper/ratebypass/__init__.py
index ecbe3188e..8afb79d19 100644
--- a/resources/lib/youtube_plugin/youtube/helper/ratebypass/__init__.py
+++ b/resources/lib/youtube_plugin/youtube/helper/ratebypass/__init__.py
@@ -9,4 +9,5 @@
from ....youtube.helper.ratebypass import ratebypass
-__all__ = ['ratebypass']
+
+__all__ = ('ratebypass',)
diff --git a/resources/lib/youtube_plugin/youtube/helper/resource_manager.py b/resources/lib/youtube_plugin/youtube/helper/resource_manager.py
index 5f74d5824..21896f83a 100644
--- a/resources/lib/youtube_plugin/youtube/helper/resource_manager.py
+++ b/resources/lib/youtube_plugin/youtube/helper/resource_manager.py
@@ -9,13 +9,13 @@
"""
from ..youtube_exceptions import YouTubeException
-from ...kodion.utils import FunctionCache, DataCache, strip_html_from_text
+from ...kodion.utils import strip_html_from_text
class ResourceManager(object):
- def __init__(self, context, youtube_client):
+ def __init__(self, context, client):
self._context = context
- self._youtube_client = youtube_client
+ self._client = client
self._channel_data = {}
self._video_data = {}
self._playlist_data = {}
@@ -41,7 +41,7 @@ def _update_channels(self, channel_ids):
for channel_id in channel_ids:
if channel_id == 'mine':
- json_data = function_cache.get(FunctionCache.ONE_DAY, self._youtube_client.get_channel_by_username, channel_id)
+ json_data = function_cache.get(function_cache.ONE_DAY, self._client.get_channel_by_username, channel_id)
items = json_data.get('items', [{'id': 'mine'}])
try:
@@ -58,7 +58,7 @@ def _update_channels(self, channel_ids):
channel_ids = updated_channel_ids
data_cache = self._context.get_data_cache()
- channel_data = data_cache.get_items(DataCache.ONE_MONTH, channel_ids)
+ channel_data = data_cache.get_items(data_cache.ONE_MONTH, channel_ids)
channel_ids = set(channel_ids)
channel_ids_cached = set(channel_data)
@@ -72,7 +72,7 @@ def _update_channels(self, channel_ids):
if channel_ids_to_update:
self._context.log_debug('No data for channels |%s| cached' % ', '.join(channel_ids_to_update))
json_data = [
- self._youtube_client.get_channels(list_of_50)
+ self._client.get_channels(list_of_50)
for list_of_50 in self._list_batch(channel_ids_to_update, n=50)
]
channel_data = {
@@ -92,7 +92,7 @@ def _update_channels(self, channel_ids):
def _update_videos(self, video_ids, live_details=False, suppress_errors=False):
json_data = None
data_cache = self._context.get_data_cache()
- video_data = data_cache.get_items(DataCache.ONE_MONTH, video_ids)
+ video_data = data_cache.get_items(data_cache.ONE_MONTH, video_ids)
video_ids = set(video_ids)
video_ids_cached = set(video_data)
@@ -105,7 +105,7 @@ def _update_videos(self, video_ids, live_details=False, suppress_errors=False):
if video_ids_to_update:
self._context.log_debug('No data for videos |%s| cached' % ', '.join(video_ids_to_update))
- json_data = self._youtube_client.get_videos(video_ids_to_update, live_details)
+ json_data = self._client.get_videos(video_ids_to_update, live_details)
video_data = {
yt_item['id']: yt_item
for yt_item in json_data.get('items', [])
@@ -143,7 +143,7 @@ def get_videos(self, video_ids, live_details=False, suppress_errors=False):
def _update_playlists(self, playlists_ids):
json_data = None
data_cache = self._context.get_data_cache()
- playlist_data = data_cache.get_items(DataCache.ONE_MONTH, playlists_ids)
+ playlist_data = data_cache.get_items(data_cache.ONE_MONTH, playlists_ids)
playlists_ids = set(playlists_ids)
playlists_ids_cached = set(playlist_data)
@@ -156,7 +156,7 @@ def _update_playlists(self, playlists_ids):
if playlist_ids_to_update:
self._context.log_debug('No data for playlists |%s| cached' % ', '.join(playlist_ids_to_update))
- json_data = self._youtube_client.get_playlists(playlist_ids_to_update)
+ json_data = self._client.get_playlists(playlist_ids_to_update)
playlist_data = {
yt_item['id']: yt_item
for yt_item in json_data.get('items', [])
diff --git a/resources/lib/youtube_plugin/youtube/helper/signature/__init__.py b/resources/lib/youtube_plugin/youtube/helper/signature/__init__.py
index a1da71f71..886aaaa2e 100644
--- a/resources/lib/youtube_plugin/youtube/helper/signature/__init__.py
+++ b/resources/lib/youtube_plugin/youtube/helper/signature/__init__.py
@@ -10,4 +10,5 @@
from ....youtube.helper.signature.cipher import Cipher
-__all__ = ['Cipher']
+
+__all__ = ('Cipher',)
diff --git a/resources/lib/youtube_plugin/youtube/helper/signature/cipher.py b/resources/lib/youtube_plugin/youtube/helper/signature/cipher.py
index ae89edf24..15c7d4028 100644
--- a/resources/lib/youtube_plugin/youtube/helper/signature/cipher.py
+++ b/resources/lib/youtube_plugin/youtube/helper/signature/cipher.py
@@ -121,7 +121,7 @@ def _find_signature_function_name(javascript):
r'yt\.akamaized\.net/\)\s*\|\|\s*.*?\s*[cs]\s*&&\s*[adf]\.set\([^,]+\s*,\s*(?:encodeURIComponent\s*\()?\s*(?P[a-zA-Z0-9$]+)\(',
r'\b[cs]\s*&&\s*[adf]\.set\([^,]+\s*,\s*(?P[a-zA-Z0-9$]+)\(',
r'\b[a-zA-Z0-9]+\s*&&\s*[a-zA-Z0-9]+\.set\([^,]+\s*,\s*(?P[a-zA-Z0-9$]+)\(',
- r'\bc\s*&&\s*[a-zA-Z0-9]+\.set\([^,]+\s*,\s*\([^)]*\)\s*\(\s*(?P[a-zA-Z0-9$]+)\('
+ r'\bc\s*&&\s*[a-zA-Z0-9]+\.set\([^,]+\s*,\s*\([^)]*\)\s*\(\s*(?P[a-zA-Z0-9$]+)\('
)
for pattern in match_patterns:
diff --git a/resources/lib/youtube_plugin/youtube/helper/utils.py b/resources/lib/youtube_plugin/youtube/helper/utils.py
index 90b19f3c5..0680b342e 100644
--- a/resources/lib/youtube_plugin/youtube/helper/utils.py
+++ b/resources/lib/youtube_plugin/youtube/helper/utils.py
@@ -146,13 +146,19 @@ def update_channel_infos(provider, context, channel_id_dict,
logged_in = provider.is_logged_in()
path = context.get_path()
if path == '/subscriptions/list/':
- filter_string = context.get_settings().get_string('youtube.filter.my_subscriptions_filtered.list', '')
+ filter_string = context.get_settings().get_string(
+ 'youtube.filter.my_subscriptions_filtered.list', ''
+ )
filter_string = filter_string.replace(', ', ',')
filter_list = filter_string.split(',')
filter_list = [x.lower() for x in filter_list]
thumb_size = context.get_settings().use_thumbnail_size
- banners = ['bannerTvMediumImageUrl', 'bannerTvLowImageUrl', 'bannerTvImageUrl']
+ banners = [
+ 'bannerTvMediumImageUrl',
+ 'bannerTvLowImageUrl',
+ 'bannerTvImageUrl'
+ ]
for channel_id, yt_item in data.items():
channel_item = channel_id_dict[channel_id]
@@ -173,17 +179,25 @@ def update_channel_infos(provider, context, channel_id_dict,
subscription_id = subscription_id_dict.get(channel_id, '')
if subscription_id:
channel_item.set_channel_subscription_id(subscription_id)
- yt_context_menu.append_unsubscribe_from_channel(context_menu, context, subscription_id)
+ yt_context_menu.append_unsubscribe_from_channel(
+ context_menu, context, subscription_id
+ )
# -- subscribe to the channel
if logged_in and path != '/subscriptions/list/':
- yt_context_menu.append_subscribe_to_channel(context_menu, context, channel_id)
+ yt_context_menu.append_subscribe_to_channel(
+ context_menu, context, channel_id
+ )
if path == '/subscriptions/list/':
channel = title.lower().replace(',', '')
if channel in filter_list:
- yt_context_menu.append_remove_my_subscriptions_filter(context_menu, context, title)
+ yt_context_menu.append_remove_my_subscriptions_filter(
+ context_menu, context, title
+ )
else:
- yt_context_menu.append_add_my_subscriptions_filter(context_menu, context, title)
+ yt_context_menu.append_add_my_subscriptions_filter(
+ context_menu, context, title
+ )
channel_item.set_context_menu(context_menu)
fanart_images = yt_item.get('brandingSettings', {}).get('image', {})
@@ -239,32 +253,47 @@ def update_playlist_infos(provider, context, playlist_id_dict,
channel_name = snippet.get('channelTitle', '')
context_menu = []
# play all videos of the playlist
- yt_context_menu.append_play_all_from_playlist(context_menu, context, playlist_id)
+ yt_context_menu.append_play_all_from_playlist(
+ context_menu, context, playlist_id
+ )
if logged_in:
if channel_id != 'mine':
# subscribe to the channel via the playlist item
- yt_context_menu.append_subscribe_to_channel(context_menu, context, channel_id,
- channel_name)
+ yt_context_menu.append_subscribe_to_channel(
+ context_menu, context, channel_id, channel_name
+ )
else:
# remove my playlist
- yt_context_menu.append_delete_playlist(context_menu, context, playlist_id, title)
+ yt_context_menu.append_delete_playlist(
+ context_menu, context, playlist_id, title
+ )
# rename playlist
- yt_context_menu.append_rename_playlist(context_menu, context, playlist_id, title)
+ yt_context_menu.append_rename_playlist(
+ context_menu, context, playlist_id, title
+ )
# remove as my custom watch later playlist
if playlist_id == custom_watch_later_id:
- yt_context_menu.append_remove_as_watchlater(context_menu, context, playlist_id, title)
+ yt_context_menu.append_remove_as_watchlater(
+ context_menu, context, playlist_id, title
+ )
# set as my custom watch later playlist
else:
- yt_context_menu.append_set_as_watchlater(context_menu, context, playlist_id, title)
+ yt_context_menu.append_set_as_watchlater(
+ context_menu, context, playlist_id, title
+ )
# remove as custom history playlist
if playlist_id == custom_history_id:
- yt_context_menu.append_remove_as_history(context_menu, context, playlist_id, title)
+ yt_context_menu.append_remove_as_history(
+ context_menu, context, playlist_id, title
+ )
# set as custom history playlist
else:
- yt_context_menu.append_set_as_history(context_menu, context, playlist_id, title)
+ yt_context_menu.append_set_as_history(
+ context_menu, context, playlist_id, title
+ )
if context_menu:
playlist_item.set_context_menu(context_menu)
@@ -478,8 +507,12 @@ def update_video_infos(provider, context, video_id_dict,
replace_context_menu = True
playlist_id = some_playlist_match.group('playlist_id')
- yt_context_menu.append_play_all_from_playlist(context_menu, context, playlist_id, video_id)
- yt_context_menu.append_play_all_from_playlist(context_menu, context, playlist_id)
+ yt_context_menu.append_play_all_from_playlist(
+ context_menu, context, playlist_id, video_id
+ )
+ yt_context_menu.append_play_all_from_playlist(
+ context_menu, context, playlist_id
+ )
# 'play with...' (external player)
if alternate_player:
@@ -517,37 +550,58 @@ def update_video_infos(provider, context, video_id_dict,
if (channel_id and channel_name and
utils.create_path('channel', channel_id) != path):
video_item.set_channel_id(channel_id)
- yt_context_menu.append_go_to_channel(context_menu, context, channel_id, channel_name)
+ yt_context_menu.append_go_to_channel(
+ context_menu, context, channel_id, channel_name
+ )
if logged_in:
# subscribe to the channel of the video
video_item.set_subscription_id(channel_id)
- yt_context_menu.append_subscribe_to_channel(context_menu, context, channel_id, channel_name)
+ yt_context_menu.append_subscribe_to_channel(
+ context_menu, context, channel_id, channel_name
+ )
if not video_item.live and play_data:
if not play_data.get('play_count'):
- yt_context_menu.append_mark_watched(context_menu, context, video_id)
+ yt_context_menu.append_mark_watched(
+ context_menu, context, video_id
+ )
else:
- yt_context_menu.append_mark_unwatched(context_menu, context, video_id)
+ yt_context_menu.append_mark_unwatched(
+ context_menu, context, video_id
+ )
- if play_data.get('played_percent', 0) > 0 or play_data.get('played_time', 0) > 0:
- yt_context_menu.append_reset_resume_point(context_menu, context, video_id)
+ if (play_data.get('played_percent', 0) > 0
+ or play_data.get('played_time', 0) > 0):
+ yt_context_menu.append_reset_resume_point(
+ context_menu, context, video_id
+ )
# more...
refresh_container = (path.startswith('/channel/mine/playlist/LL')
or path == '/special/disliked_videos/')
- yt_context_menu.append_more_for_video(context_menu, context, video_id,
- is_logged_in=logged_in,
- refresh_container=refresh_container)
+ yt_context_menu.append_more_for_video(
+ context_menu, context, video_id,
+ is_logged_in=logged_in,
+ refresh_container=refresh_container
+ )
if not video_item.live:
- yt_context_menu.append_play_with_subtitles(context_menu, context, video_id)
- yt_context_menu.append_play_audio_only(context_menu, context, video_id)
+ yt_context_menu.append_play_with_subtitles(
+ context_menu, context, video_id
+ )
+ yt_context_menu.append_play_audio_only(
+ context_menu, context, video_id
+ )
- yt_context_menu.append_play_ask_for_quality(context_menu, context, video_id)
+ yt_context_menu.append_play_ask_for_quality(
+ context_menu, context, video_id
+ )
if context_menu:
- video_item.set_context_menu(context_menu, replace=replace_context_menu)
+ video_item.set_context_menu(
+ context_menu, replace=replace_context_menu
+ )
def update_play_info(provider, context, video_id, video_item, video_stream,
diff --git a/resources/lib/youtube_plugin/youtube/helper/video_info.py b/resources/lib/youtube_plugin/youtube/helper/video_info.py
index 28637160b..193245d9f 100644
--- a/resources/lib/youtube_plugin/youtube/helper/video_info.py
+++ b/resources/lib/youtube_plugin/youtube/helper/video_info.py
@@ -837,7 +837,7 @@ def _load_hls_manifest(self, url, live_type=None, meta_info=None,
# Capture the URL of a .m3u8 playlist and the itag value from that URL.
re_playlist_data = re.compile(
r'#EXT-X-STREAM-INF[^#]+'
- r'(?Phttp[^\s]+/itag/(?P\d+)[^\s]+)'
+ r'(?Phttp\S+/itag/(?P\d+)\S+)'
)
for match in re_playlist_data.finditer(result.text):
playlist_url = match.group('url')
diff --git a/resources/settings.xml b/resources/settings.xml
index e6ee7a6e6..dfa126b51 100644
--- a/resources/settings.xml
+++ b/resources/settings.xml
@@ -317,7 +317,7 @@
2
-
+
From 4a46dcdf25e270a80f71a3bff6181e3cf8020aaa Mon Sep 17 00:00:00 2001
From: MoojMidge <56883549+MoojMidge@users.noreply.github.com>
Date: Tue, 28 Nov 2023 17:01:41 +1100
Subject: [PATCH 045/141] Fix missing utils.to_str in __all__
---
resources/lib/youtube_plugin/kodion/utils/__init__.py | 2 ++
1 file changed, 2 insertions(+)
diff --git a/resources/lib/youtube_plugin/kodion/utils/__init__.py b/resources/lib/youtube_plugin/kodion/utils/__init__.py
index ff5c0cc44..5b9e8df41 100644
--- a/resources/lib/youtube_plugin/kodion/utils/__init__.py
+++ b/resources/lib/youtube_plugin/kodion/utils/__init__.py
@@ -19,6 +19,7 @@
make_dirs,
select_stream,
strip_html_from_text,
+ to_str,
to_unicode,
to_utf8,
)
@@ -45,6 +46,7 @@
'make_dirs',
'select_stream',
'strip_html_from_text',
+ 'to_str',
'to_unicode',
'to_utf8',
'AccessManager',
From 20d7d33231833d4a2fcfde78c08cbfdede34f9cd Mon Sep 17 00:00:00 2001
From: MoojMidge <56883549+MoojMidge@users.noreply.github.com>
Date: Tue, 28 Nov 2023 17:09:35 +1100
Subject: [PATCH 046/141] Minor improvement to update_video_infos
- Fix watch later context menu item on items already in WL
- Update regexps
- Minimise work in loop
---
.../youtube_plugin/youtube/helper/utils.py | 83 +++++++++++--------
1 file changed, 47 insertions(+), 36 deletions(-)
diff --git a/resources/lib/youtube_plugin/youtube/helper/utils.py b/resources/lib/youtube_plugin/youtube/helper/utils.py
index 0680b342e..20f608d65 100644
--- a/resources/lib/youtube_plugin/youtube/helper/utils.py
+++ b/resources/lib/youtube_plugin/youtube/helper/utils.py
@@ -21,29 +21,34 @@
ISHelper = None
+__RE_HISTORY_MATCH = re.compile(r'^/special/watch_history_tv/$')
+
+__RE_PLAYLIST_MATCH = re.compile(
+ r'^(/channel/(?P[^/]+))/playlist/(?P[^/]+)/$'
+)
+
__RE_SEASON_EPISODE_MATCHES__ = [
re.compile(r'Part (?P\d+)'),
re.compile(r'#(?P\d+)'),
- re.compile(r'Ep.[^\w]?(?P\d+)'),
- re.compile(r'\[(?P\d+)\]'),
+ re.compile(r'Ep.\W?(?P\d+)'),
+ re.compile(r'\[(?P\d+)]'),
re.compile(r'S(?P\d+)E(?P\d+)'),
re.compile(r'Season (?P\d+)(.+)Episode (?P\d+)'),
re.compile(r'Episode (?P\d+)'),
]
+__RE_URL = re.compile(r'(https?://\S+)')
-def extract_urls(text):
- result = []
- re_url = re.compile(r'(https?://[^\s]+)')
- matches = re_url.findall(text)
- result = matches or result
-
- return result
+def extract_urls(text):
+ return __RE_URL.findall(text)
def get_thumb_timestamp(minutes=15):
- return str(time.mktime(time.gmtime(minutes * 60 * (round(time.time() / (minutes * 60))))))
+ seconds = minutes * 60
+ return str(time.mktime(time.gmtime(
+ seconds * (round(time.time() / seconds))
+ )))
def make_comment_item(context, snippet, uri, total_replies=0):
@@ -335,6 +340,7 @@ def update_video_infos(provider, context, video_id_dict,
thumb_size = settings.use_thumbnail_size()
thumb_stamp = get_thumb_timestamp()
ui = context.get_ui()
+ watch_later_playlist_id = context.get_access_manager().get_watch_later_id()
for video_id, yt_item in data.items():
video_item = video_id_dict[video_id]
@@ -502,10 +508,12 @@ def update_video_infos(provider, context, video_id_dict,
/channel/[CHANNEL_ID]/playlist/[PLAYLIST_ID]/
/playlist/[PLAYLIST_ID]/
"""
- some_playlist_match = re.match(r'^(/channel/([^/]+))/playlist/(?P[^/]+)/$', path)
- if some_playlist_match:
+ playlist_match = __RE_PLAYLIST_MATCH.match(path)
+ playlist_id = playlist_channel_id = ''
+ if playlist_match:
replace_context_menu = True
- playlist_id = some_playlist_match.group('playlist_id')
+ playlist_id = playlist_match.group('playlist_id')
+ playlist_channel_id = playlist_match.group('channel_id')
yt_context_menu.append_play_all_from_playlist(
context_menu, context, playlist_id, video_id
@@ -520,31 +528,34 @@ def update_video_infos(provider, context, video_id_dict,
if logged_in:
# add 'Watch Later' only if we are not in my 'Watch Later' list
- watch_later_playlist_id = context.get_access_manager().get_watch_later_id()
- if watch_later_playlist_id:
- yt_context_menu.append_watch_later(context_menu, context, watch_later_playlist_id, video_id)
+ if (watch_later_playlist_id
+ and watch_later_playlist_id != playlist_id):
+ yt_context_menu.append_watch_later(
+ context_menu, context, watch_later_playlist_id, video_id
+ )
# provide 'remove' for videos in my playlists
- if video_id in playlist_item_id_dict:
- playlist_match = re.match('^/channel/mine/playlist/(?P[^/]+)/$', path)
- if playlist_match:
- playlist_id = playlist_match.group('playlist_id')
- # we support all playlist except 'Watch History'
- if playlist_id and playlist_id != 'HL' and playlist_id.strip().lower() != 'wl':
- playlist_item_id = playlist_item_id_dict[video_id]
- video_item.set_playlist_id(playlist_id)
- video_item.set_playlist_item_id(playlist_item_id)
- context_menu.append((context.localize('remove'),
- 'RunPlugin(%s)' % context.create_uri(
- ['playlist', 'remove', 'video'],
- {'playlist_id': playlist_id,
- 'video_id': playlist_item_id,
- 'video_name': video_item.get_name()}
- )))
-
- is_history = re.match('^/special/watch_history_tv/$', path)
- if is_history:
- yt_context_menu.append_clear_watch_history(context_menu, context)
+ # we support all playlist except 'Watch History'
+ if (video_id in playlist_item_id_dict and playlist_id
+ and playlist_channel_id == 'mine'
+ and playlist_id.strip().lower() not in ('hl', 'wl')):
+ playlist_item_id = playlist_item_id_dict[video_id]
+ video_item.set_playlist_id(playlist_id)
+ video_item.set_playlist_item_id(playlist_item_id)
+ context_menu.append((
+ context.localize('remove'),
+ 'RunPlugin(%s)' % context.create_uri(
+ ['playlist', 'remove', 'video'],
+ {'playlist_id': playlist_id,
+ 'video_id': playlist_item_id,
+ 'video_name': video_item.get_name()}
+ )
+ ))
+
+ if __RE_HISTORY_MATCH.match(path):
+ yt_context_menu.append_clear_watch_history(
+ context_menu, context
+ )
# got to [CHANNEL], only if we are not directly in the channel provide a jump to the channel
if (channel_id and channel_name and
From cb38bb5ab5716c4a549b02ef3895cec6a292314c Mon Sep 17 00:00:00 2001
From: MoojMidge <56883549+MoojMidge@users.noreply.github.com>
Date: Tue, 28 Nov 2023 18:08:03 +1100
Subject: [PATCH 047/141] Update video stats display
- Uses same formatting style as comment details
---
.../kodion/context/xbmc/xbmc_context.py | 2 +-
.../youtube_plugin/youtube/helper/utils.py | 48 ++++++++++++-------
2 files changed, 32 insertions(+), 18 deletions(-)
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 66fdf6a94..873c42f9a 100644
--- a/resources/lib/youtube_plugin/kodion/context/xbmc/xbmc_context.py
+++ b/resources/lib/youtube_plugin/kodion/context/xbmc/xbmc_context.py
@@ -167,7 +167,7 @@ class XbmcContext(AbstractContext):
'sign.twice.text': 30547,
'sign.twice.title': 30546,
'stats.commentCount': 30732,
- 'stats.favoriteCount': 30100,
+ # 'stats.favoriteCount': 30100,
'stats.likeCount': 30733,
'stats.viewCount': 30767,
'stream.alternate': 30747,
diff --git a/resources/lib/youtube_plugin/youtube/helper/utils.py b/resources/lib/youtube_plugin/youtube/helper/utils.py
index 20f608d65..27a2db505 100644
--- a/resources/lib/youtube_plugin/youtube/helper/utils.py
+++ b/resources/lib/youtube_plugin/youtube/helper/utils.py
@@ -20,6 +20,12 @@
except ImportError:
ISHelper = None
+__COLOR_MAP = {
+ 'commentCount': 'cyan',
+ 'favoriteCount': 'gold',
+ 'likeCount': 'lime',
+ 'viewCount': 'lightblue',
+}
__RE_HISTORY_MATCH = re.compile(r'^/special/watch_history_tv/$')
@@ -63,8 +69,9 @@ def make_comment_item(context, snippet, uri, total_replies=0):
like_count = snippet['likeCount']
if like_count:
like_count = utils.friendly_number(like_count)
- label_likes = ui.color('lime', ui.bold(like_count))
- plot_likes = ui.color('lime', ui.bold(' '.join((
+ color = __COLOR_MAP['likeCount']
+ label_likes = ui.color(color, ui.bold(like_count))
+ plot_likes = ui.color(color, ui.bold(' '.join((
like_count, context.localize('video.comments.likes')
))))
label_props.append(label_likes)
@@ -72,8 +79,9 @@ def make_comment_item(context, snippet, uri, total_replies=0):
if total_replies:
total_replies = utils.friendly_number(total_replies)
- label_replies = ui.color('cyan', ui.bold(total_replies))
- plot_replies = ui.color('cyan', ui.bold(' '.join((
+ color = __COLOR_MAP['commentCount']
+ label_replies = ui.color(color, ui.bold(total_replies))
+ plot_replies = ui.color(color, ui.bold(' '.join((
total_replies, context.localize('video.comments.replies')
))))
label_props.append(label_replies)
@@ -405,30 +413,36 @@ def update_video_infos(provider, context, video_id_dict,
)
)
- # update and set the title
- title = video_item.get_title() or snippet['title'] or ''
- if video_item.upcoming:
- title = ui.italic(title)
- video_item.set_title(title)
-
+ label_stats = []
stats = []
if 'statistics' in yt_item:
for stat, value in yt_item['statistics'].items():
label = context.LOCAL_MAP.get('stats.' + stat)
if label:
- stats.append('{value} {name}'.format(
- name=context.localize(label).lower(),
- value=utils.friendly_number(value)
- ))
- stats = ', '.join(stats)
+ color = __COLOR_MAP.get(stat, 'white')
+ value = utils.friendly_number(value)
+ label_stats.append(ui.color(color, value))
+ stats.append(ui.color(color, ui.bold(' '.join((
+ value, context.localize(label)
+ )))))
+ label_stats = '|'.join(label_stats)
+ stats = '|'.join(stats)
# Used for label2, but is poorly supported in skins
video_details = ' | '.join((detail for detail in (
- ui.light(stats) if stats else '',
+ stats if stats else '',
ui.italic(start_at) if start_at else '',
) if detail))
video_item.set_short_details(video_details)
+ # update and set the title
+ title = video_item.get_title() or snippet['title'] or ''
+ if video_item.upcoming:
+ title = ui.italic(title)
+ if label_stats:
+ title = '{0} ({1})'.format(title, label_stats)
+ video_item.set_title(title)
+
"""
This is experimental. We try to get the most information out of the title of a video.
This is not based on any language. In some cases this won't work at all.
@@ -452,7 +466,7 @@ def update_video_infos(provider, context, video_id_dict,
if show_details:
description = ''.join((
ui.bold(channel_name, cr_after=2) if channel_name else '',
- ui.light(stats, cr_after=1) if stats else '',
+ ui.new_line(stats, cr_after=1) if stats else '',
ui.italic(start_at, cr_after=1) if start_at else '',
ui.new_line() if stats or start_at else '',
description,
From 1402511c5eb15bc15e83f435b421f026088f57be Mon Sep 17 00:00:00 2001
From: MoojMidge <56883549+MoojMidge@users.noreply.github.com>
Date: Tue, 28 Nov 2023 23:34:06 +1100
Subject: [PATCH 048/141] Update subtitles to use BaseRequestClass
---
.../kodion/constants/const_settings.py | 1 +
.../kodion/settings/abstract_settings.py | 3 +
.../youtube/helper/subtitles.py | 134 +++++++++++-------
3 files changed, 84 insertions(+), 54 deletions(-)
diff --git a/resources/lib/youtube_plugin/kodion/constants/const_settings.py b/resources/lib/youtube_plugin/kodion/constants/const_settings.py
index 25a61cc0a..47dfeb3a0 100644
--- a/resources/lib/youtube_plugin/kodion/constants/const_settings.py
+++ b/resources/lib/youtube_plugin/kodion/constants/const_settings.py
@@ -19,6 +19,7 @@
SUBTITLE_LANGUAGE = 'kodion.subtitle.languages.num' # (int)
SUBTITLE_DOWNLOAD = 'kodion.subtitle.download' # (bool)
SETUP_WIZARD = 'kodion.setup_wizard' # (bool)
+LANGUAGE = 'youtube.language' # (str)
LOCATION = 'youtube.location' # (str)
LOCATION_RADIUS = 'youtube.location.radius' # (int)
PLAY_COUNT_MIN_PERCENT = 'kodion.play_count.percent' # (int)
diff --git a/resources/lib/youtube_plugin/kodion/settings/abstract_settings.py b/resources/lib/youtube_plugin/kodion/settings/abstract_settings.py
index f2b6cd36c..79a32cd6b 100644
--- a/resources/lib/youtube_plugin/kodion/settings/abstract_settings.py
+++ b/resources/lib/youtube_plugin/kodion/settings/abstract_settings.py
@@ -316,3 +316,6 @@ def client_selection(self):
def show_detailed_description(self):
return self.get_bool(SETTINGS.DETAILED_DESCRIPTION, True)
+
+ def get_language(self):
+ return self.get_string(SETTINGS.LANGUAGE, 'en_US').replace('_', '-')
diff --git a/resources/lib/youtube_plugin/youtube/helper/subtitles.py b/resources/lib/youtube_plugin/youtube/helper/subtitles.py
index af604d8d6..bcf7dd4d4 100644
--- a/resources/lib/youtube_plugin/youtube/helper/subtitles.py
+++ b/resources/lib/youtube_plugin/youtube/helper/subtitles.py
@@ -10,7 +10,8 @@
from urllib.parse import parse_qs, urlsplit, urlunsplit, urlencode, urljoin
import xbmcvfs
-import requests
+
+from ...kodion.network import BaseRequestsClass
from ...kodion.utils import make_dirs
@@ -25,12 +26,13 @@ class Subtitles(object):
SRT_FILE = ''.join([BASE_PATH, '%s.%s.srt'])
def __init__(self, context, video_id, captions, headers=None):
- self.context = context
- self._verify = context.get_settings().verify_ssl()
self.video_id = video_id
- self.language = (context.get_settings()
- .get_string('youtube.language', 'en_US')
- .replace('_', '-'))
+ self.context = context
+
+ settings = context.get_settings()
+ self.language = settings.get_language()
+ self.pre_download = settings.subtitle_download()
+ self.subtitle_languages = settings.subtitle_languages()
if not headers and 'headers' in captions:
headers = captions['headers']
@@ -66,11 +68,8 @@ def __init__(self, context, video_id, captions, headers=None):
'defaultTranslationSourceTrackIndices', [None]
)[0]
- if default_caption is None:
- default_caption = (
- default_audio.get('hasDefaultTrack')
- and default_audio.get('defaultCaptionTrackIndex')
- )
+ if default_caption is None and default_audio.get('hasDefaultTrack'):
+ default_caption = default_audio.get('defaultCaptionTrackIndex')
if default_caption is None:
try:
@@ -92,25 +91,21 @@ def __init__(self, context, video_id, captions, headers=None):
def srt_filename(self, sub_language):
return self.SRT_FILE % (self.video_id, sub_language)
- def _write_file(self, _file, contents):
+ def _write_file(self, filepath, contents):
if not make_dirs(self.BASE_PATH):
self.context.log_debug('Failed to create directories: %s' % self.BASE_PATH)
return False
- self.context.log_debug('Writing subtitle file: %s' % _file)
+ self.context.log_debug('Writing subtitle file: %s' % filepath)
+
try:
- f = xbmcvfs.File(_file, 'w')
- f.write(contents)
- f.close()
- return True
- except:
- self.context.log_debug('File write failed for: %s' % _file)
+ with xbmcvfs.File(filepath, 'w') as srt_file:
+ success = srt_file.write(contents)
+ except (IOError, OSError):
+ self.context.log_debug('File write failed for: %s' % filepath)
return False
+ return success
def _unescape(self, text):
- try:
- text = text.decode('utf8', 'ignore')
- except:
- self.context.log_debug('Subtitle unescape: failed to decode utf-8')
try:
text = unescape(text)
except:
@@ -127,7 +122,7 @@ def get_subtitles(self):
if self.prompt_override:
languages = self.LANG_PROMPT
else:
- languages = self.context.get_settings().subtitle_languages()
+ languages = self.subtitle_languages
self.context.log_debug('Subtitle get_subtitles: for setting |%s|' % str(languages))
if languages == self.LANG_NONE:
return []
@@ -154,30 +149,60 @@ def get_subtitles(self):
self.context.log_debug('Unknown language_enum: %s for subtitles' % str(languages))
return []
- def _get_all(self):
+ def _get_all(self, download=False):
list_of_subs = []
- for language in self.translation_langs:
- list_of_subs.extend(self._get(language=language.get('languageCode')))
+ for track in self.caption_tracks:
+ list_of_subs.extend(self._get(track.get('languageCode'),
+ self._get_language_name(track),
+ download=download))
+ for track in self.translation_langs:
+ list_of_subs.extend(self._get(track.get('languageCode'),
+ self._get_language_name(track),
+ download=download))
return list(set(list_of_subs))
def _prompt(self):
- tracks = [(track.get('languageCode'), self._get_language_name(track)) for track in self.caption_tracks]
- translations = [(track.get('languageCode'), self._get_language_name(track)) for track in self.translation_langs]
- languages = tracks + translations
- if languages:
- choice = self.context.get_ui().on_select(self.context.localize('subtitles.language'), [language for _, language in languages])
- if choice != -1:
- return self._get(lang_code=languages[choice][0], language=languages[choice][1])
- self.context.log_debug('Subtitle selection cancelled')
- return []
+ captions = [(track.get('languageCode'),
+ self._get_language_name(track))
+ for track in self.caption_tracks]
+ translations = [(track.get('languageCode'),
+ self._get_language_name(track))
+ for track in self.translation_langs]
+ num_captions = len(captions)
+ num_translations = len(translations)
+ num_total = num_captions + num_translations
+
+ if num_total:
+ choice = self.context.get_ui().on_select(
+ self.context.localize('subtitles.language'),
+ [name for _, name in captions] +
+ [name + ' *' for _, name in translations]
+ )
+ if choice == -1:
+ self.context.log_debug('Subtitle selection cancelled')
+ return []
+
+ subtitle = None
+ if 0 <= choice < num_captions:
+ choice = captions[choice]
+ subtitle = self._get(lang_code=choice[0], language=choice[1])
+ elif num_captions <= choice < num_total:
+ choice = translations[choice - num_captions]
+ subtitle = self._get(lang_code=choice[0], language=choice[1])
+
+ if subtitle:
+ return subtitle
self.context.log_debug('No subtitles found for prompt')
return []
- def _get(self, lang_code='en', language=None, no_asr=False):
- fname = self.srt_filename(lang_code)
- if xbmcvfs.exists(fname):
- self.context.log_debug('Subtitle exists for: %s, filename: %s' % (lang_code, fname))
- return [fname]
+ def _get(self, lang_code='en', language=None, no_asr=False, download=None):
+ filename = self.srt_filename(lang_code)
+ if xbmcvfs.exists(filename):
+ self.context.log_debug('Subtitle exists for: %s, filename: %s' % (lang_code, filename))
+ return [filename]
+
+ if download is None:
+ download = self.pre_download
caption_track = None
asr_track = None
@@ -229,21 +254,27 @@ def _get(self, lang_code='en', language=None, no_asr=False):
if subtitle_url:
self.context.log_debug('Subtitle url: %s' % subtitle_url)
- if not self.context.get_settings().subtitle_download():
+ if not download:
return [subtitle_url]
- result_auto = requests.get(subtitle_url, headers=self.headers,
- verify=self._verify, allow_redirects=True)
- if result_auto.text:
+ response = BaseRequestsClass().request(subtitle_url,
+ headers=self.headers)
+ if response.text:
self.context.log_debug('Subtitle found for: %s' % lang_code)
- self._write_file(fname, bytearray(self._unescape(result_auto.text), encoding='utf8', errors='ignore'))
- return [fname]
+ self._write_file(filename,
+ bytearray(self._unescape(response.text),
+ encoding='utf8',
+ errors='ignore'))
+ return [filename]
+
self.context.log_debug('Failed to retrieve subtitles for: %s' % lang_code)
return []
+
self.context.log_debug('No subtitles found for: %s' % lang_code)
return []
- def _get_language_name(self, track):
+ @staticmethod
+ def _get_language_name(track):
key = 'languageName' if 'languageName' in track else 'name'
lang_name = track.get(key, {}).get('simpleText')
if not lang_name:
@@ -252,14 +283,9 @@ def _get_language_name(self, track):
lang_name = track_name[0].get('text')
if lang_name:
- return self._recode_language_name(lang_name)
-
+ return lang_name
return None
- @staticmethod
- def _recode_language_name(language_name):
- return language_name
-
@staticmethod
def _set_query_param(url, *pairs):
if not url or not pairs:
From 929fefc8170fb03810a0cd47210b9ab15342a72a Mon Sep 17 00:00:00 2001
From: MoojMidge <56883549+MoojMidge@users.noreply.github.com>
Date: Thu, 30 Nov 2023 16:34:07 +1100
Subject: [PATCH 049/141] Update url_resolver to use BaseRequestClass
- Update user-agent
- Use retry and error handling functionality of BaseRequestClass to fix #250
- Simplify/update resolver logic for new URLs and meta content
- Needs testing for regressions
---
.../youtube/helper/url_resolver.py | 297 +++++++++---------
.../youtube/helper/yt_specials.py | 2 -
2 files changed, 146 insertions(+), 153 deletions(-)
diff --git a/resources/lib/youtube_plugin/youtube/helper/url_resolver.py b/resources/lib/youtube_plugin/youtube/helper/url_resolver.py
index ffca68a24..d82a1db84 100644
--- a/resources/lib/youtube_plugin/youtube/helper/url_resolver.py
+++ b/resources/lib/youtube_plugin/youtube/helper/url_resolver.py
@@ -9,20 +9,39 @@
"""
import re
-from urllib.parse import parse_qsl
-from urllib.parse import urlparse
-from urllib.parse import parse_qs
-from urllib.parse import urlunsplit
-from urllib.parse import urlencode
+from urllib.parse import parse_qsl, urlencode, urlparse
+
+from ...kodion.network import BaseRequestsClass
+
+
+class AbstractResolver(BaseRequestsClass):
+ _HEADERS = {
+ 'Cache-Control': 'max-age=0',
+ 'Accept': ('text/html,'
+ 'application/xhtml+xml,'
+ 'application/xml;q=0.9,'
+ 'image/webp,'
+ '*/*;q=0.8'),
+ # Desktop user agent
+ 'User-Agent': ('Mozilla/5.0 (Windows NT 10.0; Win64; x64)'
+ ' AppleWebKit/537.36 (KHTML, like Gecko)'
+ ' Chrome/119.0.0.0 Safari/537.36'),
+ # Mobile user agent - for testing m.youtube.com redirect
+ # 'User-Agent': ('Mozilla/5.0 (Linux; Android 10; SM-G981B)'
+ # ' AppleWebKit/537.36 (KHTML, like Gecko)'
+ # ' Chrome/80.0.3987.162 Mobile Safari/537.36'),
+ # Old desktop user agent - for testing /supported_browsers redirect
+ # 'User-Agent': ('Mozilla/5.0 (Windows NT 6.1; WOW64)'
+ # ' AppleWebKit/537.36 (KHTML, like Gecko)'
+ # ' Chrome/41.0.2272.118 Safari/537.36'),
+ 'DNT': '1',
+ 'Accept-Encoding': 'gzip, deflate',
+ 'Accept-Language': 'en-US,en;q=0.8,de;q=0.6'
+ }
-from ...kodion.utils import FunctionCache
-from ...kodion import Context as _Context
-import requests
-
-
-class AbstractResolver(object):
- def __init__(self):
- self._verify = _Context(plugin_id='plugin.video.youtube').get_settings().verify_ssl()
+ def __init__(self, context):
+ self._context = context
+ super(AbstractResolver, self).__init__()
def supports_url(self, url, url_components):
raise NotImplementedError()
@@ -32,165 +51,141 @@ def resolve(self, url, url_components):
class YouTubeResolver(AbstractResolver):
- RE_USER_NAME = re.compile(r'http(s)?://(www.)?youtube.com/(?P[a-zA-Z0-9]+)$')
-
- def __init__(self):
- super(YouTubeResolver, self).__init__()
+ def __init__(self, *args, **kwargs):
+ super(YouTubeResolver, self).__init__(*args, **kwargs)
def supports_url(self, url, url_components):
- if url_components.hostname == 'www.youtube.com' or url_components.hostname == 'youtube.com':
- if url_components.path.lower() in ['/redirect', '/user']:
- return True
-
- if url_components.path.lower().startswith('/user'):
- return True
-
- re_match = self.RE_USER_NAME.match(url)
- if re_match:
- return True
-
- return False
-
- def resolve(self, url, url_components):
- def _load_page(_url):
- # we try to extract the channel id from the html content. With the channel id we can construct a url we
- # already work with.
- # https://www.youtube.com/channel/
- try:
- headers = {'Cache-Control': 'max-age=0',
- 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
- 'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.118 Safari/537.36',
- 'DNT': '1',
- 'Accept-Encoding': 'gzip, deflate',
- 'Accept-Language': 'en-US,en;q=0.8,de;q=0.6'}
- response = requests.get(url, headers=headers, verify=self._verify)
- if response.status_code == 200:
- match = re.search(r'', response.text)
- if match:
- channel_id = match.group('channel_id')
- return 'https://www.youtube.com/channel/%s' % channel_id
- except:
- # do nothing
- pass
-
- return _url
-
- if url_components.path.lower() == '/redirect':
+ if url_components.hostname not in (
+ 'www.youtube.com',
+ 'youtube.com',
+ 'm.youtube.com',
+ ):
+ return False
+
+ path = url_components.path.lower()
+ if path.startswith((
+ '/@',
+ '/c/',
+ '/channel/',
+ '/user/',
+ )):
+ return 'GET'
+
+ if path.startswith((
+ '/embed',
+ '/live',
+ '/redirect',
+ '/shorts',
+ '/supported_browsers',
+ '/watch',
+ )):
+ return 'HEAD'
+
+ # user channel in the form of youtube.com/username
+ path = path.strip('/').split('/', 1)
+ return 'GET' if len(path) == 1 and path[0] else False
+
+ def resolve(self, url, url_components, method='HEAD'):
+ path = url_components.path.lower()
+ if path == '/redirect':
params = dict(parse_qsl(url_components.query))
- return params['q']
-
- if url_components.path.lower().startswith('/user'):
- return _load_page(url)
+ url = params['q']
+
+ # "sometimes", we get a redirect through a URL of the form
+ # https://.../supported_browsers?next_url=&further=parameters&stuck=here
+ # put together query string from both what's encoded inside
+ # next_url and the remaining parameters of this URL...
+ elif path == '/supported_browsers':
+ # top-level query string
+ params = dict(parse_qsl(url_components.query))
+ # components of next_url
+ next_components = urlparse(params.pop('next_url', ''))
+ if not next_components.scheme or not next_components.netloc:
+ return url
+ # query string encoded inside next_url
+ next_params = dict(parse_qsl(next_components.query))
+ # add/overwrite all other params from top level query string
+ next_params.update(params)
+ # build new URL from these components
+ return next_components._replace(
+ query=urlencode(next_params)
+ ).geturl()
+
+ response = self.request(url,
+ method=method,
+ headers=self._HEADERS,
+ allow_redirects=True)
+ if response.status_code != 200:
+ return url
- re_match = self.RE_USER_NAME.match(url)
- if re_match:
- return _load_page(url)
+ # we try to extract the channel id from the html content
+ # With the channel id we can construct a URL we already work with
+ # https://www.youtube.com/channel/
+ if method == 'GET':
+ match = re.search(
+ r'',
+ response.text)
+ if match:
+ return match.group('channel_url')
- return url
+ return response.url
-class CommonResolver(AbstractResolver, list):
- def __init__(self):
- super(CommonResolver, self).__init__()
+class CommonResolver(AbstractResolver):
+ def __init__(self, *args, **kwargs):
+ super(CommonResolver, self).__init__(*args, **kwargs)
def supports_url(self, url, url_components):
- return True
-
- def resolve(self, url, url_components):
- def _loop(_url, tries=5):
- if tries == 0:
- return _url
-
- try:
- headers = {'Cache-Control': 'max-age=0',
- 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
- 'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.118 Safari/537.36',
- 'DNT': '1',
- 'Accept-Encoding': 'gzip, deflate',
- 'Accept-Language': 'en-US,en;q=0.8,de;q=0.6'}
- response = requests.head(_url, headers=headers, verify=self._verify, allow_redirects=False)
- if response.status_code == 304:
- return url
-
- if response.status_code in [301, 302, 303]:
- headers = response.headers
- location = headers.get('location', '')
-
- # validate the location - some server returned garbage
- _url_components = urlparse(location)
- if not _url_components.scheme and not _url_components.hostname:
- return url
-
- # some server return 301 for HEAD requests
- # we just compare the new location - if it's equal we can return the url
- if location == _url or ''.join([location, '/']) == _url or location == ''.join([_url, '/']):
- return _url
-
- if location:
- return _loop(location, tries=tries - 1)
-
- # just to be sure ;)
- location = headers.get('Location', '')
- if location:
- return _loop(location, tries=tries - 1)
-
- if response.status_code == 200:
- _url_components = urlparse(_url)
- if _url_components.path == '/supported_browsers':
- # "sometimes", we get a redirect through an URL of the form https://.../supported_browsers?next_url=&further=paramaters&stuck=here
- # put together query string from both what's encoded inside next_url and the remaining paramaters of this URL...
- _query = parse_qs(_url_components.query) # top-level query string
- _nc = urlparse(_query['next_url'][0]) # components of next_url
- _next_query = parse_qs(_nc.query) # query string encoded inside next_url
- del _query['next_url'] # remove next_url from top level query string
- _next_query.update(_query) # add/overwrite all other params from top level query string
- _next_query = dict(map(lambda kv: (kv[0], kv[1][0]), _next_query.items())) # flatten to only use first argument of each param
- _next_url = urlunsplit((_nc.scheme, _nc.netloc, _nc.path, urlencode(_next_query), _nc.fragment)) # build new URL from these components
- return _next_url
-
- except:
- # do nothing
- pass
-
- return _url
-
- resolved_url = _loop(url)
-
- return resolved_url
+ return 'HEAD'
+
+ def resolve(self, url, url_components, method='HEAD'):
+ response = self.request(url,
+ method=method,
+ headers=self._HEADERS,
+ allow_redirects=True)
+ if response.status_code != 200:
+ return url
+ return response.url
class UrlResolver(object):
def __init__(self, context):
self._context = context
- self._cache = {}
- self._youtube_resolver = YouTubeResolver()
- self._resolver = [
- self._youtube_resolver,
- CommonResolver()
+ self._cache = context.get_function_cache()
+ self._resolver_map = {
+ 'common_resolver': CommonResolver(context),
+ 'youtube_resolver': YouTubeResolver(context),
+ }
+ self._resolvers = [
+ 'common_resolver',
+ 'youtube_resolver',
]
def clear(self):
- self._context.get_function_cache().clear()
+ self._cache.clear()
def _resolve(self, url):
- # try one of the resolver
- url_components = urlparse(url)
- for resolver in self._resolver:
- if resolver.supports_url(url, url_components):
- resolved_url = resolver.resolve(url, url_components)
- self._cache[url] = resolved_url
-
- # one last check...sometimes the resolved url is YouTube-specific and can be resolved again or
- # simplified.
- url_components = urlparse(resolved_url)
- if resolver is not self._youtube_resolver and self._youtube_resolver.supports_url(resolved_url, url_components):
- return self._youtube_resolver.resolve(resolved_url, url_components)
-
- return resolved_url
+ # try one of the resolvers
+ resolved_url = url
+ for resolver_name in self._resolvers:
+ resolver = self._resolver_map[resolver_name]
+ url_components = urlparse(resolved_url)
+ method = resolver.supports_url(resolved_url, url_components)
+ if not method:
+ continue
+
+ self._context.log_debug('Resolving |{uri}| using |{name} {method}|'
+ .format(uri=resolved_url,
+ name=resolver_name,
+ method=method))
+ resolved_url = resolver.resolve(resolved_url,
+ url_components,
+ method)
+ self._context.log_debug('Resolved to |{0}|'.format(resolved_url))
+ return resolved_url
def resolve(self, url):
- function_cache = self._context.get_function_cache()
- resolved_url = function_cache.get(FunctionCache.ONE_DAY, self._resolve, url)
+ resolved_url = self._cache.get(self._cache.ONE_DAY, self._resolve, url)
if not resolved_url or resolved_url == '/':
return url
diff --git a/resources/lib/youtube_plugin/youtube/helper/yt_specials.py b/resources/lib/youtube_plugin/youtube/helper/yt_specials.py
index 3d8a56e55..4cfff9f26 100644
--- a/resources/lib/youtube_plugin/youtube/helper/yt_specials.py
+++ b/resources/lib/youtube_plugin/youtube/helper/yt_specials.py
@@ -164,10 +164,8 @@ def _extract_urls(_video_id):
res_urls = []
for url in urls:
- context.log_debug('Resolving url "%s"' % url)
progress_dialog.update(steps=1, text=url)
resolved_url = url_resolver.resolve(url)
- context.log_debug('Resolved url "%s"' % resolved_url)
res_urls.append(resolved_url)
if progress_dialog.is_aborted():
From 680ab035928ba050853d41c9e68013b8de7c5f6a Mon Sep 17 00:00:00 2001
From: MoojMidge <56883549+MoojMidge@users.noreply.github.com>
Date: Fri, 1 Dec 2023 11:31:45 +1100
Subject: [PATCH 050/141] Improve UrlToItemConverter channels and playlists
functionality
- Add support for live channels
- Add support for playlist starting video_id
- Simplify regexp
---
.../youtube/helper/url_to_item_converter.py | 44 ++++++++++---------
1 file changed, 24 insertions(+), 20 deletions(-)
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 20f0d4452..536e0e0d1 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
@@ -16,9 +16,7 @@
class UrlToItemConverter(object):
- RE_CHANNEL_ID = re.compile(r'^/channel/(?P.+)$', re.I)
- RE_LIVE_VID = re.compile(r'^/live/(?P.+)$', re.I)
- RE_SHORTS_VID = re.compile(r'^/shorts/(?P[^?/]+)$', re.I)
+ RE_PATH_ID = re.compile(r'/\w+/(?P[^/?#]+)', re.I)
RE_SEEK_TIME = re.compile(r'\d+')
VALID_HOSTNAMES = {
'youtube.com',
@@ -48,25 +46,25 @@ def add_url(self, url, provider, context):
))
return
- params = dict(parse_qsl(parsed_url.query))
+ url_params = dict(parse_qsl(parsed_url.query))
path = parsed_url.path.lower()
- video_id = playlist_id = channel_id = seek_time = None
+ channel_id = live = playlist_id = seek_time = video_id = None
if path == '/watch':
- video_id = params.get('v')
- playlist_id = params.get('list')
- seek_time = params.get('t')
+ video_id = url_params.get('v')
+ playlist_id = url_params.get('list')
+ seek_time = url_params.get('t')
elif path == '/playlist':
- playlist_id = params.get('list')
- elif path.startswith('/shorts/'):
- re_match = self.RE_SHORTS_VID.match(parsed_url.path)
- video_id = re_match.group('video_id')
+ playlist_id = url_params.get('list')
elif path.startswith('/channel/'):
- re_match = self.RE_CHANNEL_ID.match(parsed_url.path)
- channel_id = re_match.group('channel_id')
- elif path.startswith('/live/'):
- re_match = self.RE_LIVE_VID.match(parsed_url.path)
- video_id = re_match.group('video_id')
+ re_match = self.RE_PATH_ID.match(parsed_url.path)
+ channel_id = re_match.group('id')
+ if '/live' in path:
+ live = 1
+ elif path.startswith(('/live/', '/shorts/')):
+ re_match = self.RE_PATH_ID.match(parsed_url.path)
+ video_id = re_match.group('id')
+ seek_time = url_params.get('t')
else:
context.log_debug('Unknown path "{0}" in url "{1}"'.format(
parsed_url.path, url
@@ -87,8 +85,11 @@ def add_url(self, url, provider, context):
if number
)
plugin_params['seek'] = seek_time
- plugin_uri = context.create_uri(['play'], plugin_params)
- video_item = VideoItem('', plugin_uri)
+ if playlist_id:
+ plugin_params['playlist_id'] = playlist_id
+ video_item = VideoItem(
+ '', context.create_uri(['play'], plugin_params)
+ )
self._video_id_dict[video_id] = video_item
elif playlist_id:
@@ -103,10 +104,13 @@ def add_url(self, url, provider, context):
elif channel_id:
if self._flatten:
+ if live:
+ context.set_param('live', live)
self._channel_ids.append(channel_id)
else:
channel_item = DirectoryItem(
- '', context.create_uri(['channel', channel_id])
+ '', context.create_uri(['channel', channel_id],
+ {'live': live} if live else None)
)
channel_item.set_fanart(provider.get_fanart(context))
self._channel_id_dict[channel_id] = channel_item
From 0d31f4b0dc4ba088d9fe23c51306e169e1272b61 Mon Sep 17 00:00:00 2001
From: MoojMidge <56883549+MoojMidge@users.noreply.github.com>
Date: Sat, 2 Dec 2023 10:10:13 +1100
Subject: [PATCH 051/141] Add option to disable HFR video at max resolution
- Closes #539
---
resources/language/resource.language.en_au/strings.po | 4 ++++
resources/language/resource.language.en_gb/strings.po | 4 ++++
resources/language/resource.language.en_nz/strings.po | 4 ++++
resources/language/resource.language.en_us/strings.po | 4 ++++
resources/lib/youtube_plugin/youtube/helper/video_info.py | 6 +++++-
resources/settings.xml | 1 +
6 files changed, 22 insertions(+), 1 deletion(-)
diff --git a/resources/language/resource.language.en_au/strings.po b/resources/language/resource.language.en_au/strings.po
index 783ff9c92..de411ad0c 100644
--- a/resources/language/resource.language.en_au/strings.po
+++ b/resources/language/resource.language.en_au/strings.po
@@ -1368,3 +1368,7 @@ msgstr ""
msgctxt "#30767"
msgid "Views"
msgstr ""
+
+msgctxt "#30768"
+msgid "Disable high framerate video at maximum video quality"
+msgstr ""
diff --git a/resources/language/resource.language.en_gb/strings.po b/resources/language/resource.language.en_gb/strings.po
index f8007413a..831ef1a2b 100644
--- a/resources/language/resource.language.en_gb/strings.po
+++ b/resources/language/resource.language.en_gb/strings.po
@@ -1368,3 +1368,7 @@ msgstr ""
msgctxt "#30767"
msgid "Views"
msgstr ""
+
+msgctxt "#30768"
+msgid "Disable high framerate video at maximum video quality"
+msgstr ""
diff --git a/resources/language/resource.language.en_nz/strings.po b/resources/language/resource.language.en_nz/strings.po
index b1e21fa84..15c435e76 100644
--- a/resources/language/resource.language.en_nz/strings.po
+++ b/resources/language/resource.language.en_nz/strings.po
@@ -1364,3 +1364,7 @@ msgstr ""
msgctxt "#30767"
msgid "Views"
msgstr ""
+
+msgctxt "#30768"
+msgid "Disable high framerate video at maximum video quality"
+msgstr ""
diff --git a/resources/language/resource.language.en_us/strings.po b/resources/language/resource.language.en_us/strings.po
index 2248415ba..e33ce18a0 100644
--- a/resources/language/resource.language.en_us/strings.po
+++ b/resources/language/resource.language.en_us/strings.po
@@ -1369,3 +1369,7 @@ msgstr ""
msgctxt "#30767"
msgid "Views"
msgstr ""
+
+msgctxt "#30768"
+msgid "Disable high framerate video at maximum video quality"
+msgstr ""
diff --git a/resources/lib/youtube_plugin/youtube/helper/video_info.py b/resources/lib/youtube_plugin/youtube/helper/video_info.py
index 193245d9f..34dd9916b 100644
--- a/resources/lib/youtube_plugin/youtube/helper/video_info.py
+++ b/resources/lib/youtube_plugin/youtube/helper/video_info.py
@@ -1358,6 +1358,7 @@ def _process_stream_data(self, stream_data, default_lang_code='und'):
stream_features = _settings.stream_features()
allow_hdr = 'hdr' in stream_features
allow_hfr = 'hfr' in stream_features
+ disable_hfr_max = 'no_hfr_max' in stream_features
allow_ssa = 'ssa' in stream_features
stream_select = _settings.stream_select()
@@ -1512,7 +1513,7 @@ def _process_stream_data(self, stream_data, default_lang_code='und'):
compare_width = width
compare_height = height
- bounded_quality = {}
+ bounded_quality = None
for quality in qualities:
if compare_width > quality['width']:
if bounded_quality:
@@ -1520,7 +1521,10 @@ def _process_stream_data(self, stream_data, default_lang_code='und'):
quality = bounded_quality
elif compare_height < quality['height']:
quality = qualities[-1]
+ if fps > 30 and disable_hfr_max:
+ bounded_quality = None
break
+ disable_hfr_max = disable_hfr_max and not bounded_quality
bounded_quality = quality
if not bounded_quality:
continue
diff --git a/resources/settings.xml b/resources/settings.xml
index dfa126b51..4c06c207f 100644
--- a/resources/settings.xml
+++ b/resources/settings.xml
@@ -271,6 +271,7 @@
+
From bf7b12a57d0288e9f1994339691d7030df0c34d8 Mon Sep 17 00:00:00 2001
From: MoojMidge <56883549+MoojMidge@users.noreply.github.com>
Date: Sun, 3 Dec 2023 22:10:10 +1100
Subject: [PATCH 052/141] Improve video stats display
- Fix incorrect duration of upcoming videos
- Fix imdbnumber being saved to incorrect info label
- Add view count to listitem info and sort methods
- Add rating to listitem info and sort method based on made up ranking
- Add label masking to sort methods
---
.../kodion/abstract_provider.py | 6 +-
.../kodion/constants/const_sort_methods.py | 101 ++++++++----------
.../kodion/context/xbmc/xbmc_context.py | 2 +-
.../youtube_plugin/kodion/items/base_item.py | 7 ++
.../youtube_plugin/kodion/items/video_item.py | 21 ++--
.../kodion/ui/xbmc/info_labels.py | 19 +++-
.../youtube_plugin/kodion/utils/__init__.py | 2 +
.../youtube_plugin/kodion/utils/methods.py | 19 +++-
.../youtube_plugin/youtube/helper/utils.py | 49 ++++++---
.../lib/youtube_plugin/youtube/provider.py | 21 ++--
10 files changed, 150 insertions(+), 97 deletions(-)
diff --git a/resources/lib/youtube_plugin/kodion/abstract_provider.py b/resources/lib/youtube_plugin/kodion/abstract_provider.py
index 15c150ece..67aacfdc3 100644
--- a/resources/lib/youtube_plugin/kodion/abstract_provider.py
+++ b/resources/lib/youtube_plugin/kodion/abstract_provider.py
@@ -134,8 +134,6 @@ def _internal_root(self, context, re_match):
@staticmethod
def _internal_favorite(context, re_match):
- context.add_sort_method(constants.sort_method.LABEL_IGNORE_THE)
-
params = context.get_params()
command = re_match.group('command')
@@ -197,8 +195,6 @@ def data_cache(self, context):
self._data_cache = context.get_data_cache()
def _internal_search(self, context, re_match):
- context.add_sort_method(constants.sort_method.UNSORTED)
-
params = context.get_params()
command = re_match.group('command')
@@ -276,7 +272,7 @@ def _internal_search(self, context, re_match):
query = query.decode('utf-8')
return self.on_search(query, context, re_match)
- context.set_content_type(constants.content_type.FILES)
+ context.set_content_type(constants.content_type.VIDEOS)
result = []
location = context.get_param('location', False)
diff --git a/resources/lib/youtube_plugin/kodion/constants/const_sort_methods.py b/resources/lib/youtube_plugin/kodion/constants/const_sort_methods.py
index 2838ed93b..c8d2cdb8e 100644
--- a/resources/lib/youtube_plugin/kodion/constants/const_sort_methods.py
+++ b/resources/lib/youtube_plugin/kodion/constants/const_sort_methods.py
@@ -9,66 +9,57 @@
"""
import sys
+
from xbmcplugin import __dict__ as xbmcplugin
namespace = sys.modules[__name__]
names = [
- # 'NONE',
- 'LABEL',
- 'LABEL_IGNORE_THE',
- 'DATE',
- 'SIZE',
- 'FILE',
- 'DRIVE_TYPE',
- 'TRACKNUM',
- 'DURATION',
- 'TITLE',
- 'TITLE_IGNORE_THE',
- 'ARTIST',
- # 'ARTIST_AND_YEAR',
- 'ARTIST_IGNORE_THE',
- 'ALBUM',
- 'ALBUM_IGNORE_THE',
- 'GENRE',
- 'COUNTRY',
- # 'YEAR',
- 'VIDEO_YEAR',
- 'VIDEO_RATING',
- 'VIDEO_USER_RATING',
- 'DATEADDED',
- 'PROGRAM_COUNT',
- 'PLAYLIST_ORDER',
- 'EPISODE',
- 'VIDEO_TITLE',
- 'VIDEO_SORT_TITLE',
- 'VIDEO_SORT_TITLE_IGNORE_THE',
- 'PRODUCTIONCODE',
- 'SONG_RATING',
- 'SONG_USER_RATING',
- 'MPAA_RATING',
- 'VIDEO_RUNTIME',
- 'STUDIO',
- 'STUDIO_IGNORE_THE',
- 'FULLPATH',
- 'LABEL_IGNORE_FOLDERS',
- 'LASTPLAYED',
- 'PLAYCOUNT',
- 'LISTENERS',
- 'UNSORTED',
- 'CHANNEL',
- 'CHANNEL_NUMBER',
- 'BITRATE',
- 'DATE_TAKEN',
- 'CLIENT_CHANNEL_ORDER',
- 'TOTAL_DISCS',
- 'ORIG_DATE',
- 'BPM',
- 'VIDEO_ORIGINAL_TITLE',
- 'VIDEO_ORIGINAL_TITLE_IGNORE_THE',
- 'PROVIDER',
- 'USER_PREFERENCE',
- # 'MAX',
+ 'NONE', # 0
+ 'LABEL', # 1
+ 'LABEL_IGNORE_THE', # 2
+ 'DATE', # 3
+ 'SIZE', # 4
+ 'FILE', # 5
+ 'DRIVE_TYPE', # 6
+ 'TRACKNUM', # 7
+ 'DURATION', # 8
+ 'TITLE', # 9
+ 'TITLE_IGNORE_THE', # 10
+ 'ARTIST', # 11
+ 'ARTIST_IGNORE_THE', # 13
+ 'ALBUM', # 14
+ 'ALBUM_IGNORE_THE', # 15
+ 'GENRE', # 16
+ 'COUNTRY', # 17
+ 'VIDEO_YEAR', # 18
+ 'VIDEO_RATING', # 19
+ 'VIDEO_USER_RATING', # 20
+ 'DATEADDED', # 21
+ 'PROGRAM_COUNT', # 22
+ 'PLAYLIST_ORDER', # 23
+ 'EPISODE', # 24
+ 'VIDEO_TITLE', # 25
+ 'VIDEO_SORT_TITLE', # 26
+ 'VIDEO_SORT_TITLE_IGNORE_THE', # 27
+ 'PRODUCTIONCODE', # 28
+ 'SONG_RATING', # 29
+ 'SONG_USER_RATING', # 30
+ 'MPAA_RATING', # 31
+ 'VIDEO_RUNTIME', # 32
+ 'STUDIO', # 33
+ 'STUDIO_IGNORE_THE', # 34
+ 'FULLPATH', # 35
+ 'LABEL_IGNORE_FOLDERS', # 36
+ 'LASTPLAYED', # 37
+ 'PLAYCOUNT', # 38
+ 'LISTENERS', # 39
+ 'UNSORTED', # 40
+ 'CHANNEL', # 41
+ 'BITRATE', # 43
+ 'DATE_TAKEN', # 44
+ 'VIDEO_ORIGINAL_TITLE', # 49
+ 'VIDEO_ORIGINAL_TITLE_IGNORE_THE', # 50
]
for name in names:
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 873c42f9a..9ea2bbe4c 100644
--- a/resources/lib/youtube_plugin/kodion/context/xbmc/xbmc_context.py
+++ b/resources/lib/youtube_plugin/kodion/context/xbmc/xbmc_context.py
@@ -425,7 +425,7 @@ def set_content_type(self, content_type):
def add_sort_method(self, *sort_methods):
for sort_method in sort_methods:
- xbmcplugin.addSortMethod(self._plugin_handle, sort_method)
+ xbmcplugin.addSortMethod(self._plugin_handle, *sort_method)
def clone(self, new_path=None, new_params=None):
if not new_path:
diff --git a/resources/lib/youtube_plugin/kodion/items/base_item.py b/resources/lib/youtube_plugin/kodion/items/base_item.py
index 832d0fa6b..92d70650a 100644
--- a/resources/lib/youtube_plugin/kodion/items/base_item.py
+++ b/resources/lib/youtube_plugin/kodion/items/base_item.py
@@ -37,6 +37,7 @@ def __init__(self, name, uri, image='', fanart=''):
self._context_menu = None
self._replace_context_menu = False
self._added_utc = None
+ self._count = None
self._date = None
self._dateadded = None
self._short_details = None
@@ -144,6 +145,12 @@ def get_short_details(self):
def set_short_details(self, details):
self._short_details = details or ''
+ def get_count(self):
+ return self._count
+
+ def set_count(self, count):
+ self._count = int(count or 0)
+
@property
def next_page(self):
return self._next_page
diff --git a/resources/lib/youtube_plugin/kodion/items/video_item.py b/resources/lib/youtube_plugin/kodion/items/video_item.py
index c605d3d4d..ba80baf5a 100644
--- a/resources/lib/youtube_plugin/kodion/items/video_item.py
+++ b/resources/lib/youtube_plugin/kodion/items/video_item.py
@@ -8,12 +8,13 @@
See LICENSES/GPL-2.0-only for more information.
"""
-import re
import datetime
+import re
+from html import unescape
from .base_item import BaseItem
+from ..utils import duration_to_seconds
-from html import unescape
__RE_IMDB__ = re.compile(r'(http(s)?://)?www.imdb.(com|de)/title/(?P[t0-9]+)(/)?')
@@ -56,6 +57,7 @@ def __init__(self, name, uri, image='', fanart=''):
self._subscription_id = None
self._playlist_id = None
self._playlist_item_id = None
+ self._production_code = None
def set_play_count(self, play_count):
self._play_count = int(play_count or 0)
@@ -169,10 +171,11 @@ def set_season(self, season):
def get_season(self):
return self._season
- def set_duration(self, hours, minutes, seconds=0):
- _seconds = seconds
- _seconds += minutes * 60
- _seconds += hours * 60 * 60
+ def set_duration(self, hours=0, minutes=0, seconds=0, duration=''):
+ if duration:
+ _seconds = duration_to_seconds(duration)
+ else:
+ _seconds = seconds + minutes * 60 + hours * 60 * 60
self.set_duration_from_seconds(_seconds)
def set_duration_from_minutes(self, minutes):
@@ -317,3 +320,9 @@ def get_playlist_item_id(self):
def set_playlist_item_id(self, value):
self._playlist_item_id = value
+
+ def get_code(self):
+ return self._production_code
+
+ def set_code(self, value):
+ self._production_code = value or ''
diff --git a/resources/lib/youtube_plugin/kodion/ui/xbmc/info_labels.py b/resources/lib/youtube_plugin/kodion/ui/xbmc/info_labels.py
index 13601ddf2..e78d98fc4 100644
--- a/resources/lib/youtube_plugin/kodion/ui/xbmc/info_labels.py
+++ b/resources/lib/youtube_plugin/kodion/ui/xbmc/info_labels.py
@@ -38,9 +38,8 @@ def _process_audio_rating(info_labels, param):
rating = int(param)
if rating > 5:
rating = 5
- if rating < 0:
+ elif rating < 0:
rating = 0
-
info_labels['rating'] = rating
@@ -59,7 +58,7 @@ def _process_video_rating(info_labels, param):
rating = float(param)
if rating > 10.0:
rating = 10.0
- if rating < 0.0:
+ elif rating < 0.0:
rating = 0.0
info_labels['rating'] = rating
@@ -93,6 +92,11 @@ def create_from_item(base_item):
# 'date' = '1982-03-09'
_process_date(info_labels, base_item.get_date())
+ # 'count' = 12 (integer)
+ # Can be used to store an id for later, or for sorting purposes
+ # Used for video view count
+ _process_int_value(info_labels, 'count', base_item.get_count())
+
# Directory
if isinstance(base_item, DirectoryItem):
_process_string_value(info_labels, 'plot', base_item.get_plot())
@@ -160,12 +164,17 @@ def create_from_item(base_item):
# 'plot' = '...' (string)
_process_string_value(info_labels, 'plot', base_item.get_plot())
- # 'code' = 'tt3458353' (string) - imdb id
- _process_string_value(info_labels, 'code', base_item.get_imdb_id())
+ # 'imdbnumber' = 'tt3458353' (string) - imdb id
+ _process_string_value(info_labels, 'imdbnumber', base_item.get_imdb_id())
# 'cast' = [] (list)
_process_list_value(info_labels, 'cast', base_item.get_cast())
+ # 'code' = '101' (string)
+ # Production code, currently used to store misc video data for label
+ # formatting
+ _process_string_value(info_labels, 'code', base_item.get_code())
+
# Audio and Video
if isinstance(base_item, (AudioItem, VideoItem)):
# 'title' = 'Blow Your Head Off' (string)
diff --git a/resources/lib/youtube_plugin/kodion/utils/__init__.py b/resources/lib/youtube_plugin/kodion/utils/__init__.py
index 5b9e8df41..763a92912 100644
--- a/resources/lib/youtube_plugin/kodion/utils/__init__.py
+++ b/resources/lib/youtube_plugin/kodion/utils/__init__.py
@@ -12,6 +12,7 @@
from .methods import (
create_path,
create_uri_path,
+ duration_to_seconds,
find_best_fit,
find_video_id,
friendly_number,
@@ -39,6 +40,7 @@
'create_path',
'create_uri_path',
'datetime_parser',
+ 'duration_to_seconds',
'find_best_fit',
'find_video_id',
'friendly_number',
diff --git a/resources/lib/youtube_plugin/kodion/utils/methods.py b/resources/lib/youtube_plugin/kodion/utils/methods.py
index 92b695431..b5482e6dd 100644
--- a/resources/lib/youtube_plugin/kodion/utils/methods.py
+++ b/resources/lib/youtube_plugin/kodion/utils/methods.py
@@ -20,6 +20,7 @@
__all__ = (
'create_path',
'create_uri_path',
+ 'duration_to_seconds',
'find_best_fit',
'find_video_id',
'friendly_number',
@@ -266,4 +267,20 @@ def friendly_number(number, precision=3, scale=('', 'K', 'M', 'B')):
magnitude = 0 if _abs_input < 1000 else int(log(floor(_abs_input), 1000))
return '{output:f}'.format(
output=_input / 1000 ** magnitude
- ).rstrip('0').rstrip('.') + scale[magnitude]
+ ).rstrip('0').rstrip('.') + scale[magnitude], _input
+
+
+_RE_PERIODS = re.compile(r'(\d+)(d|h|m|s)')
+_SECONDS_IN_PERIODS = {
+ 's': 1, # 1 second
+ 'm': 60, # 1 minute
+ 'h': 3600, # 1 hour
+ 'd': 86400, # 1 day
+}
+
+
+def duration_to_seconds(duration):
+ return sum(
+ int(number) * _SECONDS_IN_PERIODS[period]
+ for number, period in re.findall(_RE_PERIODS, duration)
+ )
diff --git a/resources/lib/youtube_plugin/youtube/helper/utils.py b/resources/lib/youtube_plugin/youtube/helper/utils.py
index 27a2db505..fd29f4b3d 100644
--- a/resources/lib/youtube_plugin/youtube/helper/utils.py
+++ b/resources/lib/youtube_plugin/youtube/helper/utils.py
@@ -10,6 +10,7 @@
import re
import time
+from math import log10
from ...kodion import utils
from ...kodion.items import DirectoryItem
@@ -68,7 +69,7 @@ def make_comment_item(context, snippet, uri, total_replies=0):
like_count = snippet['likeCount']
if like_count:
- like_count = utils.friendly_number(like_count)
+ like_count, _ = utils.friendly_number(like_count)
color = __COLOR_MAP['likeCount']
label_likes = ui.color(color, ui.bold(like_count))
plot_likes = ui.color(color, ui.bold(' '.join((
@@ -78,7 +79,7 @@ def make_comment_item(context, snippet, uri, total_replies=0):
plot_props.append(plot_likes)
if total_replies:
- total_replies = utils.friendly_number(total_replies)
+ total_replies, _ = utils.friendly_number(total_replies)
color = __COLOR_MAP['commentCount']
label_replies = ui.color(color, ui.bold(total_replies))
plot_replies = ui.color(color, ui.bold(' '.join((
@@ -366,13 +367,15 @@ def update_video_infos(provider, context, video_id_dict,
video_item.upcoming = broadcast_type == 'upcoming'
# duration
- if not video_item.live and play_data and 'total_time' in play_data:
+ if (not (video_item.live or video_item.upcoming)
+ and play_data and 'total_time' in play_data):
duration = play_data['total_time']
else:
duration = yt_item.get('contentDetails', {}).get('duration')
if duration:
+ duration = utils.datetime_parser.parse(duration)
# subtract 1s because YouTube duration is +1s too long
- duration = utils.datetime_parser.parse(duration).seconds - 1
+ duration = (duration.seconds - 1) if duration.seconds else None
if duration:
video_item.set_duration_from_seconds(duration)
@@ -415,33 +418,45 @@ def update_video_infos(provider, context, video_id_dict,
label_stats = []
stats = []
+ rating = [0, 0]
if 'statistics' in yt_item:
for stat, value in yt_item['statistics'].items():
label = context.LOCAL_MAP.get('stats.' + stat)
if label:
color = __COLOR_MAP.get(stat, 'white')
- value = utils.friendly_number(value)
- label_stats.append(ui.color(color, value))
+ str_value, value = utils.friendly_number(value)
+ label_stats.append(ui.color(color, str_value))
stats.append(ui.color(color, ui.bold(' '.join((
- value, context.localize(label)
+ str_value, context.localize(label)
)))))
+ else:
+ continue
+ if stat == 'likeCount':
+ rating[0] = value
+ elif stat == 'viewCount':
+ rating[1] = value
+ video_item.set_count(value)
label_stats = '|'.join(label_stats)
stats = '|'.join(stats)
+ if 0 < rating[0] <= rating[1]:
+ if rating[0] == rating[1]:
+ rating = 10
+ else:
+ # This is a completely made up, arbitrary ranking score
+ rating = (10 * (log10(rating[1]) * log10(rating[0]))
+ / (log10(rating[0] + rating[1]) ** 2))
+ video_item.set_rating(rating)
# Used for label2, but is poorly supported in skins
- video_details = ' | '.join((detail for detail in (
- stats if stats else '',
- ui.italic(start_at) if start_at else '',
- ) if detail))
- video_item.set_short_details(video_details)
+ video_item.set_short_details(label_stats)
+ # Hack to force a custom label mask containing production code,
+ # activated on sort order selection, to display details
+ # Refer Provider.set_content_type for usage
+ video_item.set_code(label_stats)
# update and set the title
title = video_item.get_title() or snippet['title'] or ''
- if video_item.upcoming:
- title = ui.italic(title)
- if label_stats:
- title = '{0} ({1})'.format(title, label_stats)
- video_item.set_title(title)
+ video_item.set_title(ui.italic(title) if video_item.upcoming else title)
"""
This is experimental. We try to get the most information out of the title of a video.
diff --git a/resources/lib/youtube_plugin/youtube/provider.py b/resources/lib/youtube_plugin/youtube/provider.py
index a945f673b..8eeaab1fc 100644
--- a/resources/lib/youtube_plugin/youtube/provider.py
+++ b/resources/lib/youtube_plugin/youtube/provider.py
@@ -1441,13 +1441,20 @@ def on_root(self, context, re_match):
@staticmethod
def set_content_type(context, content_type):
context.set_content_type(content_type)
- if content_type == constants.content_type.VIDEOS:
- context.add_sort_method(constants.sort_method.UNSORTED,
- constants.sort_method.VIDEO_RUNTIME,
- constants.sort_method.DATEADDED,
- constants.sort_method.TRACKNUM,
- constants.sort_method.VIDEO_TITLE,
- constants.sort_method.DATE)
+ context.add_sort_method(
+ (constants.sort_method.UNSORTED, '%T', '%P | %J | %D'),
+ (constants.sort_method.LABEL_IGNORE_THE, '%T', '%P | %J | %D'),
+ )
+ if content_type != constants.content_type.VIDEOS:
+ return
+ context.add_sort_method(
+ (constants.sort_method.PROGRAM_COUNT, '%T \u2022 %P | %J | %D', '%C'),
+ (constants.sort_method.VIDEO_RATING, '%T \u2022 %P | %J | %D', '%R'),
+ (constants.sort_method.DATE, '%T \u2022 %P | %D', '%J'),
+ (constants.sort_method.DATEADDED, '%T \u2022 %P | %D', '%a'),
+ (constants.sort_method.VIDEO_RUNTIME, '%T \u2022 %P | %J', '%D'),
+ (constants.sort_method.TRACKNUM, '[%N. ]%T', '%P | %J | %D'),
+ )
def handle_exception(self, context, exception_to_handle):
if isinstance(exception_to_handle, (InvalidGrant, LoginException)):
From 0bb49d77d81f75bd66e6fd87771060d6699718b7 Mon Sep 17 00:00:00 2001
From: MoojMidge <56883549+MoojMidge@users.noreply.github.com>
Date: Sun, 3 Dec 2023 22:23:26 +1100
Subject: [PATCH 053/141] Fix incorrect usage of DataCache in VideoInfo
- Move class variable to Storage and access via instances
- Change parameter order for DataCache.getItem[s] and FunctionCache.get
- first param is now item/func to
- second param is now time in seconds
- Optimise imports
---
.../kodion/abstract_provider.py | 31 ++++++-----
.../youtube_plugin/kodion/utils/data_cache.py | 17 ++----
.../kodion/utils/function_cache.py | 11 +---
.../youtube_plugin/kodion/utils/storage.py | 6 +++
.../youtube_plugin/youtube/client/youtube.py | 19 ++++---
.../youtube/helper/resource_manager.py | 10 ++--
.../youtube/helper/signature/cipher.py | 8 +--
.../youtube/helper/url_resolver.py | 2 +-
.../youtube/helper/video_info.py | 54 +++++++++----------
.../youtube/helper/yt_playlist.py | 27 +++++-----
.../youtube/helper/yt_specials.py | 54 +++++++++++--------
.../lib/youtube_plugin/youtube/provider.py | 52 +++++++++---------
12 files changed, 152 insertions(+), 139 deletions(-)
diff --git a/resources/lib/youtube_plugin/kodion/abstract_provider.py b/resources/lib/youtube_plugin/kodion/abstract_provider.py
index 67aacfdc3..61fec5695 100644
--- a/resources/lib/youtube_plugin/kodion/abstract_provider.py
+++ b/resources/lib/youtube_plugin/kodion/abstract_provider.py
@@ -196,41 +196,46 @@ def data_cache(self, context):
def _internal_search(self, context, re_match):
params = context.get_params()
+ ui = context.get_ui()
command = re_match.group('command')
search_history = context.get_search_history()
if command == 'remove':
query = params['q']
search_history.remove(query)
- context.get_ui().refresh_container()
+ ui.refresh_container()
return True
if command == 'rename':
query = params['q']
- result, new_query = context.get_ui().on_keyboard_input(context.localize('search.rename'),
- query)
+ result, new_query = ui.on_keyboard_input(
+ context.localize('search.rename'), query
+ )
if result:
search_history.rename(query, new_query)
- context.get_ui().refresh_container()
+ ui.refresh_container()
return True
if command == 'clear':
search_history.clear()
- context.get_ui().refresh_container()
+ ui.refresh_container()
return True
if command == 'input':
self.data_cache = context
- folder_path = context.get_ui().get_info_label('Container.FolderPath')
+ folder_path = ui.get_info_label('Container.FolderPath')
query = None
+ # came from page 1 of search query by '..'/back
+ # user doesn't want to input on this path
if (folder_path.startswith('plugin://%s' % context.get_id()) and
re.match('.+/(?:query|input)/.*', folder_path)):
- cached_query = self.data_cache.get_item(self.data_cache.ONE_DAY, 'search_query')
- # came from page 1 of search query by '..'/back, user doesn't want to input on this path
- if cached_query and cached_query.get('search_query', {}).get('query'):
- query = cached_query.get('search_query', {}).get('query')
- query = to_unicode(query)
- query = unquote(query)
+ cached = self.data_cache.get_item('search_query',
+ self.data_cache.ONE_DAY)
+ cached = cached and cached.get('query')
+ if cached:
+ query = unquote(to_unicode(cached))
else:
- result, input_query = context.get_ui().on_keyboard_input(context.localize('search.title'))
+ result, input_query = ui.on_keyboard_input(
+ context.localize('search.title')
+ )
if result:
query = input_query
diff --git a/resources/lib/youtube_plugin/kodion/utils/data_cache.py b/resources/lib/youtube_plugin/kodion/utils/data_cache.py
index 7e986beeb..f3a29ed92 100644
--- a/resources/lib/youtube_plugin/kodion/utils/data_cache.py
+++ b/resources/lib/youtube_plugin/kodion/utils/data_cache.py
@@ -15,12 +15,6 @@
class DataCache(Storage):
- ONE_MINUTE = 60
- ONE_HOUR = 60 * ONE_MINUTE
- ONE_DAY = 24 * ONE_HOUR
- ONE_WEEK = 7 * ONE_DAY
- ONE_MONTH = 4 * ONE_WEEK
-
def __init__(self, filename, max_file_size_mb=5):
max_file_size_kb = max_file_size_mb * 1024
super(DataCache, self).__init__(filename, max_file_size_kb=max_file_size_kb)
@@ -28,7 +22,7 @@ def __init__(self, filename, max_file_size_mb=5):
def is_empty(self):
return self._is_empty()
- def get_items(self, seconds, content_ids):
+ def get_items(self, content_ids, seconds):
query_result = self._get_by_ids(content_ids, process=json.loads)
if not query_result:
return {}
@@ -41,18 +35,17 @@ def get_items(self, seconds, content_ids):
}
return result
- def get_item(self, seconds, content_id):
+ def get_item(self, content_id, seconds):
content_id = str(content_id)
query_result = self._get(content_id)
if not query_result:
- return {}
+ return None
current_time = datetime.now()
if self.get_seconds_diff(query_result[1] or current_time) > seconds:
- return {}
+ return None
- result = {content_id: json.loads(query_result[0])}
- return result
+ return json.loads(query_result[0])
def set_item(self, content_id, item):
self._set(content_id, item)
diff --git a/resources/lib/youtube_plugin/kodion/utils/function_cache.py b/resources/lib/youtube_plugin/kodion/utils/function_cache.py
index ea0b7c03f..3929878dc 100644
--- a/resources/lib/youtube_plugin/kodion/utils/function_cache.py
+++ b/resources/lib/youtube_plugin/kodion/utils/function_cache.py
@@ -15,12 +15,6 @@
class FunctionCache(Storage):
- ONE_MINUTE = 60
- ONE_HOUR = 60 * ONE_MINUTE
- ONE_DAY = 24 * ONE_HOUR
- ONE_WEEK = 7 * ONE_DAY
- ONE_MONTH = 4 * ONE_WEEK
-
def __init__(self, filename, max_file_size_mb=5):
max_file_size_kb = max_file_size_mb * 1024
super(FunctionCache, self).__init__(filename, max_file_size_kb=max_file_size_kb)
@@ -76,12 +70,11 @@ def get_cached_only(self, func, *args, **keywords):
return None
- def get(self, seconds, func, *args, **keywords):
+ def get(self, func, seconds, *args, **keywords):
"""
Returns the cached data of the given function.
- :param partial_func: function to cache
+ :param func, function to cache
:param seconds: time to live in seconds
- :param return_cached_only: return only cached data and don't call the function
:return:
"""
diff --git a/resources/lib/youtube_plugin/kodion/utils/storage.py b/resources/lib/youtube_plugin/kodion/utils/storage.py
index 00efd4983..0306ee053 100644
--- a/resources/lib/youtube_plugin/kodion/utils/storage.py
+++ b/resources/lib/youtube_plugin/kodion/utils/storage.py
@@ -20,6 +20,12 @@
class Storage(object):
+ ONE_MINUTE = 60
+ ONE_HOUR = 60 * ONE_MINUTE
+ ONE_DAY = 24 * ONE_HOUR
+ ONE_WEEK = 7 * ONE_DAY
+ ONE_MONTH = 4 * ONE_WEEK
+
_table_name = 'storage'
_clear_query = 'DELETE FROM %s' % _table_name
_create_table_query = 'CREATE TABLE IF NOT EXISTS %s (key TEXT PRIMARY KEY, time TIMESTAMP, value BLOB)' % _table_name
diff --git a/resources/lib/youtube_plugin/youtube/client/youtube.py b/resources/lib/youtube_plugin/youtube/client/youtube.py
index fdb04295c..c81a4fedd 100644
--- a/resources/lib/youtube_plugin/youtube/client/youtube.py
+++ b/resources/lib/youtube_plugin/youtube/client/youtube.py
@@ -303,16 +303,15 @@ def _get_recommendations_for_home(self):
# Do we have a cached result?
cache_home_key = 'get-activities-home'
- cached = cache.get_item(cache.ONE_HOUR * 4, cache_home_key)
- if cache_home_key in cached and cached[cache_home_key].get('items'):
- return cached[cache_home_key]
+ cached = cache.get_item(cache_home_key, cache.ONE_HOUR * 4)
+ cached = cached and cached.get('items')
+ if cached:
+ return cached
# Fetch existing list of items, if any
- items = []
cache_items_key = 'get-activities-home-items'
- cached = cache.get_item(cache.ONE_WEEK * 2, cache_items_key)
- if cache_items_key in cached:
- items = cached[cache_items_key]
+ cached = cache.get_item(cache_items_key, cache.ONE_WEEK * 2)
+ items = cached if cached else []
# Fetch history and recommended items. Use threads for faster execution.
def helper(video_id, responses):
@@ -761,9 +760,9 @@ def _perform(_page_token, _offset, _result):
# if new uploads is cached
cache_items_key = 'my-subscriptions-items'
- cached = cache.get_item(cache.ONE_HOUR, cache_items_key)
- if cache_items_key in cached:
- _result['items'] = cached[cache_items_key]
+ cached = cache.get_item(cache_items_key, cache.ONE_HOUR)
+ if cached:
+ _result['items'] = cached
""" no cache, get uploads data from web """
if not _result['items']:
diff --git a/resources/lib/youtube_plugin/youtube/helper/resource_manager.py b/resources/lib/youtube_plugin/youtube/helper/resource_manager.py
index 21896f83a..0ec5e7e3e 100644
--- a/resources/lib/youtube_plugin/youtube/helper/resource_manager.py
+++ b/resources/lib/youtube_plugin/youtube/helper/resource_manager.py
@@ -41,7 +41,9 @@ def _update_channels(self, channel_ids):
for channel_id in channel_ids:
if channel_id == 'mine':
- json_data = function_cache.get(function_cache.ONE_DAY, self._client.get_channel_by_username, channel_id)
+ json_data = function_cache.get(self._client.get_channel_by_username,
+ function_cache.ONE_DAY,
+ channel_id)
items = json_data.get('items', [{'id': 'mine'}])
try:
@@ -58,7 +60,7 @@ def _update_channels(self, channel_ids):
channel_ids = updated_channel_ids
data_cache = self._context.get_data_cache()
- channel_data = data_cache.get_items(data_cache.ONE_MONTH, channel_ids)
+ channel_data = data_cache.get_items(channel_ids, data_cache.ONE_MONTH)
channel_ids = set(channel_ids)
channel_ids_cached = set(channel_data)
@@ -92,7 +94,7 @@ def _update_channels(self, channel_ids):
def _update_videos(self, video_ids, live_details=False, suppress_errors=False):
json_data = None
data_cache = self._context.get_data_cache()
- video_data = data_cache.get_items(data_cache.ONE_MONTH, video_ids)
+ video_data = data_cache.get_items(video_ids, data_cache.ONE_MONTH)
video_ids = set(video_ids)
video_ids_cached = set(video_data)
@@ -143,7 +145,7 @@ def get_videos(self, video_ids, live_details=False, suppress_errors=False):
def _update_playlists(self, playlists_ids):
json_data = None
data_cache = self._context.get_data_cache()
- playlist_data = data_cache.get_items(data_cache.ONE_MONTH, playlists_ids)
+ playlist_data = data_cache.get_items(playlists_ids, data_cache.ONE_MONTH)
playlists_ids = set(playlists_ids)
playlists_ids_cached = set(playlist_data)
diff --git a/resources/lib/youtube_plugin/youtube/helper/signature/cipher.py b/resources/lib/youtube_plugin/youtube/helper/signature/cipher.py
index 15c7d4028..fa52385c6 100644
--- a/resources/lib/youtube_plugin/youtube/helper/signature/cipher.py
+++ b/resources/lib/youtube_plugin/youtube/helper/signature/cipher.py
@@ -10,7 +10,6 @@
import re
-from ....kodion.utils import FunctionCache
from .json_script_engine import JsonScriptEngine
@@ -24,9 +23,12 @@ def __init__(self, context, javascript):
def get_signature(self, signature):
function_cache = self._context.get_function_cache()
- json_script = function_cache.get_cached_only(self._load_javascript, self._javascript)
+ json_script = function_cache.get_cached_only(self._load_javascript,
+ self._javascript)
if not json_script:
- json_script = function_cache.get(FunctionCache.ONE_DAY, self._load_javascript, self._javascript)
+ json_script = function_cache.get(self._load_javascript,
+ function_cache.ONE_DAY,
+ self._javascript)
if json_script:
json_script_engine = JsonScriptEngine(json_script)
diff --git a/resources/lib/youtube_plugin/youtube/helper/url_resolver.py b/resources/lib/youtube_plugin/youtube/helper/url_resolver.py
index d82a1db84..bed917bd6 100644
--- a/resources/lib/youtube_plugin/youtube/helper/url_resolver.py
+++ b/resources/lib/youtube_plugin/youtube/helper/url_resolver.py
@@ -185,7 +185,7 @@ def _resolve(self, url):
return resolved_url
def resolve(self, url):
- resolved_url = self._cache.get(self._cache.ONE_DAY, self._resolve, url)
+ resolved_url = self._cache.get(self._resolve, self._cache.ONE_DAY, url)
if not resolved_url or resolved_url == '/':
return url
diff --git a/resources/lib/youtube_plugin/youtube/helper/video_info.py b/resources/lib/youtube_plugin/youtube/helper/video_info.py
index 34dd9916b..580f722b3 100644
--- a/resources/lib/youtube_plugin/youtube/helper/video_info.py
+++ b/resources/lib/youtube_plugin/youtube/helper/video_info.py
@@ -8,31 +8,31 @@
See LICENSES/GPL-2.0-only for more information.
"""
-import re
import random
+import re
import traceback
-from json import dumps as json_dumps, loads as json_loads
from html import unescape
+from json import dumps as json_dumps, loads as json_loads
from urllib.parse import (
parse_qs,
quote,
unquote,
- urlsplit,
- urlunsplit,
urlencode,
urljoin,
+ urlsplit,
+ urlunsplit,
)
import xbmcvfs
-from ..client.request_client import YouTubeRequestClient
-from ...kodion.network import is_httpd_live
-from ...kodion.utils import make_dirs, DataCache
-from ..youtube_exceptions import YouTubeException
-from .subtitles import Subtitles
from .ratebypass import ratebypass
from .signature.cipher import Cipher
+from .subtitles import Subtitles
+from ..client.request_client import YouTubeRequestClient
+from ..youtube_exceptions import YouTubeException
+from ...kodion.network import is_httpd_live
+from ...kodion.utils import make_dirs
class VideoInfo(YouTubeRequestClient):
@@ -711,13 +711,10 @@ def _get_player_config(page):
return None
def _get_player_js(self):
- cached_url = self._data_cache.get_item(
- DataCache.ONE_HOUR * 4, 'player_js_url'
- ).get('url', '')
- if cached_url not in {'', 'http://', 'https://'}:
- js_url = cached_url
- else:
- js_url = None
+ cached = self._data_cache.get_item('player_js_url',
+ self._data_cache.ONE_HOUR * 4)
+ cached = cached and cached.get('url', '')
+ js_url = cached if cached not in {'', 'http://', 'https://'} else None
if not js_url:
player_page = self._get_player_page()
@@ -739,12 +736,12 @@ def _get_player_js(self):
js_url = self._normalize_url(js_url)
self._data_cache.set_item('player_js_url', json_dumps({'url': js_url}))
- cache_key = quote(js_url)
- cached_js = self._data_cache.get_item(
- DataCache.ONE_HOUR * 4, cache_key
- ).get('js')
- if cached_js:
- return cached_js
+ js_cache_key = quote(js_url)
+ cached = self._data_cache.get_item(js_cache_key,
+ self._data_cache.ONE_HOUR * 4)
+ cached = cached and cached.get('js')
+ if cached:
+ return cached
client = self.build_client('web')
result = self.request(
@@ -756,7 +753,7 @@ def _get_player_js(self):
return ''
javascript = result.text
- self._data_cache.set_item(cache_key, json_dumps({'js': javascript}))
+ self._data_cache.set_item(js_cache_key, json_dumps({'js': javascript}))
return javascript
@staticmethod
@@ -928,9 +925,9 @@ def _process_signature_cipher(self, stream_map):
if not url or not encrypted_signature:
return None
- signature = self._data_cache.get_item(
- DataCache.ONE_HOUR * 4, encrypted_signature
- ).get('sig')
+ signature = self._data_cache.get_item(encrypted_signature,
+ self._data_cache.ONE_HOUR * 4)
+ signature = signature and signature.get('sig')
if not signature:
try:
signature = self._cipher.get_signature(encrypted_signature)
@@ -942,9 +939,8 @@ def _process_signature_cipher(self, stream_map):
'Failed to extract URL from signatureCipher'
)
return None
- self._data_cache.set_item(
- encrypted_signature, json_dumps({'sig': signature})
- )
+ self._data_cache.set_item(encrypted_signature,
+ json_dumps({'sig': signature}))
if signature:
url = '{0}&{1}={2}'.format(url, query_var, signature)
diff --git a/resources/lib/youtube_plugin/youtube/helper/yt_playlist.py b/resources/lib/youtube_plugin/youtube/helper/yt_playlist.py
index d055238d1..2ecec12be 100644
--- a/resources/lib/youtube_plugin/youtube/helper/yt_playlist.py
+++ b/resources/lib/youtube_plugin/youtube/helper/yt_playlist.py
@@ -8,8 +8,6 @@
See LICENSES/GPL-2.0-only for more information.
"""
-from ...kodion.utils.function_cache import FunctionCache
-
from ... import kodion
from ...youtube.helper import v3
@@ -135,9 +133,9 @@ def _process_remove_playlist(provider, context):
def _process_select_playlist(provider, context):
- listitem_path = context.get_ui().get_info_label('Container.ListItem(0).FileNameAndPath') # do this asap, relies on listitems focus
- keymap_action = False
ui = context.get_ui()
+ listitem_path = ui.get_info_label('Container.ListItem(0).FileNameAndPath') # do this asap, relies on listitems focus
+ keymap_action = False
page_token = ''
current_page = 0
@@ -151,16 +149,19 @@ def _process_select_playlist(provider, context):
if not video_id:
raise kodion.KodionException('Playlist/Select: missing video_id')
+ function_cache = context.get_function_cache()
+ client = provider.get_client(context)
while True:
current_page += 1
if not page_token:
- json_data = context.get_function_cache().get((FunctionCache.ONE_MINUTE // 3),
- provider.get_client(context).get_playlists_of_channel,
- channel_id='mine')
+ json_data = function_cache.get(client.get_playlists_of_channel,
+ function_cache.ONE_MINUTE // 3,
+ channel_id='mine')
else:
- json_data = context.get_function_cache().get((FunctionCache.ONE_MINUTE // 3),
- provider.get_client(context).get_playlists_of_channel,
- channel_id='mine', page_token=page_token)
+ json_data = function_cache.get(client.get_playlists_of_channel,
+ function_cache.ONE_MINUTE // 3,
+ channel_id='mine',
+ page_token=page_token)
playlists = json_data.get('items', [])
page_token = json_data.get('nextPageToken', False)
@@ -193,12 +194,12 @@ def _process_select_playlist(provider, context):
items.append((ui.bold(context.localize('next_page')).replace('%d', str(current_page + 1)), '',
'playlist.next', 'DefaultFolder.png'))
- result = context.get_ui().on_select(context.localize('playlist.select'), items)
+ result = ui.on_select(context.localize('playlist.select'), items)
if result == 'playlist.create':
- result, text = context.get_ui().on_keyboard_input(
+ result, text = ui.on_keyboard_input(
context.localize('playlist.create'))
if result and text:
- json_data = provider.get_client(context).create_playlist(title=text)
+ json_data = client.create_playlist(title=text)
if not v3.handle_error(context, json_data):
break
diff --git a/resources/lib/youtube_plugin/youtube/helper/yt_specials.py b/resources/lib/youtube_plugin/youtube/helper/yt_specials.py
index 4cfff9f26..77f9bfe72 100644
--- a/resources/lib/youtube_plugin/youtube/helper/yt_specials.py
+++ b/resources/lib/youtube_plugin/youtube/helper/yt_specials.py
@@ -8,14 +8,21 @@
See LICENSES/GPL-2.0-only for more information.
"""
-from ... import kodion
-from ...kodion.items import DirectoryItem, UriItem
-from ...youtube.helper import v3, tv, extract_urls, UrlResolver, UrlToItemConverter
from . import utils
+from ...kodion import KodionException, constants
+from ...kodion.items import DirectoryItem, UriItem
+from ...kodion.utils import strip_html_from_text
+from ...youtube.helper import (
+ UrlResolver,
+ UrlToItemConverter,
+ extract_urls,
+ tv,
+ v3,
+)
def _process_related_videos(provider, context):
- provider.set_content_type(context, kodion.constants.content_type.VIDEOS)
+ provider.set_content_type(context, constants.content_type.VIDEOS)
result = []
page_token = context.get_param('page_token', '')
@@ -30,7 +37,7 @@ def _process_related_videos(provider, context):
def _process_parent_comments(provider, context):
- provider.set_content_type(context, kodion.constants.content_type.FILES)
+ provider.set_content_type(context, constants.content_type.FILES)
result = []
page_token = context.get_param('page_token', '')
@@ -45,7 +52,7 @@ def _process_parent_comments(provider, context):
def _process_child_comments(provider, context):
- provider.set_content_type(context, kodion.constants.content_type.FILES)
+ provider.set_content_type(context, constants.content_type.FILES)
result = []
page_token = context.get_param('page_token', '')
@@ -60,7 +67,7 @@ def _process_child_comments(provider, context):
def _process_recommendations(provider, context):
- provider.set_content_type(context, kodion.constants.content_type.VIDEOS)
+ provider.set_content_type(context, constants.content_type.VIDEOS)
result = []
page_token = context.get_param('page_token', '')
@@ -72,7 +79,7 @@ def _process_recommendations(provider, context):
def _process_popular_right_now(provider, context):
- provider.set_content_type(context, kodion.constants.content_type.VIDEOS)
+ provider.set_content_type(context, constants.content_type.VIDEOS)
result = []
page_token = context.get_param('page_token', '')
@@ -85,7 +92,7 @@ def _process_popular_right_now(provider, context):
def _process_browse_channels(provider, context):
- provider.set_content_type(context, kodion.constants.content_type.FILES)
+ provider.set_content_type(context, constants.content_type.FILES)
result = []
# page_token = context.get_param('page_token', '')
@@ -98,7 +105,9 @@ def _process_browse_channels(provider, context):
return False
result.extend(v3.response_to_items(provider, context, json_data))
else:
- json_data = context.get_function_cache().get(kodion.utils.FunctionCache.ONE_MONTH, client.get_guide_categories)
+ function_cache = context.get_function_cache()
+ json_data = function_cache.get(client.get_guide_categories,
+ function_cache.ONE_MONTH)
if not v3.handle_error(context, json_data):
return False
result.extend(v3.response_to_items(provider, context, json_data))
@@ -107,7 +116,7 @@ def _process_browse_channels(provider, context):
def _process_disliked_videos(provider, context):
- provider.set_content_type(context, kodion.constants.content_type.VIDEOS)
+ provider.set_content_type(context, constants.content_type.VIDEOS)
result = []
page_token = context.get_param('page_token', '')
@@ -122,7 +131,7 @@ def _process_live_events(provider, context, event_type='live'):
def _sort(x):
return x.get_aired()
- provider.set_content_type(context, kodion.constants.content_type.VIDEOS)
+ provider.set_content_type(context, constants.content_type.VIDEOS)
result = []
# TODO: cache result
@@ -142,7 +151,7 @@ def _process_description_links(provider, context):
addon_id = context.get_param('addon_id', '')
def _extract_urls(_video_id):
- provider.set_content_type(context, kodion.constants.content_type.VIDEOS)
+ provider.set_content_type(context, constants.content_type.VIDEOS)
url_resolver = UrlResolver(context)
result = []
@@ -156,9 +165,12 @@ def _extract_urls(_video_id):
video_data = resource_manager.get_videos([_video_id])
yt_item = video_data[_video_id]
snippet = yt_item['snippet'] # crash if not conform
- description = kodion.utils.strip_html_from_text(snippet['description'])
+ description = strip_html_from_text(snippet['description'])
- urls = context.get_function_cache().get(kodion.utils.FunctionCache.ONE_WEEK, extract_urls, description)
+ function_cache = context.get_function_cache()
+ urls = function_cache.get(extract_urls,
+ function_cache.ONE_WEEK,
+ description)
progress_dialog.set_total(len(urls))
@@ -261,7 +273,7 @@ def _display_playlists(_playlist_ids):
def _process_saved_playlists_tv(provider, context):
- provider.set_content_type(context, kodion.constants.content_type.FILES)
+ provider.set_content_type(context, constants.content_type.FILES)
result = []
next_page_token = context.get_param('next_page_token', '')
@@ -273,7 +285,7 @@ def _process_saved_playlists_tv(provider, context):
def _process_watch_history_tv(provider, context):
- provider.set_content_type(context, kodion.constants.content_type.VIDEOS)
+ provider.set_content_type(context, constants.content_type.VIDEOS)
result = []
next_page_token = context.get_param('next_page_token', '')
@@ -285,7 +297,7 @@ def _process_watch_history_tv(provider, context):
def _process_purchases_tv(provider, context):
- provider.set_content_type(context, kodion.constants.content_type.VIDEOS)
+ provider.set_content_type(context, constants.content_type.VIDEOS)
result = []
next_page_token = context.get_param('next_page_token', '')
@@ -297,7 +309,7 @@ def _process_purchases_tv(provider, context):
def _process_new_uploaded_videos_tv(provider, context):
- provider.set_content_type(context, kodion.constants.content_type.VIDEOS)
+ provider.set_content_type(context, constants.content_type.VIDEOS)
result = []
next_page_token = context.get_param('next_page_token', '')
@@ -309,7 +321,7 @@ def _process_new_uploaded_videos_tv(provider, context):
def _process_new_uploaded_videos_tv_filtered(provider, context):
- provider.set_content_type(context, kodion.constants.content_type.VIDEOS)
+ provider.set_content_type(context, constants.content_type.VIDEOS)
result = []
next_page_token = context.get_param('next_page_token', '')
@@ -353,4 +365,4 @@ def process(category, provider, context):
return _process_child_comments(provider, context)
if category == 'saved_playlists':
return _process_saved_playlists_tv(provider, context)
- raise kodion.KodionException("YouTube special category '%s' not found" % category)
+ raise KodionException("YouTube special category '%s' not found" % category)
diff --git a/resources/lib/youtube_plugin/youtube/provider.py b/resources/lib/youtube_plugin/youtube/provider.py
index 8eeaab1fc..a7c75ebfc 100644
--- a/resources/lib/youtube_plugin/youtube/provider.py
+++ b/resources/lib/youtube_plugin/youtube/provider.py
@@ -15,7 +15,13 @@
import socket
from base64 import b64decode
+import xbmcaddon
+import xbmcvfs
+
from .helper import (
+ ResourceManager,
+ UrlResolver,
+ UrlToItemConverter,
v3,
yt_context_menu,
yt_login,
@@ -25,27 +31,15 @@
yt_setup_wizard,
yt_specials,
yt_video,
- ResourceManager,
- UrlResolver,
- UrlToItemConverter,
)
from .youtube_exceptions import InvalidGrant, LoginException
-from ..kodion import (
- constants,
- AbstractProvider,
- RegisterProviderPath,
-)
-from ..youtube.client import YouTube
+from ..kodion import (AbstractProvider, RegisterProviderPath, constants)
from ..kodion.items import DirectoryItem, NewSearchItem, SearchItem
from ..kodion.network import get_client_ip_address, is_httpd_live
-from ..kodion.utils import find_video_id, strip_html_from_text, FunctionCache
+from ..kodion.utils import find_video_id, strip_html_from_text
+from ..youtube.client import YouTube
from ..youtube.helper import yt_subscriptions
-import xbmcaddon
-import xbmcvfs
-import xbmcgui
-import xbmcplugin
-
class Provider(AbstractProvider):
def __init__(self):
@@ -397,6 +391,7 @@ def _on_channel_live(self, context, re_match):
@RegisterProviderPath('^/(?P(channel|user))/(?P[^/]+)/$')
def _on_channel(self, context, re_match):
+ client = self.get_client(context)
localize = context.localize
create_path = context.create_resource_path
create_uri = context.create_uri
@@ -424,14 +419,15 @@ def _on_channel(self, context, re_match):
result = []
"""
- This is a helper routine if we only have the username of a channel. This will retrieve the correct channel id
- based on the username.
+ This is a helper routine if we only have the username of a channel.
+ This will retrieve the correct channel id based on the username.
"""
if method == 'user' or channel_id == 'mine':
context.log_debug('Trying to get channel id for user "%s"' % channel_id)
- json_data = function_cache.get(FunctionCache.ONE_DAY,
- self.get_client(context).get_channel_by_username, channel_id)
+ json_data = function_cache.get(client.get_channel_by_username,
+ function_cache.ONE_DAY,
+ channel_id)
if not v3.handle_error(context, json_data):
return False
@@ -490,8 +486,9 @@ def _on_channel(self, context, re_match):
playlists = resource_manager.get_related_playlists(channel_id)
upload_playlist = playlists.get('uploads', '')
if upload_playlist:
- json_data = function_cache.get(FunctionCache.ONE_MINUTE * 5,
- self.get_client(context).get_playlist_items, upload_playlist,
+ json_data = function_cache.get(client.get_playlist_items,
+ function_cache.ONE_MINUTE * 5,
+ upload_playlist,
page_token=page_token)
if not v3.handle_error(context, json_data):
return False
@@ -887,9 +884,16 @@ def on_search(self, search_text, context, re_match):
live_item.set_fanart(self.get_fanart(context))
result.append(live_item)
- json_data = context.get_function_cache().get(FunctionCache.ONE_MINUTE * 10, self.get_client(context).search,
- q=search_text, search_type=search_type, event_type=event_type,
- safe_search=safe_search, page_token=page_token, channel_id=channel_id, location=location)
+ function_cache = context.get_function_cache()
+ json_data = function_cache.get(self.get_client(context).search,
+ function_cache.ONE_MINUTE * 10,
+ q=search_text,
+ search_type=search_type,
+ event_type=event_type,
+ safe_search=safe_search,
+ page_token=page_token,
+ channel_id=channel_id,
+ location=location)
if not v3.handle_error(context, json_data):
return False
result.extend(v3.response_to_items(self, context, json_data))
From e16c7a0581aec2447da7898ecc29b570aff23c65 Mon Sep 17 00:00:00 2001
From: MoojMidge <56883549+MoojMidge@users.noreply.github.com>
Date: Mon, 4 Dec 2023 11:58:15 +1100
Subject: [PATCH 054/141] Rationalise item date and duration methods
- use datetime objects for setting rather than parsing back and forth
- date and dateadded values stored as datetime rather than str
- date and dateadded getter can retrieve value as datetime, date str or datetime.isoformat
- aired and premiered values stored as date rather than st
- aired and premiered getter can retrieve value as date, or date.isoformat
- datetime_parser.parse will always create a datetime object rather than a date object
- the caller can determine whether datetime.date() needs to be used after that
- duration getter can optionally retrieve value as hh:mm:ss string
- listitem label2 now uses the same data as default label mask including date and duration
---
.../youtube_plugin/kodion/items/base_item.py | 51 ++++++++++---------
.../youtube_plugin/kodion/items/video_item.py | 46 +++++++++--------
.../kodion/ui/xbmc/info_labels.py | 27 +++++-----
.../kodion/ui/xbmc/xbmc_items.py | 33 +++++++++---
.../youtube_plugin/kodion/utils/__init__.py | 2 +
.../kodion/utils/datetime_parser.py | 6 +--
.../youtube_plugin/kodion/utils/methods.py | 11 ++++
.../kodion/utils/watch_later_list.py | 3 +-
8 files changed, 107 insertions(+), 72 deletions(-)
diff --git a/resources/lib/youtube_plugin/kodion/items/base_item.py b/resources/lib/youtube_plugin/kodion/items/base_item.py
index 92d70650a..49da304e7 100644
--- a/resources/lib/youtube_plugin/kodion/items/base_item.py
+++ b/resources/lib/youtube_plugin/kodion/items/base_item.py
@@ -104,41 +104,44 @@ def replace_context_menu(self):
return self._replace_context_menu
def set_date(self, year, month, day, hour=0, minute=0, second=0):
- date = datetime.datetime(year, month, day, hour, minute, second)
- self._date = date.isoformat(sep=' ')
+ self._date = datetime.datetime(year, month, day, hour, minute, second)
def set_date_from_datetime(self, date_time):
- self.set_date(year=date_time.year,
- month=date_time.month,
- day=date_time.day,
- hour=date_time.hour,
- minute=date_time.minute,
- second=date_time.second)
+ self._date = date_time
+
+ def get_date(self, as_text=True, short=False):
+ if not self._date:
+ return ''
+ if short:
+ return self._date.date().isoformat()
+ if as_text:
+ return self._date.isoformat(sep=' ')
+ return self._date
def set_dateadded(self, year, month, day, hour=0, minute=0, second=0):
- date = datetime.datetime(year, month, day, hour, minute, second)
- self._dateadded = date.isoformat(sep=' ')
+ self._dateadded = datetime.datetime(year,
+ month,
+ day,
+ hour,
+ minute,
+ second)
def set_dateadded_from_datetime(self, date_time):
- self.set_dateadded(year=date_time.year,
- month=date_time.month,
- day=date_time.day,
- hour=date_time.hour,
- minute=date_time.minute,
- second=date_time.second)
+ self._dateadded = date_time
+
+ def get_dateadded(self, as_text=True):
+ if not self._dateadded:
+ return ''
+ if as_text:
+ return self._dateadded.isoformat(sep=' ')
+ return self._dateadded
- def set_added_utc(self, dt):
- self._added_utc = dt
+ def set_added_utc(self, date_time):
+ self._added_utc = date_time
def get_added_utc(self):
return self._added_utc
- def get_date(self):
- return self._date
-
- def get_dateadded(self):
- return self._dateadded
-
def get_short_details(self):
return self._short_details
diff --git a/resources/lib/youtube_plugin/kodion/items/video_item.py b/resources/lib/youtube_plugin/kodion/items/video_item.py
index ba80baf5a..35521f098 100644
--- a/resources/lib/youtube_plugin/kodion/items/video_item.py
+++ b/resources/lib/youtube_plugin/kodion/items/video_item.py
@@ -13,7 +13,7 @@
from html import unescape
from .base_item import BaseItem
-from ..utils import duration_to_seconds
+from ..utils import duration_to_seconds, seconds_to_duration
__RE_IMDB__ = re.compile(r'(http(s)?://)?www.imdb.(com|de)/title/(?P[t0-9]+)(/)?')
@@ -107,15 +107,16 @@ def get_year(self):
return self._year
def set_premiered(self, year, month, day):
- date = datetime.date(year, month, day)
- self._premiered = date.isoformat()
+ self._premiered = datetime.date(year, month, day)
def set_premiered_from_datetime(self, date_time):
- self.set_premiered(year=date_time.year,
- month=date_time.month,
- day=date_time.day)
+ self._premiered = date_time.date()
- def get_premiered(self):
+ def get_premiered(self, as_text=True):
+ if not self._premiered:
+ return ''
+ if as_text:
+ return self._premiered.isoformat()
return self._premiered
def set_plot(self, plot):
@@ -175,29 +176,35 @@ def set_duration(self, hours=0, minutes=0, seconds=0, duration=''):
if duration:
_seconds = duration_to_seconds(duration)
else:
- _seconds = seconds + minutes * 60 + hours * 60 * 60
- self.set_duration_from_seconds(_seconds)
+ _seconds = seconds + minutes * 60 + hours * 3600
+ self._duration = _seconds or 0
def set_duration_from_minutes(self, minutes):
- self.set_duration_from_seconds(int(minutes) * 60)
+ self._duration = int(minutes) * 60
def set_duration_from_seconds(self, seconds):
self._duration = int(seconds or 0)
- def get_duration(self):
+ def get_duration(self, as_text=False):
+ if as_text:
+ return seconds_to_duration(self._duration)
return self._duration
def set_aired(self, year, month, day):
- date = datetime.date(year, month, day)
- self._aired = date.isoformat()
+ self._aired = datetime.date(year, month, day)
def set_aired_from_datetime(self, date_time):
- self.set_aired(year=date_time.year,
- month=date_time.month,
- day=date_time.day)
+ self._aired = date_time.date()
- def set_scheduled_start_utc(self, dt):
- self._scheduled_start_utc = dt
+ def get_aired(self, as_text=True):
+ if not self._aired:
+ return ''
+ if as_text:
+ return self._aired.isoformat()
+ return self._aired
+
+ def set_scheduled_start_utc(self, date_time):
+ self._scheduled_start_utc = date_time
def get_scheduled_start_utc(self):
return self._scheduled_start_utc
@@ -218,9 +225,6 @@ def upcoming(self):
def upcoming(self, value):
self._upcoming = value
- def get_aired(self):
- return self._aired
-
def set_genre(self, genre):
self._genre = genre
diff --git a/resources/lib/youtube_plugin/kodion/ui/xbmc/info_labels.py b/resources/lib/youtube_plugin/kodion/ui/xbmc/info_labels.py
index e78d98fc4..767f34d5a 100644
--- a/resources/lib/youtube_plugin/kodion/ui/xbmc/info_labels.py
+++ b/resources/lib/youtube_plugin/kodion/ui/xbmc/info_labels.py
@@ -12,10 +12,14 @@
from ...items import AudioItem, DirectoryItem, ImageItem, VideoItem
-def _process_date(info_labels, param):
+def _process_date_value(info_labels, name, param):
+ if param:
+ info_labels[name] = param.isoformat()
+
+
+def _process_datetime_value(info_labels, name, param):
if param:
- datetime = utils.datetime_parser.parse(param)
- info_labels['date'] = datetime.isoformat()
+ info_labels[name] = param.isoformat(' ')
def _process_int_value(info_labels, name, param):
@@ -43,11 +47,6 @@ def _process_audio_rating(info_labels, param):
info_labels['rating'] = rating
-def _process_video_dateadded(info_labels, param):
- if param is not None and param:
- info_labels['dateadded'] = param
-
-
def _process_video_duration(info_labels, param):
if param is not None:
info_labels['duration'] = '%d' % param
@@ -63,7 +62,7 @@ def _process_video_rating(info_labels, param):
info_labels['rating'] = rating
-def _process_date_value(info_labels, name, param):
+def _process_date_string(info_labels, name, param):
if param:
date = utils.datetime_parser.parse(param)
info_labels[name] = date.isoformat()
@@ -89,8 +88,8 @@ def _process_last_played(info_labels, name, param):
def create_from_item(base_item):
info_labels = {}
- # 'date' = '1982-03-09'
- _process_date(info_labels, base_item.get_date())
+ # 'date' = '1982-03-09' (string)
+ _process_datetime_value(info_labels, 'date', base_item.get_date(as_text=False))
# 'count' = 12 (integer)
# Can be used to store an id for later, or for sorting purposes
@@ -135,7 +134,7 @@ def create_from_item(base_item):
_process_list_value(info_labels, 'artist', base_item.get_artist())
# 'dateadded' = '2014-08-11 13:08:56' (string) will be taken from 'dateadded'
- _process_video_dateadded(info_labels, base_item.get_dateadded())
+ _process_datetime_value(info_labels, 'dateadded', base_item.get_dateadded(as_text=False))
# TODO: starting with Helix this could be seconds
# 'duration' = '3:18' (string)
@@ -147,13 +146,13 @@ def create_from_item(base_item):
_process_video_rating(info_labels, base_item.get_rating())
# 'aired' = '2013-12-12' (string)
- _process_date_value(info_labels, 'aired', base_item.get_aired())
+ _process_date_value(info_labels, 'aired', base_item.get_aired(as_text=False))
# 'director' = 'Steven Spielberg' (string)
_process_string_value(info_labels, 'director', base_item.get_director())
# 'premiered' = '2013-12-12' (string)
- _process_date_value(info_labels, 'premiered', base_item.get_premiered())
+ _process_date_value(info_labels, 'premiered', base_item.get_premiered(as_text=False))
# 'episode' = 12 (int)
_process_int_value(info_labels, 'episode', base_item.get_episode())
diff --git a/resources/lib/youtube_plugin/kodion/ui/xbmc/xbmc_items.py b/resources/lib/youtube_plugin/kodion/ui/xbmc/xbmc_items.py
index 95c51a123..01b73f171 100644
--- a/resources/lib/youtube_plugin/kodion/ui/xbmc/xbmc_items.py
+++ b/resources/lib/youtube_plugin/kodion/ui/xbmc/xbmc_items.py
@@ -54,13 +54,24 @@ def video_playback_item(context, video_item):
is_strm = context.get_param('strm')
mime_type = None
- kwargs = {
- 'label': (None if is_strm
- else (video_item.get_title() or video_item.get_name())),
- 'label2': None if is_strm else video_item.get_short_details(),
- 'path': uri,
- 'offscreen': True,
- }
+ if is_strm:
+ kwargs = {
+ 'path': uri,
+ 'offscreen': True,
+ }
+ else:
+ kwargs = {
+ 'label': video_item.get_title() or video_item.get_name(),
+ 'label2': ' | '.join((part
+ for part in (
+ video_item.get_code(),
+ video_item.get_date(short=True),
+ video_item.get_duration(as_text=True),
+ )
+ if part)),
+ 'path': uri,
+ 'offscreen': True,
+ }
props = {
'isPlayable': str(video_item.playable).lower(),
}
@@ -193,7 +204,13 @@ def video_listitem(context, video_item):
kwargs = {
'label': video_item.get_title() or video_item.get_name(),
- 'label2': video_item.get_short_details(),
+ 'label2': ' | '.join((part
+ for part in (
+ video_item.get_code(),
+ video_item.get_date(short=True),
+ video_item.get_duration(as_text=True),
+ )
+ if part)),
'path': uri,
'offscreen': True,
}
diff --git a/resources/lib/youtube_plugin/kodion/utils/__init__.py b/resources/lib/youtube_plugin/kodion/utils/__init__.py
index 763a92912..0cd6e0e08 100644
--- a/resources/lib/youtube_plugin/kodion/utils/__init__.py
+++ b/resources/lib/youtube_plugin/kodion/utils/__init__.py
@@ -18,6 +18,7 @@
friendly_number,
loose_version,
make_dirs,
+ seconds_to_duration,
select_stream,
strip_html_from_text,
to_str,
@@ -46,6 +47,7 @@
'friendly_number',
'loose_version',
'make_dirs',
+ 'seconds_to_duration',
'select_stream',
'strip_html_from_text',
'to_str',
diff --git a/resources/lib/youtube_plugin/kodion/utils/datetime_parser.py b/resources/lib/youtube_plugin/kodion/utils/datetime_parser.py
index a9d0cb29d..1f6c4ea33 100644
--- a/resources/lib/youtube_plugin/kodion/utils/datetime_parser.py
+++ b/resources/lib/youtube_plugin/kodion/utils/datetime_parser.py
@@ -55,9 +55,9 @@ def _to_int(value):
date_only_match = __RE_MATCH_DATE_ONLY__.match(datetime_string)
if date_only_match:
return utc_to_local(
- dt=date(_to_int(date_only_match.group('year')),
- _to_int(date_only_match.group('month')),
- _to_int(date_only_match.group('day'))),
+ dt=datetime(_to_int(date_only_match.group('year')),
+ _to_int(date_only_match.group('month')),
+ _to_int(date_only_match.group('day'))),
offset=offset
)
diff --git a/resources/lib/youtube_plugin/kodion/utils/methods.py b/resources/lib/youtube_plugin/kodion/utils/methods.py
index b5482e6dd..e17d50b42 100644
--- a/resources/lib/youtube_plugin/kodion/utils/methods.py
+++ b/resources/lib/youtube_plugin/kodion/utils/methods.py
@@ -11,6 +11,7 @@
import os
import copy
import re
+from datetime import timedelta
from math import floor, log
from urllib.parse import quote
@@ -27,6 +28,7 @@
'loose_version',
'make_dirs',
'print_items',
+ 'seconds_to_duration',
'select_stream',
'strip_html_from_text',
'to_str',
@@ -280,7 +282,16 @@ def friendly_number(number, precision=3, scale=('', 'K', 'M', 'B')):
def duration_to_seconds(duration):
+ if ':' in duration:
+ seconds = 0
+ for part in duration.split(':'):
+ seconds = seconds * 60 + int(part, 10)
+ return seconds
return sum(
int(number) * _SECONDS_IN_PERIODS[period]
for number, period in re.findall(_RE_PERIODS, duration)
)
+
+
+def seconds_to_duration(seconds):
+ return str(timedelta(seconds=seconds))
diff --git a/resources/lib/youtube_plugin/kodion/utils/watch_later_list.py b/resources/lib/youtube_plugin/kodion/utils/watch_later_list.py
index e6a570960..20425d513 100644
--- a/resources/lib/youtube_plugin/kodion/utils/watch_later_list.py
+++ b/resources/lib/youtube_plugin/kodion/utils/watch_later_list.py
@@ -30,8 +30,7 @@ def get_items(self):
return sorted(result, key=self._sort_item, reverse=False)
def add(self, base_item):
- now = datetime.datetime.now()
- base_item.set_date(now.year, now.month, now.day, now.hour, now.minute, now.second)
+ base_item.set_date_from_datetime(datetime.datetime.now())
item_json_data = items.to_json(base_item)
self._set(base_item.get_id(), item_json_data)
From d11f9579449f3d6b4553755e9c8753abd055984e Mon Sep 17 00:00:00 2001
From: MoojMidge <56883549+MoojMidge@users.noreply.github.com>
Date: Tue, 5 Dec 2023 12:44:34 +1100
Subject: [PATCH 055/141] Add support for start and end parameters
---
.../kodion/context/abstract_context.py | 2 +
.../kodion/ui/xbmc/xbmc_items.py | 6 +-
.../lib/youtube_plugin/kodion/utils/player.py | 65 +++++++++++++------
.../youtube_plugin/youtube/helper/yt_play.py | 41 ++++++------
4 files changed, 74 insertions(+), 40 deletions(-)
diff --git a/resources/lib/youtube_plugin/kodion/context/abstract_context.py b/resources/lib/youtube_plugin/kodion/context/abstract_context.py
index fc2595f18..d005f9c97 100644
--- a/resources/lib/youtube_plugin/kodion/context/abstract_context.py
+++ b/resources/lib/youtube_plugin/kodion/context/abstract_context.py
@@ -55,6 +55,8 @@ class AbstractContext(object):
}
_FLOAT_PARAMS = {
'seek',
+ 'start',
+ 'end'
}
_LIST_PARAMS = {
'channel_ids',
diff --git a/resources/lib/youtube_plugin/kodion/ui/xbmc/xbmc_items.py b/resources/lib/youtube_plugin/kodion/ui/xbmc/xbmc_items.py
index 01b73f171..0799f54cf 100644
--- a/resources/lib/youtube_plugin/kodion/ui/xbmc/xbmc_items.py
+++ b/resources/lib/youtube_plugin/kodion/ui/xbmc/xbmc_items.py
@@ -127,7 +127,11 @@ def video_playback_item(context, video_item):
return list_item
if not context.get_param('resume'):
- if 'ResumeTime' in props:
+ if context.get_param('start'):
+ prop_value = video_item.get_start_time()
+ if prop_value:
+ props['ResumeTime'] = prop_value
+ elif 'ResumeTime' in props:
del props['ResumeTime']
prop_value = video_item.get_duration()
diff --git a/resources/lib/youtube_plugin/kodion/utils/player.py b/resources/lib/youtube_plugin/kodion/utils/player.py
index de21377ea..8b77302ca 100644
--- a/resources/lib/youtube_plugin/kodion/utils/player.py
+++ b/resources/lib/youtube_plugin/kodion/utils/player.py
@@ -15,7 +15,7 @@
class PlaybackMonitorThread(threading.Thread):
- def __init__(self, provider, context, playback_json):
+ def __init__(self, player, provider, context, playback_json):
super(PlaybackMonitorThread, self).__init__()
self._stopped = threading.Event()
@@ -25,7 +25,7 @@ def __init__(self, provider, context, playback_json):
self.provider = provider
self.ui = self.context.get_ui()
- self.player = xbmc.Player()
+ self.player = player
self.playback_json = playback_json
self.video_id = self.playback_json.get('video_id')
@@ -56,10 +56,6 @@ def run(self):
use_local_history = self.playback_json.get('use_local_history', False)
playback_stats = self.playback_json.get('playback_stats')
refresh_only = self.playback_json.get('refresh_only', False)
- try:
- seek_time = float(self.playback_json.get('seek_time'))
- except (ValueError, TypeError):
- seek_time = None
player = self.player
@@ -160,14 +156,20 @@ def run(self):
self.update_times(last_total_time, last_current_time, last_segment_start, last_percent_complete)
break
- if seek_time:
- player.seekTime(seek_time)
- try:
- self.current_time = float(player.getTime())
- except RuntimeError:
- pass
- if self.current_time >= seek_time:
- seek_time = None
+ if player._start_time or player._seek_time:
+ _seek_time = player._start_time or player._seek_time
+ if self.current_time < _seek_time:
+ player.seekTime(_seek_time)
+ try:
+ self.current_time = float(player.getTime())
+ except RuntimeError:
+ pass
+
+ if player._end_time and self.current_time >= player._end_time:
+ if player._start_time:
+ player.seekTime(player._start_time)
+ else:
+ player.stop()
if self.abort_now():
self.update_times(last_total_time, last_current_time, last_segment_start, last_percent_complete)
@@ -359,6 +361,9 @@ def __init__(self, *args, **kwargs):
self.provider = kwargs.get('provider')
self.ui = self.context.get_ui()
self.threads = []
+ self._seek_time = None
+ self._start_time = None
+ self._end_time = None
def stop_threads(self):
for thread in self.threads:
@@ -400,11 +405,25 @@ def cleanup_threads(self, only_ended=True):
def onPlayBackStarted(self):
playback_json = self.ui.get_property('playback_json')
- if playback_json:
- playback_json = json.loads(playback_json)
- self.ui.clear_property('playback_json')
- self.cleanup_threads()
- self.threads.append(PlaybackMonitorThread(self.provider, self.context, playback_json))
+ if not playback_json:
+ return
+
+ playback_json = json.loads(playback_json)
+ try:
+ self._seek_time = float(playback_json.get('seek_time'))
+ self._start_time = float(playback_json.get('start_time'))
+ self._end_time = float(playback_json.get('end_time'))
+ except (ValueError, TypeError):
+ self._seek_time = None
+ self._start_time = None
+ self._end_time = None
+
+ self.ui.clear_property('playback_json')
+ self.cleanup_threads()
+ self.threads.append(PlaybackMonitorThread(self,
+ self.provider,
+ self.context,
+ playback_json))
def onPlayBackEnded(self):
self.stop_threads()
@@ -415,3 +434,11 @@ def onPlayBackStopped(self):
def onPlayBackError(self):
self.onPlayBackEnded()
+
+ def onPlayBackSeek(self, time, seekOffset):
+ time_s = time // 1000
+ self._seek_time = None
+ if ((self._end_time and time_s > self._end_time)
+ or (self._start_time and time_s < self._start_time)):
+ self._start_time = None
+ self._end_time = None
diff --git a/resources/lib/youtube_plugin/youtube/helper/yt_play.py b/resources/lib/youtube_plugin/youtube/helper/yt_play.py
index 1df2af008..ea7daf631 100644
--- a/resources/lib/youtube_plugin/youtube/helper/yt_play.py
+++ b/resources/lib/youtube_plugin/youtube/helper/yt_play.py
@@ -93,30 +93,31 @@ def play_video(provider, context):
utils.update_play_info(provider, context, video_id, video_item,
video_stream, use_play_data=use_play_data)
- seek_time = 0.0
- play_count = 0
- playback_stats = video_stream.get('playback_stats')
+ seek_time = 0.0 if params.get('resume') else params.get('seek', 0.0)
+ start_time = params.get('start', 0.0)
+ end_time = params.get('end', 0.0)
- if not params.get('resume'):
- try:
- seek_time = params.get('seek', 0.0)
- except (ValueError, TypeError):
- pass
+ if start_time:
+ video_item.set_start_time(start_time)
+ if end_time:
+ video_item.set_duration_from_seconds(end_time)
- if use_play_data:
- play_count = video_item.get_play_count() or 0
+ play_count = use_play_data and video_item.get_play_count() or 0
+ playback_stats = video_stream.get('playback_stats')
playback_json = {
- "video_id": video_id,
- "channel_id": metadata.get('channel', {}).get('id', ''),
- "video_status": metadata.get('video', {}).get('status', {}),
- "playing_file": video_item.get_uri(),
- "play_count": play_count,
- "use_remote_history": use_remote_history,
- "use_local_history": use_play_data,
- "playback_stats": playback_stats,
- "seek_time": seek_time,
- "refresh_only": screensaver
+ 'video_id': video_id,
+ 'channel_id': metadata.get('channel', {}).get('id', ''),
+ 'video_status': metadata.get('video', {}).get('status', {}),
+ 'playing_file': video_item.get_uri(),
+ 'play_count': play_count,
+ 'use_remote_history': use_remote_history,
+ 'use_local_history': use_play_data,
+ 'playback_stats': playback_stats,
+ 'seek_time': seek_time,
+ 'start_time': start_time,
+ 'end_time': end_time,
+ 'refresh_only': screensaver
}
ui.set_property('playback_json', json.dumps(playback_json))
From b3bbd5c6564e9c2f55439a7754c598d714b92ace Mon Sep 17 00:00:00 2001
From: MoojMidge <56883549+MoojMidge@users.noreply.github.com>
Date: Tue, 5 Dec 2023 12:47:22 +1100
Subject: [PATCH 056/141] Update live status based on current video details
- Partially fix #540
- Also properly select protocol type for InputStream Helper
---
.../youtube_plugin/youtube/helper/utils.py | 21 +++++++++++--------
1 file changed, 12 insertions(+), 9 deletions(-)
diff --git a/resources/lib/youtube_plugin/youtube/helper/utils.py b/resources/lib/youtube_plugin/youtube/helper/utils.py
index fd29f4b3d..35a58ff17 100644
--- a/resources/lib/youtube_plugin/youtube/helper/utils.py
+++ b/resources/lib/youtube_plugin/youtube/helper/utils.py
@@ -657,6 +657,7 @@ def update_play_info(provider, context, video_id, video_item, video_stream,
meta_data = video_stream.get('meta', None)
if meta_data:
+ video_item.live = meta_data.get('status', {}).get('live', False)
video_item.set_subtitles(meta_data.get('subtitles', None))
image = get_thumbnail(settings.use_thumbnail_size(),
meta_data.get('images', {}))
@@ -674,17 +675,19 @@ def update_play_info(provider, context, video_id, video_item, video_stream,
elif video_item.use_hls_video() or video_item.use_mpd_video():
video_item.set_isa_video(settings.use_isa())
- license_info = video_stream.get('license_info', {})
- license_proxy = license_info.get('proxy', '')
- license_url = license_info.get('url', '')
- license_token = license_info.get('token', '')
+ if video_item.use_isa_video():
+ license_info = video_stream.get('license_info', {})
+ license_proxy = license_info.get('proxy', '')
+ license_url = license_info.get('url', '')
+ license_token = license_info.get('token', '')
- if ISHelper and license_proxy and license_url and license_token:
- ISHelper('mpd', drm='com.widevine.alpha').check_inputstream()
+ if ISHelper and license_proxy and license_url and license_token:
+ ISHelper('mpd' if video_item.use_mpd_video() else 'hls',
+ drm='com.widevine.alpha').check_inputstream()
- video_item.set_license_key(license_proxy)
- ui.set_property('license_url', license_url)
- ui.set_property('license_token', license_token)
+ video_item.set_license_key(license_proxy)
+ ui.set_property('license_url', license_url)
+ ui.set_property('license_token', license_token)
def update_fanarts(provider, context, channel_items_dict, data=None):
From 72480b5b5c606dd3f4540d94312fa9e996a31e9d Mon Sep 17 00:00:00 2001
From: MoojMidge <56883549+MoojMidge@users.noreply.github.com>
Date: Wed, 6 Dec 2023 11:49:21 +1100
Subject: [PATCH 057/141] Support Youtube Clips
- Closes #450
- Support following plugin url formats:
- plugin://plugin.video.youtube/uri2addon/?uri=https://www.youtube.com/clip/
- plugin://plugin.video.youtube/play/?video_id=&start=&end=&clip=true
- optional clip parameter will loop the clip between start_time and end_time, otherwise video will stop after end_time is reached
---
.../kodion/context/abstract_context.py | 1 +
.../youtube_plugin/kodion/utils/methods.py | 10 ++-
.../lib/youtube_plugin/kodion/utils/player.py | 11 ++-
.../youtube/helper/url_resolver.py | 53 ++++++++++-
.../youtube/helper/url_to_item_converter.py | 90 +++++++++----------
.../youtube_plugin/youtube/helper/yt_play.py | 7 +-
6 files changed, 112 insertions(+), 60 deletions(-)
diff --git a/resources/lib/youtube_plugin/kodion/context/abstract_context.py b/resources/lib/youtube_plugin/kodion/context/abstract_context.py
index d005f9c97..497e949c9 100644
--- a/resources/lib/youtube_plugin/kodion/context/abstract_context.py
+++ b/resources/lib/youtube_plugin/kodion/context/abstract_context.py
@@ -32,6 +32,7 @@ class AbstractContext(object):
'ask_for_quality',
'audio_only',
'confirmed',
+ 'clip',
'enable',
'hide_folders',
'hide_live',
diff --git a/resources/lib/youtube_plugin/kodion/utils/methods.py b/resources/lib/youtube_plugin/kodion/utils/methods.py
index e17d50b42..a88e2475b 100644
--- a/resources/lib/youtube_plugin/kodion/utils/methods.py
+++ b/resources/lib/youtube_plugin/kodion/utils/methods.py
@@ -272,8 +272,9 @@ def friendly_number(number, precision=3, scale=('', 'K', 'M', 'B')):
).rstrip('0').rstrip('.') + scale[magnitude], _input
-_RE_PERIODS = re.compile(r'(\d+)(d|h|m|s)')
+_RE_PERIODS = re.compile(r'([\d.]+)(d|h|m|s|$)')
_SECONDS_IN_PERIODS = {
+ '': 1, # 1 second for unitless period
's': 1, # 1 second
'm': 60, # 1 minute
'h': 3600, # 1 hour
@@ -285,11 +286,12 @@ def duration_to_seconds(duration):
if ':' in duration:
seconds = 0
for part in duration.split(':'):
- seconds = seconds * 60 + int(part, 10)
+ seconds = seconds * 60 + (float(part) if '.' in part else int(part))
return seconds
return sum(
- int(number) * _SECONDS_IN_PERIODS[period]
- for number, period in re.findall(_RE_PERIODS, duration)
+ (float(number) if '.' in number else int(number))
+ * _SECONDS_IN_PERIODS.get(period, 1)
+ for number, period in re.findall(_RE_PERIODS, duration.lower())
)
diff --git a/resources/lib/youtube_plugin/kodion/utils/player.py b/resources/lib/youtube_plugin/kodion/utils/player.py
index 8b77302ca..1481d932f 100644
--- a/resources/lib/youtube_plugin/kodion/utils/player.py
+++ b/resources/lib/youtube_plugin/kodion/utils/player.py
@@ -56,6 +56,7 @@ def run(self):
use_local_history = self.playback_json.get('use_local_history', False)
playback_stats = self.playback_json.get('playback_stats')
refresh_only = self.playback_json.get('refresh_only', False)
+ clip = self.playback_json.get('clip', False)
player = self.player
@@ -166,7 +167,7 @@ def run(self):
pass
if player._end_time and self.current_time >= player._end_time:
- if player._start_time:
+ if clip and player._start_time:
player.seekTime(player._start_time)
else:
player.stop()
@@ -403,7 +404,10 @@ def cleanup_threads(self, only_ended=True):
', '.join([thread.video_id for thread in active_threads]))
self.threads = active_threads
- def onPlayBackStarted(self):
+ def onAVStarted(self):
+ if not self.ui.busy_dialog_active():
+ self.ui.clear_property('busy')
+
playback_json = self.ui.get_property('playback_json')
if not playback_json:
return
@@ -426,6 +430,9 @@ def onPlayBackStarted(self):
playback_json))
def onPlayBackEnded(self):
+ if not self.ui.busy_dialog_active():
+ self.ui.clear_property('busy')
+
self.stop_threads()
self.cleanup_threads()
diff --git a/resources/lib/youtube_plugin/youtube/helper/url_resolver.py b/resources/lib/youtube_plugin/youtube/helper/url_resolver.py
index bed917bd6..3033bf834 100644
--- a/resources/lib/youtube_plugin/youtube/helper/url_resolver.py
+++ b/resources/lib/youtube_plugin/youtube/helper/url_resolver.py
@@ -9,6 +9,7 @@
"""
import re
+from html import unescape
from urllib.parse import parse_qsl, urlencode, urlparse
from ...kodion.network import BaseRequestsClass
@@ -51,6 +52,15 @@ def resolve(self, url, url_components):
class YouTubeResolver(AbstractResolver):
+ _RE_CHANNEL_URL = re.compile(r'')
+ _RE_CLIP_DETAILS = re.compile(r'()'
+ r'|("startTimeMs":"(?P\d+)")'
+ r'|("endTimeMs":"(?P\d+)")')
+
def __init__(self, *args, **kwargs):
super(YouTubeResolver, self).__init__(*args, **kwargs)
@@ -67,6 +77,7 @@ def supports_url(self, url, url_components):
'/@',
'/c/',
'/channel/',
+ '/clip',
'/user/',
)):
return 'GET'
@@ -118,13 +129,47 @@ def resolve(self, url, url_components, method='HEAD'):
if response.status_code != 200:
return url
+ if path.startswith('/clip'):
+ all_matches = self._RE_CLIP_DETAILS.finditer(response.text)
+ num_matched = 0
+ url_components = params = start_time = end_time = None
+ for matches in all_matches:
+ matches = matches.groupdict()
+
+ if not num_matched & 1:
+ url = matches['video_url']
+ if url:
+ num_matched += 1
+ url_components = urlparse(unescape(url))
+ params = dict(parse_qsl(url_components.query))
+
+ if not num_matched & 2:
+ start_time = matches['start_time']
+ if start_time:
+ start_time = int(start_time) / 1000
+ num_matched += 2
+
+ if not num_matched & 4:
+ end_time = matches['end_time']
+ if end_time:
+ end_time = int(end_time) / 1000
+ num_matched += 4
+
+ if num_matched != 7:
+ continue
+
+ params.update({
+ 'clip': True,
+ 'start': start_time,
+ 'end': end_time,
+ })
+ return url_components._replace(query=urlencode(params)).geturl()
+
# we try to extract the channel id from the html content
# With the channel id we can construct a URL we already work with
# https://www.youtube.com/channel/
- if method == 'GET':
- match = re.search(
- r'',
- response.text)
+ elif method == 'GET':
+ match = self._RE_CHANNEL_URL.search(response.text)
if match:
return match.group('channel_url')
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 536e0e0d1..6b564163d 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
@@ -13,11 +13,11 @@
from . import utils
from ...kodion.items import DirectoryItem, UriItem, VideoItem
+from ...kodion.utils import duration_to_seconds
class UrlToItemConverter(object):
RE_PATH_ID = re.compile(r'/\w+/(?P[^/?#]+)', re.I)
- RE_SEEK_TIME = re.compile(r'\d+')
VALID_HOSTNAMES = {
'youtube.com',
'www.youtube.com',
@@ -47,73 +47,67 @@ def add_url(self, url, provider, context):
return
url_params = dict(parse_qsl(parsed_url.query))
- path = parsed_url.path.lower()
-
- channel_id = live = playlist_id = seek_time = video_id = None
- if path == '/watch':
- video_id = url_params.get('v')
- playlist_id = url_params.get('list')
- seek_time = url_params.get('t')
- elif path == '/playlist':
- playlist_id = url_params.get('list')
+ new_params = {
+ new: process(url_params[old]) if process else url_params[old]
+ for old, new, process in (
+ ('end', 'end', duration_to_seconds),
+ ('start', 'start', duration_to_seconds),
+ ('t', 'seek', duration_to_seconds),
+ ('list', 'playlist_id', False),
+ ('v', 'video_id', False),
+ ('live', 'live', False),
+ ('clip', 'clip', False),
+ )
+ if old in url_params
+ }
+
+ path = parsed_url.path.rstrip('/').lower()
+ channel_id = video_id = None
+ if path.startswith(('/playlist', '/watch')):
+ pass
elif path.startswith('/channel/'):
re_match = self.RE_PATH_ID.match(parsed_url.path)
channel_id = re_match.group('id')
- if '/live' in path:
- live = 1
- elif path.startswith(('/live/', '/shorts/')):
+ if path.endswith(('/live', '/streams')):
+ new_params['live'] = 1
+ elif path.startswith(('/clip/', '/embed/', '/live/', '/shorts/')):
re_match = self.RE_PATH_ID.match(parsed_url.path)
- video_id = re_match.group('id')
- seek_time = url_params.get('t')
+ new_params['video_id'] = re_match.group('id')
else:
context.log_debug('Unknown path "{0}" in url "{1}"'.format(
parsed_url.path, url
))
return
- if video_id:
- plugin_params = {
- 'video_id': video_id,
- }
- if seek_time:
- seek_time = sum(
- int(number) * seconds_per_unit
- for number, seconds_per_unit in zip(
- reversed(re.findall(self.RE_SEEK_TIME, seek_time)),
- (1, 60, 3600, 86400)
- )
- if number
- )
- plugin_params['seek'] = seek_time
- if playlist_id:
- plugin_params['playlist_id'] = playlist_id
+ if 'video_id' in new_params:
+ video_id = new_params['video_id']
video_item = VideoItem(
- '', context.create_uri(['play'], plugin_params)
+ '', context.create_uri(['play'], new_params)
)
self._video_id_dict[video_id] = video_item
- elif playlist_id:
+ elif 'playlist_id' in new_params:
+ playlist_id = new_params['playlist_id']
if self._flatten:
self._playlist_ids.append(playlist_id)
- else:
- playlist_item = DirectoryItem(
- '', context.create_uri(['playlist', playlist_id])
- )
- playlist_item.set_fanart(provider.get_fanart(context))
- self._playlist_id_dict[playlist_id] = playlist_item
+ return
+
+ playlist_item = DirectoryItem(
+ '', context.create_uri(['playlist', playlist_id]), new_params
+ )
+ playlist_item.set_fanart(provider.get_fanart(context))
+ self._playlist_id_dict[playlist_id] = playlist_item
elif channel_id:
if self._flatten:
- if live:
- context.set_param('live', live)
self._channel_ids.append(channel_id)
- else:
- channel_item = DirectoryItem(
- '', context.create_uri(['channel', channel_id],
- {'live': live} if live else None)
- )
- channel_item.set_fanart(provider.get_fanart(context))
- self._channel_id_dict[channel_id] = channel_item
+ return
+
+ channel_item = DirectoryItem(
+ '', context.create_uri(['channel', channel_id], new_params)
+ )
+ channel_item.set_fanart(provider.get_fanart(context))
+ self._channel_id_dict[channel_id] = channel_item
else:
context.log_debug('No items found in url "{0}"'.format(url))
diff --git a/resources/lib/youtube_plugin/youtube/helper/yt_play.py b/resources/lib/youtube_plugin/youtube/helper/yt_play.py
index ea7daf631..130e7d1e0 100644
--- a/resources/lib/youtube_plugin/youtube/helper/yt_play.py
+++ b/resources/lib/youtube_plugin/youtube/helper/yt_play.py
@@ -99,8 +99,10 @@ def play_video(provider, context):
if start_time:
video_item.set_start_time(start_time)
- if end_time:
- video_item.set_duration_from_seconds(end_time)
+ # Setting the duration based on end_time can cause issues with
+ # listing/sorting and other addons that monitor playback
+ # if end_time:
+ # video_item.set_duration_from_seconds(end_time)
play_count = use_play_data and video_item.get_play_count() or 0
playback_stats = video_stream.get('playback_stats')
@@ -117,6 +119,7 @@ def play_video(provider, context):
'seek_time': seek_time,
'start_time': start_time,
'end_time': end_time,
+ 'clip': params.get('clip'),
'refresh_only': screensaver
}
From a32d39393bdc5ca1fb47f7bb017f3f740863ce9a Mon Sep 17 00:00:00 2001
From: MoojMidge <56883549+MoojMidge@users.noreply.github.com>
Date: Wed, 6 Dec 2023 14:42:05 +1100
Subject: [PATCH 058/141] Workaround Kodi crash due to multiple busy dialogs
- Workaround crashes in #540, #113
- Won't fix all busy dialog crashes due to how Kodi resolves plugin urls, but should fix most common crashes
---
.../kodion/context/xbmc/xbmc_context.py | 4 +-
.../kodion/player/xbmc/xbmc_playlist.py | 52 ++++++++++++++-----
.../kodion/plugin/xbmc/xbmc_runner.py | 31 +++++++++--
.../kodion/ui/xbmc/xbmc_context_ui.py | 7 +++
.../youtube/helper/yt_playlist.py | 4 +-
.../youtube_plugin/youtube/helper/yt_video.py | 2 +-
.../lib/youtube_plugin/youtube/provider.py | 6 +--
7 files changed, 81 insertions(+), 25 deletions(-)
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 9ea2bbe4c..a95f6a431 100644
--- a/resources/lib/youtube_plugin/kodion/context/xbmc/xbmc_context.py
+++ b/resources/lib/youtube_plugin/kodion/context/xbmc/xbmc_context.py
@@ -298,8 +298,8 @@ def get_region(self):
def addon(self):
return self._addon
- def is_plugin_path(self, uri, uri_path):
- return uri.startswith('plugin://%s/%s/' % (self.get_id(), uri_path))
+ def is_plugin_path(self, uri, uri_path=''):
+ return uri.startswith('plugin://%s/%s' % (self.get_id(), uri_path))
@staticmethod
def format_date_short(date_obj, short_isoformat=False):
diff --git a/resources/lib/youtube_plugin/kodion/player/xbmc/xbmc_playlist.py b/resources/lib/youtube_plugin/kodion/player/xbmc/xbmc_playlist.py
index 69c603e04..bc171be58 100644
--- a/resources/lib/youtube_plugin/kodion/player/xbmc/xbmc_playlist.py
+++ b/resources/lib/youtube_plugin/kodion/player/xbmc/xbmc_playlist.py
@@ -12,6 +12,7 @@
import xbmc
from ..abstract_playlist import AbstractPlaylist
+from ...items import VideoItem
from ...ui.xbmc import xbmc_items
@@ -43,24 +44,25 @@ def unshuffle(self):
def size(self):
return self._playlist.size()
- def get_items(self):
- rpc_request = json.dumps(
- {
- "jsonrpc": "2.0",
- "method": "Playlist.GetItems",
- "params": {
- "properties": ["title", "file"],
- "playlistid": self._playlist.getPlayListId()
- },
- "id": 1
- })
+ def get_items(self, properties=None, dumps=False):
+ rpc_request = json.dumps({
+ 'jsonrpc': '2.0',
+ 'method': 'Playlist.GetItems',
+ 'params': {
+ 'properties': properties if properties else ['title', 'file'],
+ 'playlistid': self._playlist.getPlayListId()
+ },
+ 'id': 1
+ })
response = json.loads(xbmc.executeJSONRPC(rpc_request))
if 'result' in response:
if 'items' in response['result']:
- return response['result']['items']
- return []
+ result = response['result']['items']
+ else:
+ result = []
+ return json.dumps(result) if dumps else result
if 'error' in response:
message = response['error']['message']
@@ -69,4 +71,26 @@ def get_items(self):
else:
error = 'Requested |%s| received error |%s|' % (rpc_request, str(response))
self._context.log_debug(error)
- return []
+ return '[]' if dumps else []
+
+ def add_items(self, items, loads=False):
+ if loads:
+ items = json.loads(items)
+
+ # Playlist.GetItems allows retrieving full playlist item details, but
+ # Playlist.Add only allows for file/path/id etc.
+ # Have to add items individually rather than using JSON-RPC
+
+ for item in items:
+ self.add(VideoItem(item.get('title', ''), item['file']))
+
+ # rpc_request = json.dumps({
+ # 'jsonrpc': '2.0',
+ # 'method': 'Playlist.Add',
+ # 'params': {
+ # 'playlistid': self._playlist.getPlayListId(),
+ # 'item': items,
+ # },
+ # 'id': 1
+ # })
+ # response = json.loads(xbmc.executeJSONRPC(rpc_request))
diff --git a/resources/lib/youtube_plugin/kodion/plugin/xbmc/xbmc_runner.py b/resources/lib/youtube_plugin/kodion/plugin/xbmc/xbmc_runner.py
index 37fe89365..c4b178b6c 100644
--- a/resources/lib/youtube_plugin/kodion/plugin/xbmc/xbmc_runner.py
+++ b/resources/lib/youtube_plugin/kodion/plugin/xbmc/xbmc_runner.py
@@ -14,6 +14,7 @@
from ..abstract_provider_runner import AbstractProviderRunner
from ...exceptions import KodionException
from ...items import AudioItem, DirectoryItem, ImageItem, UriItem, VideoItem
+from ...player import Playlist
from ...ui.xbmc import info_labels, xbmc_items
@@ -24,8 +25,24 @@ def __init__(self):
self.settings = None
def run(self, provider, context):
-
self.handle = context.get_handle()
+ ui = context.get_ui()
+
+ if ui.get_property('busy').lower() == 'true':
+ ui.clear_property('busy')
+ if ui.busy_dialog_active():
+ playlist = Playlist('video', context)
+ playlist.clear()
+
+ xbmcplugin.endOfDirectory(self.handle, succeeded=False)
+
+ items = ui.get_property('playlist')
+ if items:
+ ui.clear_property('playlist')
+ context.log_error('Multiple busy dialogs active - playlist'
+ ' reloading to prevent Kodi crashing')
+ playlist.add_items(items, loads=True)
+ return False
try:
results = provider.navigate(context)
@@ -80,17 +97,25 @@ def run(self, provider, context):
return succeeded
def _set_resolved_url(self, context, base_item):
+ uri = base_item.get_uri()
+
if base_item.playable:
+ ui = context.get_ui()
+ if not context.is_plugin_path(uri) and ui.busy_dialog_active():
+ ui.set_property('busy', 'true')
+ playlist = Playlist('video', context)
+ ui.set_property('playlist', playlist.get_items(dumps=True))
+
item = xbmc_items.to_playback_item(context, base_item)
xbmcplugin.setResolvedUrl(self.handle,
succeeded=True,
listitem=item)
return True
- uri = base_item.get_uri()
- if uri.startswith('plugin://'):
+ if context.is_plugin_path(uri):
context.log_debug('Redirecting to |{0}|'.format(uri))
context.execute('RunPlugin({0})'.format(uri))
+
xbmcplugin.endOfDirectory(self.handle,
succeeded=False,
updateListing=False,
diff --git a/resources/lib/youtube_plugin/kodion/ui/xbmc/xbmc_context_ui.py b/resources/lib/youtube_plugin/kodion/ui/xbmc/xbmc_context_ui.py
index fb47f7684..032380c87 100644
--- a/resources/lib/youtube_plugin/kodion/ui/xbmc/xbmc_context_ui.py
+++ b/resources/lib/youtube_plugin/kodion/ui/xbmc/xbmc_context_ui.py
@@ -232,3 +232,10 @@ def set_focus_next_item(self):
self._context.execute('SetFocus(%s,%s)' % (cid, str(current_position)))
except ValueError:
pass
+
+ @staticmethod
+ def busy_dialog_active():
+ dialog_id = xbmcgui.getCurrentWindowDialogId()
+ if dialog_id == 10160 or dialog_id == 10138:
+ return dialog_id
+ return False
diff --git a/resources/lib/youtube_plugin/youtube/helper/yt_playlist.py b/resources/lib/youtube_plugin/youtube/helper/yt_playlist.py
index 2ecec12be..4d639a6d4 100644
--- a/resources/lib/youtube_plugin/youtube/helper/yt_playlist.py
+++ b/resources/lib/youtube_plugin/youtube/helper/yt_playlist.py
@@ -27,7 +27,7 @@ def _process_add_video(provider, context, keymap_action=False):
video_id = context.get_param('video_id', '')
if not video_id:
- if context.is_plugin_path(listitem_path, 'play'):
+ if context.is_plugin_path(listitem_path, 'play/'):
video_id = kodion.utils.find_video_id(listitem_path)
keymap_action = True
if not video_id:
@@ -141,7 +141,7 @@ def _process_select_playlist(provider, context):
video_id = context.get_param('video_id', '')
if not video_id:
- if context.is_plugin_path(listitem_path, 'play'):
+ if context.is_plugin_path(listitem_path, 'play/'):
video_id = kodion.utils.find_video_id(listitem_path)
if video_id:
context.set_param('video_id', video_id)
diff --git a/resources/lib/youtube_plugin/youtube/helper/yt_video.py b/resources/lib/youtube_plugin/youtube/helper/yt_video.py
index b1362406f..ecc4a32d5 100644
--- a/resources/lib/youtube_plugin/youtube/helper/yt_video.py
+++ b/resources/lib/youtube_plugin/youtube/helper/yt_video.py
@@ -25,7 +25,7 @@ def _process_rate_video(provider, context, re_match):
try:
video_id = re_match.group('video_id')
except IndexError:
- if context.is_plugin_path(listitem_path, 'play'):
+ if context.is_plugin_path(listitem_path, 'play/'):
video_id = kodion.utils.find_video_id(listitem_path)
if not video_id:
diff --git a/resources/lib/youtube_plugin/youtube/provider.py b/resources/lib/youtube_plugin/youtube/provider.py
index a7c75ebfc..fa87b85bb 100644
--- a/resources/lib/youtube_plugin/youtube/provider.py
+++ b/resources/lib/youtube_plugin/youtube/provider.py
@@ -566,14 +566,14 @@ def _on_my_location(self, context, re_match):
@RegisterProviderPath('^/play/$')
def on_play(self, context, re_match):
ui = context.get_ui()
- path = ui.get_info_label('Container.ListItem(0).FileNameAndPath')
redirect = False
params = context.get_params()
if ({'channel_id', 'live', 'playlist_id', 'playlist_ids', 'video_id'}
.isdisjoint(params.keys())):
- if context.is_plugin_path(path, 'play'):
+ path = ui.get_info_label('Container.ListItem(0).FileNameAndPath')
+ if context.is_plugin_path(path, 'play/'):
video_id = find_video_id(path)
if video_id:
context.set_param('video_id', video_id)
@@ -626,7 +626,7 @@ def on_play(self, context, re_match):
if builtin:
context.execute(builtin.format(
- context.create_uri(['play'], {'video_id': video_id})
+ context.create_uri(['play'], params)
))
return False
return yt_play.play_video(self, context)
From f91cfae9ed68ce8fefb1779a2dd5d55d13f72106 Mon Sep 17 00:00:00 2001
From: MoojMidge <56883549+MoojMidge@users.noreply.github.com>
Date: Thu, 7 Dec 2023 00:28:04 +1100
Subject: [PATCH 059/141] Tidy up and reformat
- store context as _context instance variable
- change YoutubePlayer time variables to have non-protected names
- seek_time, start_time, end_time
- handle watch history logging in update_watch_history
---
.../lib/youtube_plugin/kodion/utils/player.py | 290 +++++++++++-------
.../youtube_plugin/youtube/client/youtube.py | 20 +-
.../youtube/helper/subtitles.py | 36 +--
3 files changed, 214 insertions(+), 132 deletions(-)
diff --git a/resources/lib/youtube_plugin/kodion/utils/player.py b/resources/lib/youtube_plugin/kodion/utils/player.py
index 1481d932f..1e0e40ab9 100644
--- a/resources/lib/youtube_plugin/kodion/utils/player.py
+++ b/resources/lib/youtube_plugin/kodion/utils/player.py
@@ -21,9 +21,9 @@ def __init__(self, player, provider, context, playback_json):
self._stopped = threading.Event()
self._ended = threading.Event()
- self.context = context
+ self._context = context
self.provider = provider
- self.ui = self.context.get_ui()
+ self.ui = self._context.get_ui()
self.player = player
@@ -40,14 +40,20 @@ def __init__(self, player, provider, context, playback_json):
self.daemon = True
self.start()
- def update_times(self, total_time, current_time, segment_start, percent_complete):
+ def update_times(self,
+ total_time,
+ current_time,
+ segment_start,
+ percent_complete):
self.total_time = total_time
self.current_time = current_time
self.segment_start = segment_start
self.percent_complete = percent_complete
def abort_now(self):
- return not self.player.isPlaying() or self.context.abort_requested() or self.stopped()
+ return (not self.player.isPlaying()
+ or self._context.abort_requested()
+ or self.stopped())
def run(self):
playing_file = self.playback_json.get('playing_file')
@@ -60,10 +66,11 @@ def run(self):
player = self.player
- self.context.log_debug('PlaybackMonitorThread[%s]: Starting...' % self.video_id)
- access_manager = self.context.get_access_manager()
+ self._context.log_debug('PlaybackMonitorThread[{0}]: Starting'
+ .format(self.video_id))
+ access_manager = self._context.get_access_manager()
- settings = self.context.get_settings()
+ settings = self._context.get_settings()
if playback_stats is None:
playback_stats = {}
@@ -83,8 +90,8 @@ def run(self):
report_url = playback_stats.get('playback_url', '')
- while not player.isPlaying() and not self.context.abort_requested():
- self.context.log_debug('Waiting for playback to start')
+ while not player.isPlaying() and not self._context.abort_requested():
+ self._context.log_debug('Waiting for playback to start')
xbmc.sleep(int(np_wait_time * 1000))
if np_waited >= 5:
@@ -93,12 +100,18 @@ def run(self):
np_waited += np_wait_time
- client = self.provider.get_client(self.context)
+ client = self.provider.get_client(self._context)
is_logged_in = self.provider.is_logged_in()
if is_logged_in and report_url and use_remote_history:
- client.update_watch_history(self.video_id, report_url)
- self.context.log_debug('Playback start reported: |%s|' % self.video_id)
+ client.update_watch_history(
+ self._context,
+ self.video_id,
+ report_url,
+ st=0,
+ et='N/A',
+ state=state
+ )
report_url = playback_stats.get('watchtime_url', '')
@@ -107,10 +120,10 @@ def run(self):
notification_sent = False
- while player.isPlaying() and not self.context.abort_requested() and not self.stopped():
+ while not self.abort_now():
if not notification_sent:
notification_sent = True
- self.context.send_notification('PlaybackStarted', {
+ self._context.send_notification('PlaybackStarted', {
'video_id': self.video_id,
'channel_id': self.channel_id,
'status': self.video_status,
@@ -132,7 +145,10 @@ def run(self):
pass
if self.abort_now():
- self.update_times(last_total_time, last_current_time, last_segment_start, last_percent_complete)
+ self.update_times(last_total_time,
+ last_current_time,
+ last_segment_start,
+ last_percent_complete)
break
try:
@@ -145,20 +161,27 @@ def run(self):
self.current_time = 0.0
if self.abort_now():
- self.update_times(last_total_time, last_current_time, last_segment_start, last_percent_complete)
+ self.update_times(last_total_time,
+ last_current_time,
+ last_segment_start,
+ last_percent_complete)
break
try:
- self.percent_complete = int(float(self.current_time) / float(self.total_time) * 100)
+ self.percent_complete = int(100 * self.current_time
+ / self.total_time)
except ZeroDivisionError:
self.percent_complete = 0
if self.abort_now():
- self.update_times(last_total_time, last_current_time, last_segment_start, last_percent_complete)
+ self.update_times(last_total_time,
+ last_current_time,
+ last_segment_start,
+ last_percent_complete)
break
- if player._start_time or player._seek_time:
- _seek_time = player._start_time or player._seek_time
+ if player.start_time or player.seek_time:
+ _seek_time = player.start_time or player.seek_time
if self.current_time < _seek_time:
player.seekTime(_seek_time)
try:
@@ -166,20 +189,24 @@ def run(self):
except RuntimeError:
pass
- if player._end_time and self.current_time >= player._end_time:
- if clip and player._start_time:
- player.seekTime(player._start_time)
+ if player.end_time and self.current_time >= player.end_time:
+ if clip and player.start_time:
+ player.seekTime(player.start_time)
else:
player.stop()
if self.abort_now():
- self.update_times(last_total_time, last_current_time, last_segment_start, last_percent_complete)
+ self.update_times(last_total_time,
+ last_current_time,
+ last_segment_start,
+ last_percent_complete)
break
if p_waited >= report_interval:
+ # refresh client, tokens may need refreshing
if is_logged_in:
- self.provider.reset_client() # refresh client, tokens may need refreshing
- client = self.provider.get_client(self.context)
+ self.provider.reset_client()
+ client = self.provider.get_client(self._context)
is_logged_in = self.provider.is_logged_in()
if self.current_time == played_time:
@@ -192,11 +219,14 @@ def run(self):
played_time = self.current_time
if self.abort_now():
- self.update_times(last_total_time, last_current_time, last_segment_start, last_percent_complete)
+ self.update_times(last_total_time,
+ last_current_time,
+ last_segment_start,
+ last_percent_complete)
break
- if (is_logged_in and report_url and use_remote_history and (
- first_report or p_waited >= report_interval)):
+ if (is_logged_in and report_url and use_remote_history
+ and (first_report or p_waited >= report_interval)):
if first_report:
first_report = False
self.segment_start = 0.0
@@ -219,17 +249,16 @@ def run(self):
if self.segment_start > segment_end:
segment_end = self.segment_start + 10.0
- if state == 'playing' or last_state == 'playing': # only report state='paused' once
- client.update_watch_history(self.video_id, report_url
- .format(st=format(self.segment_start, '.3f'),
- et=format(segment_end, '.3f'),
- state=state))
- self.context.log_debug(
- 'Playback reported [%s]: %s segment start, %s segment end @ %s%% state=%s' %
- (self.video_id,
- format(self.segment_start, '.3f'),
- format(segment_end, '.3f'),
- self.percent_complete, state))
+ # only report state='paused' once
+ if state == 'playing' or last_state == 'playing':
+ client.update_watch_history(
+ self._context,
+ self.video_id,
+ report_url,
+ st=format(self.segment_start, '.3f'),
+ et=format(segment_end, '.3f'),
+ state=state
+ )
self.segment_start = segment_end
@@ -241,99 +270,127 @@ def run(self):
p_waited += p_wait_time
if is_logged_in and report_url and use_remote_history:
- client.update_watch_history(self.video_id, report_url
- .format(st=format(self.segment_start, '.3f'),
- et=format(self.current_time, '.3f'),
- state=state))
- self.context.log_debug('Playback reported [%s]: %s segment start, %s segment end @ %s%% state=%s' %
- (self.video_id,
- format(self.segment_start, '.3f'),
- format(self.current_time, '.3f'),
- self.percent_complete, state))
-
- self.context.send_notification('PlaybackStopped', {
+ client.update_watch_history(
+ self._context,
+ self.video_id,
+ report_url,
+ st=format(self.segment_start, '.3f'),
+ et=format(self.current_time, '.3f'),
+ state=state
+ )
+
+ self._context.send_notification('PlaybackStopped', {
'video_id': self.video_id,
'channel_id': self.channel_id,
'status': self.video_status,
})
- self.context.log_debug('Playback stopped [%s]: %s secs of %s @ %s%%' %
- (self.video_id, format(self.current_time, '.3f'),
- format(self.total_time, '.3f'), self.percent_complete))
+ self._context.log_debug('Playback stopped [{video_id}]:'
+ ' {current:.3f} secs of {total:.3f}'
+ ' @ {percent}%'.format(
+ video_id=self.video_id,
+ current=self.current_time,
+ total=self.total_time,
+ percent=self.percent_complete,
+ ))
state = 'stopped'
+ # refresh client, tokens may need refreshing
if is_logged_in:
- self.provider.reset_client() # refresh client, tokens may need refreshing
- client = self.provider.get_client(self.context)
+ self.provider.reset_client()
+ client = self.provider.get_client(self._context)
is_logged_in = self.provider.is_logged_in()
if self.percent_complete >= settings.get_play_count_min_percent():
play_count += 1
self.current_time = 0.0
if is_logged_in and report_url and use_remote_history:
- client.update_watch_history(self.video_id, report_url
- .format(st=format(self.total_time, '.3f'),
- et=format(self.total_time, '.3f'),
- state=state))
- self.context.log_debug('Playback reported [%s] @ 100%% state=%s' % (self.video_id, state))
+ client.update_watch_history(
+ self._context,
+ self.video_id,
+ report_url,
+ st=format(self.total_time, '.3f'),
+ et=format(self.total_time, '.3f'),
+ state=state
+ )
else:
if is_logged_in and report_url and use_remote_history:
- client.update_watch_history(self.video_id, report_url
- .format(st=format(self.current_time, '.3f'),
- et=format(self.current_time, '.3f'),
- state=state))
- self.context.log_debug('Playback reported [%s]: %s segment start, %s segment end @ %s%% state=%s' %
- (self.video_id, format(self.current_time, '.3f'),
- format(self.current_time, '.3f'),
- self.percent_complete, state))
+ client.update_watch_history(
+ self._context,
+ self.video_id,
+ report_url,
+ st=format(self.current_time, '.3f'),
+ et=format(self.current_time, '.3f'),
+ state=state
+ )
refresh_only = True
if use_local_history:
- self.context.get_playback_history().update(self.video_id, play_count, self.total_time,
- self.current_time, self.percent_complete)
+ self._context.get_playback_history().update(self.video_id,
+ play_count,
+ self.total_time,
+ self.current_time,
+ self.percent_complete)
if not refresh_only and is_logged_in:
- if settings.get_bool('youtube.playlist.watchlater.autoremove', True):
+ if settings.get_bool('youtube.playlist.watchlater.autoremove',
+ True):
watch_later_id = access_manager.get_watch_later_id()
if watch_later_id:
- playlist_item_id = \
- client.get_playlist_item_id_of_video_id(playlist_id=watch_later_id, video_id=self.video_id)
+ playlist_item_id = client.get_playlist_item_id_of_video_id(
+ playlist_id=watch_later_id, video_id=self.video_id
+ )
if playlist_item_id:
- json_data = client.remove_video_from_playlist(watch_later_id, playlist_item_id)
- _ = self.provider.v3_handle_error(self.provider, self.context, json_data)
+ json_data = client.remove_video_from_playlist(
+ watch_later_id, playlist_item_id
+ )
+ _ = self.provider.v3_handle_error(self.provider,
+ self._context,
+ json_data)
history_playlist_id = access_manager.get_watch_history_id()
if history_playlist_id and history_playlist_id != 'HL':
- json_data = client.add_video_to_playlist(history_playlist_id, self.video_id)
- _ = self.provider.v3_handle_error(self.provider, self.context, json_data)
+ json_data = client.add_video_to_playlist(history_playlist_id,
+ self.video_id)
+ _ = self.provider.v3_handle_error(self.provider,
+ self._context,
+ json_data)
# rate video
if settings.get_bool('youtube.post.play.rate', False):
do_rating = True
- if not settings.get_bool('youtube.post.play.rate.playlists', False):
+ if not settings.get_bool('youtube.post.play.rate.playlists',
+ False):
playlist = xbmc.PlayList(xbmc.PLAYLIST_VIDEO)
do_rating = int(playlist.size()) < 2
if do_rating:
json_data = client.get_video_rating(self.video_id)
- success = self.provider.v3_handle_error(self.provider, self.context, json_data)
+ success = self.provider.v3_handle_error(self.provider,
+ self._context,
+ json_data)
if success:
items = json_data.get('items', [{'rating': 'none'}])
rating = items[0].get('rating', 'none')
if rating == 'none':
rating_match = \
- re.search('/(?P[^/]+)/(?P[^/]+)', '/%s/%s/' %
- (self.video_id, rating))
- self.provider.yt_video.process('rate', self.provider, self.context, rating_match)
+ re.search(r'/(?P[^/]+)'
+ r'/(?P[^/]+)',
+ '/{0}/{1}/'.format(self.video_id,
+ rating))
+ self.provider.yt_video.process('rate',
+ self.provider,
+ self._context,
+ rating_match)
playlist = xbmc.PlayList(xbmc.PLAYLIST_VIDEO)
- do_refresh = (int(playlist.size()) < 2) or (playlist.getposition() == -1)
-
- if do_refresh and settings.get_bool('youtube.post.play.refresh', False) and \
- not xbmc.getInfoLabel('Container.FolderPath') \
- .startswith(self.context.create_uri(['kodion', 'search', 'input'])):
+ do_refresh = playlist.size() < 2 or playlist.getposition() == -1
+ if (do_refresh and settings.get_bool('youtube.post.play.refresh', False)
+ and not xbmc.getInfoLabel('Container.FolderPath').startswith(
+ self._context.create_uri(['kodion', 'search', 'input'])
+ )):
# don't refresh search input it causes request for new input,
# (Container.Update in abstract_provider /kodion/search/input/
# would resolve this but doesn't work with Remotes(Yatse))
@@ -342,14 +399,16 @@ def run(self):
self.end()
def stop(self):
- self.context.log_debug('PlaybackMonitorThread[%s]: Stop event set...' % self.video_id)
+ self._context.log_debug('PlaybackMonitorThread[{0}]: Stop event set'
+ .format(self.video_id))
self._stopped.set()
def stopped(self):
return self._stopped.is_set()
def end(self):
- self.context.log_debug('PlaybackMonitorThread[%s]: End event set...' % self.video_id)
+ self._context.log_debug('PlaybackMonitorThread[{0}]: End event set'
+ .format(self.video_id))
self._ended.set()
def ended(self):
@@ -357,14 +416,15 @@ def ended(self):
class YouTubePlayer(xbmc.Player):
- def __init__(self, *args, **kwargs):
- self.context = kwargs.get('context')
+ def __init__(self, *_args, **kwargs):
+ super(YouTubePlayer, self).__init__()
+ self._context = kwargs.get('context')
self.provider = kwargs.get('provider')
- self.ui = self.context.get_ui()
+ self.ui = self._context.get_ui()
self.threads = []
- self._seek_time = None
- self._start_time = None
- self._end_time = None
+ self.seek_time = None
+ self.start_time = None
+ self.end_time = None
def stop_threads(self):
for thread in self.threads:
@@ -372,7 +432,8 @@ def stop_threads(self):
continue
if not thread.stopped():
- self.context.log_debug('PlaybackMonitorThread[%s]: stopping...' % thread.video_id)
+ self._context.log_debug('PlaybackMonitorThread[{0}]: stopping'
+ .format(thread.video_id))
thread.stop()
for thread in self.threads:
@@ -390,9 +451,11 @@ def cleanup_threads(self, only_ended=True):
continue
if thread.ended():
- self.context.log_debug('PlaybackMonitorThread[%s]: clean up...' % thread.video_id)
+ self._context.log_debug('PlaybackMonitorThread[{0}]: clean up'
+ .format(thread.video_id))
else:
- self.context.log_debug('PlaybackMonitorThread[%s]: stopping...' % thread.video_id)
+ self._context.log_debug('PlaybackMonitorThread[{0}]: stopping'
+ .format(thread.video_id))
if not thread.stopped():
thread.stop()
try:
@@ -400,8 +463,9 @@ def cleanup_threads(self, only_ended=True):
except RuntimeError:
pass
- self.context.log_debug('PlaybackMonitor active threads: |%s|' %
- ', '.join([thread.video_id for thread in active_threads]))
+ self._context.log_debug('PlaybackMonitor active threads: |{0}|'.format(
+ ', '.join([thread.video_id for thread in active_threads])
+ ))
self.threads = active_threads
def onAVStarted(self):
@@ -414,19 +478,19 @@ def onAVStarted(self):
playback_json = json.loads(playback_json)
try:
- self._seek_time = float(playback_json.get('seek_time'))
- self._start_time = float(playback_json.get('start_time'))
- self._end_time = float(playback_json.get('end_time'))
+ self.seek_time = float(playback_json.get('seek_time'))
+ self.start_time = float(playback_json.get('start_time'))
+ self.end_time = float(playback_json.get('end_time'))
except (ValueError, TypeError):
- self._seek_time = None
- self._start_time = None
- self._end_time = None
+ self.seek_time = None
+ self.start_time = None
+ self.end_time = None
self.ui.clear_property('playback_json')
self.cleanup_threads()
self.threads.append(PlaybackMonitorThread(self,
self.provider,
- self.context,
+ self._context,
playback_json))
def onPlayBackEnded(self):
@@ -444,8 +508,8 @@ def onPlayBackError(self):
def onPlayBackSeek(self, time, seekOffset):
time_s = time // 1000
- self._seek_time = None
- if ((self._end_time and time_s > self._end_time)
- or (self._start_time and time_s < self._start_time)):
- self._start_time = None
- self._end_time = None
+ self.seek_time = None
+ if ((self.end_time and time_s > self.end_time)
+ or (self.start_time and time_s < self.start_time)):
+ self.start_time = None
+ self.end_time = None
diff --git a/resources/lib/youtube_plugin/youtube/client/youtube.py b/resources/lib/youtube_plugin/youtube/client/youtube.py
index c81a4fedd..cb86ad423 100644
--- a/resources/lib/youtube_plugin/youtube/client/youtube.py
+++ b/resources/lib/youtube_plugin/youtube/client/youtube.py
@@ -65,7 +65,25 @@ def calculate_next_page_token(page, max_result):
return 'C%s%s%sAA' % (high[high_iteration], low[low_iteration], overflow_token)
- def update_watch_history(self, video_id, url):
+ def update_watch_history(self,
+ context,
+ video_id,
+ url,
+ st=None,
+ et=None,
+ state=None):
+ if None not in (st, et, state):
+ url.format(st=st, et=et, state=state)
+ else:
+ st = et = state = 'N/A'
+
+ context.log_debug('Playback reported [{video_id}]:'
+ ' {st} segment start,'
+ ' {et} segment end,'
+ ' state={state}'.format(
+ video_id=video_id, st=st, et=et, state=state
+ ))
+
headers = {'Host': 'www.youtube.com',
'Connection': 'keep-alive',
'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.36 Safari/537.36',
diff --git a/resources/lib/youtube_plugin/youtube/helper/subtitles.py b/resources/lib/youtube_plugin/youtube/helper/subtitles.py
index bcf7dd4d4..48019112c 100644
--- a/resources/lib/youtube_plugin/youtube/helper/subtitles.py
+++ b/resources/lib/youtube_plugin/youtube/helper/subtitles.py
@@ -27,7 +27,7 @@ class Subtitles(object):
def __init__(self, context, video_id, captions, headers=None):
self.video_id = video_id
- self.context = context
+ self._context = context
settings = context.get_settings()
self.language = settings.get_language()
@@ -41,7 +41,7 @@ def __init__(self, context, video_id, captions, headers=None):
headers.pop('Content-Type', None)
self.headers = headers
- ui = self.context.get_ui()
+ ui = self._context.get_ui()
self.prompt_override = (ui.get_property('prompt_for_subtitles')
== video_id)
ui.clear_property('prompt_for_subtitles')
@@ -93,15 +93,15 @@ def srt_filename(self, sub_language):
def _write_file(self, filepath, contents):
if not make_dirs(self.BASE_PATH):
- self.context.log_debug('Failed to create directories: %s' % self.BASE_PATH)
+ self._context.log_debug('Failed to create directories: %s' % self.BASE_PATH)
return False
- self.context.log_debug('Writing subtitle file: %s' % filepath)
+ self._context.log_debug('Writing subtitle file: %s' % filepath)
try:
with xbmcvfs.File(filepath, 'w') as srt_file:
success = srt_file.write(contents)
except (IOError, OSError):
- self.context.log_debug('File write failed for: %s' % filepath)
+ self._context.log_debug('File write failed for: %s' % filepath)
return False
return success
@@ -109,7 +109,7 @@ def _unescape(self, text):
try:
text = unescape(text)
except:
- self.context.log_debug('Subtitle unescape: failed to unescape text')
+ self._context.log_debug('Subtitle unescape: failed to unescape text')
return text
def get_default_lang(self):
@@ -123,7 +123,7 @@ def get_subtitles(self):
languages = self.LANG_PROMPT
else:
languages = self.subtitle_languages
- self.context.log_debug('Subtitle get_subtitles: for setting |%s|' % str(languages))
+ self._context.log_debug('Subtitle get_subtitles: for setting |%s|' % str(languages))
if languages == self.LANG_NONE:
return []
if languages == self.LANG_CURR:
@@ -146,7 +146,7 @@ def get_subtitles(self):
list_of_subs.extend(self._get('en-US'))
list_of_subs.extend(self._get('en-GB'))
return list(set(list_of_subs))
- self.context.log_debug('Unknown language_enum: %s for subtitles' % str(languages))
+ self._context.log_debug('Unknown language_enum: %s for subtitles' % str(languages))
return []
def _get_all(self, download=False):
@@ -173,13 +173,13 @@ def _prompt(self):
num_total = num_captions + num_translations
if num_total:
- choice = self.context.get_ui().on_select(
- self.context.localize('subtitles.language'),
+ choice = self._context.get_ui().on_select(
+ self._context.localize('subtitles.language'),
[name for _, name in captions] +
[name + ' *' for _, name in translations]
)
if choice == -1:
- self.context.log_debug('Subtitle selection cancelled')
+ self._context.log_debug('Subtitle selection cancelled')
return []
subtitle = None
@@ -192,13 +192,13 @@ def _prompt(self):
if subtitle:
return subtitle
- self.context.log_debug('No subtitles found for prompt')
+ self._context.log_debug('No subtitles found for prompt')
return []
def _get(self, lang_code='en', language=None, no_asr=False, download=None):
filename = self.srt_filename(lang_code)
if xbmcvfs.exists(filename):
- self.context.log_debug('Subtitle exists for: %s, filename: %s' % (lang_code, filename))
+ self._context.log_debug('Subtitle exists for: %s, filename: %s' % (lang_code, filename))
return [filename]
if download is None:
@@ -230,7 +230,7 @@ def _get(self, lang_code='en', language=None, no_asr=False, download=None):
if (lang_code != self.defaults['lang_code'] and not has_translation
and caption_track is None):
- self.context.log_debug('No subtitles found for: %s' % lang_code)
+ self._context.log_debug('No subtitles found for: %s' % lang_code)
return []
subtitle_url = None
@@ -253,24 +253,24 @@ def _get(self, lang_code='en', language=None, no_asr=False, download=None):
)
if subtitle_url:
- self.context.log_debug('Subtitle url: %s' % subtitle_url)
+ self._context.log_debug('Subtitle url: %s' % subtitle_url)
if not download:
return [subtitle_url]
response = BaseRequestsClass().request(subtitle_url,
headers=self.headers)
if response.text:
- self.context.log_debug('Subtitle found for: %s' % lang_code)
+ self._context.log_debug('Subtitle found for: %s' % lang_code)
self._write_file(filename,
bytearray(self._unescape(response.text),
encoding='utf8',
errors='ignore'))
return [filename]
- self.context.log_debug('Failed to retrieve subtitles for: %s' % lang_code)
+ self._context.log_debug('Failed to retrieve subtitles for: %s' % lang_code)
return []
- self.context.log_debug('No subtitles found for: %s' % lang_code)
+ self._context.log_debug('No subtitles found for: %s' % lang_code)
return []
@staticmethod
From a9e331961b2d4af57153a5f5833e628346aadf8f Mon Sep 17 00:00:00 2001
From: MoojMidge <56883549+MoojMidge@users.noreply.github.com>
Date: Thu, 7 Dec 2023 01:50:05 +1100
Subject: [PATCH 060/141] Add ForceResolvePlugin property to listitem
- Attempt to reduce edge cases that can Kodi to crash due to multiple busy dialogs
---
.../youtube_plugin/kodion/items/uri_item.py | 2 +-
.../kodion/ui/xbmc/info_labels.py | 6 ++---
.../kodion/ui/xbmc/xbmc_items.py | 24 +++++++++++++++----
3 files changed, 23 insertions(+), 9 deletions(-)
diff --git a/resources/lib/youtube_plugin/kodion/items/uri_item.py b/resources/lib/youtube_plugin/kodion/items/uri_item.py
index 2c3e2141d..e30df6e7e 100644
--- a/resources/lib/youtube_plugin/kodion/items/uri_item.py
+++ b/resources/lib/youtube_plugin/kodion/items/uri_item.py
@@ -13,6 +13,6 @@
class UriItem(BaseItem):
def __init__(self, uri, playable=None):
- super(UriItem, self).__init__(name='', uri=uri)
+ super(UriItem, self).__init__(name=uri, uri=uri)
if playable is not None:
self._playable = playable
diff --git a/resources/lib/youtube_plugin/kodion/ui/xbmc/info_labels.py b/resources/lib/youtube_plugin/kodion/ui/xbmc/info_labels.py
index 767f34d5a..9012179e2 100644
--- a/resources/lib/youtube_plugin/kodion/ui/xbmc/info_labels.py
+++ b/resources/lib/youtube_plugin/kodion/ui/xbmc/info_labels.py
@@ -101,12 +101,12 @@ def create_from_item(base_item):
_process_string_value(info_labels, 'plot', base_item.get_plot())
# Image
- if isinstance(base_item, ImageItem):
+ elif isinstance(base_item, ImageItem):
# 'title' = 'Blow Your Head Off' (string)
_process_string_value(info_labels, 'title', base_item.get_title())
# Audio
- if isinstance(base_item, AudioItem):
+ elif isinstance(base_item, AudioItem):
# 'duration' = 79 (int)
_process_int_value(info_labels, 'duration', base_item.get_duration())
@@ -120,7 +120,7 @@ def create_from_item(base_item):
_process_audio_rating(info_labels, base_item.get_rating())
# Video
- if isinstance(base_item, VideoItem):
+ elif isinstance(base_item, VideoItem):
# mediatype
_process_mediatype(info_labels, 'mediatype', base_item.get_mediatype())
diff --git a/resources/lib/youtube_plugin/kodion/ui/xbmc/xbmc_items.py b/resources/lib/youtube_plugin/kodion/ui/xbmc/xbmc_items.py
index 0799f54cf..a511ca982 100644
--- a/resources/lib/youtube_plugin/kodion/ui/xbmc/xbmc_items.py
+++ b/resources/lib/youtube_plugin/kodion/ui/xbmc/xbmc_items.py
@@ -74,6 +74,7 @@ def video_playback_item(context, video_item):
}
props = {
'isPlayable': str(video_item.playable).lower(),
+ 'ForceResolvePlugin': 'true',
}
if (alternative_player
@@ -170,6 +171,7 @@ def audio_listitem(context, audio_item):
}
props = {
'isPlayable': str(audio_item.playable).lower(),
+ 'ForceResolvePlugin': 'true',
}
list_item = ListItem(**kwargs)
@@ -194,12 +196,23 @@ def audio_listitem(context, audio_item):
return list_item
-def uri_listitem(context, base_item):
- uri = base_item.get_uri()
+def uri_listitem(context, uri_item):
+ uri = uri_item.get_uri()
context.log_debug('Converting UriItem |%s|' % uri)
- item = ListItem(path=uri, offscreen=True)
- item.setProperty('IsPlayable', str(base_item.playable).lower())
- return item
+
+ kwargs = {
+ 'label': uri_item.get_name(),
+ 'path': uri,
+ 'offscreen': True,
+ }
+ props = {
+ 'isPlayable': str(uri_item.playable).lower(),
+ 'ForceResolvePlugin': 'true',
+ }
+
+ list_item = ListItem(**kwargs)
+ list_item.setProperties(props)
+ return list_item
def video_listitem(context, video_item):
@@ -220,6 +233,7 @@ def video_listitem(context, video_item):
}
props = {
'isPlayable': str(video_item.playable).lower(),
+ 'ForceResolvePlugin': 'true',
}
list_item = ListItem(**kwargs)
From b0329bf7a82f53eba1055be8d8d41f431d6e3ad6 Mon Sep 17 00:00:00 2001
From: MoojMidge <56883549+MoojMidge@users.noreply.github.com>
Date: Thu, 7 Dec 2023 13:31:22 +1100
Subject: [PATCH 061/141] Update monitor and http_server
- Follow up to 6d1301ea
- Fix for server settings not applying until addon restarts
- Use BaseRequestsClass
- Update formatting
- Use Settings rather than getting addon settings directly
- this is in preparation for using new settings class via xbmcaddon.Addon('id').getSettings()
- aim to update settings on onSettingsChanged rather than reading and parsing settings xml all the time
- Add basic validation of port setting
---
.../kodion/constants/const_settings.py | 2 +-
.../kodion/network/http_server.py | 662 ++++++++++--------
.../kodion/settings/abstract_settings.py | 2 +-
.../youtube_plugin/kodion/utils/monitor.py | 96 ++-
.../youtube/client/__config__.py | 1 -
resources/settings.xml | 6 +-
6 files changed, 432 insertions(+), 337 deletions(-)
diff --git a/resources/lib/youtube_plugin/kodion/constants/const_settings.py b/resources/lib/youtube_plugin/kodion/constants/const_settings.py
index 47dfeb3a0..da0308bf4 100644
--- a/resources/lib/youtube_plugin/kodion/constants/const_settings.py
+++ b/resources/lib/youtube_plugin/kodion/constants/const_settings.py
@@ -47,7 +47,7 @@
CONNECT_TIMEOUT = 'requests.timeout.connect' # (int)
READ_TIMEOUT = 'requests.timeout.read' # (int)
-HTTPD_PORT = 'kodion.mpd.proxy.port' # (number)
+HTTPD_PORT = 'kodion.http.port' # (number)
HTTPD_LISTEN = 'kodion.http.listen' # (string)
HTTPD_WHITELIST = 'kodion.http.ip.whitelist' # (string)
diff --git a/resources/lib/youtube_plugin/kodion/network/http_server.py b/resources/lib/youtube_plugin/kodion/network/http_server.py
index 910fb9a89..e5fd5422e 100644
--- a/resources/lib/youtube_plugin/kodion/network/http_server.py
+++ b/resources/lib/youtube_plugin/kodion/network/http_server.py
@@ -10,35 +10,45 @@
import json
import os
import re
-import requests
-import socket
+from socket import error as socket_error
from http import server as BaseHTTPServer
-from urllib.parse import parse_qs
-from urllib.parse import urlparse
+from textwrap import dedent
+from urllib.parse import parse_qs, urlparse
-import xbmc
-import xbmcgui
-import xbmcvfs
+from xbmc import getCondVisibility, executebuiltin
+from xbmcgui import Dialog, Window
+from xbmcvfs import translatePath
from xbmcaddon import Addon
+from .requests import BaseRequestsClass
from ..logger import log_debug
from ..settings import Settings
_addon_id = 'plugin.video.youtube'
-_settings = Settings(Addon(id=_addon_id))
+_addon = Addon(_addon_id)
+_settings = Settings(_addon)
+_i18n = _addon.getLocalizedString
+_server_requests = BaseRequestsClass()
class YouTubeProxyRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
+ base_path = translatePath('special://temp/{0}'.format(_addon_id))
+ chunk_size = 1024 * 64
+ local_ranges = (
+ '10.',
+ '172.16.',
+ '192.168.',
+ '127.0.0.1',
+ 'localhost',
+ '::1',
+ )
+
def __init__(self, request, client_address, server):
- addon = Addon(_addon_id)
- whitelist_ips = addon.getSetting('kodion.http.ip.whitelist')
- whitelist_ips = ''.join(whitelist_ips.split())
- self.whitelist_ips = whitelist_ips.split(',')
- self.local_ranges = ('10.', '172.16.', '192.168.', '127.0.0.1', 'localhost', '::1')
- self.chunk_size = 1024 * 64
- self.base_path = xbmcvfs.translatePath('special://temp/%s' % _addon_id)
- super(YouTubeProxyRequestHandler, self).__init__(request, client_address, server)
+ self.whitelist_ips = _settings.httpd_whitelist()
+ super(YouTubeProxyRequestHandler, self).__init__(request,
+ client_address,
+ server)
def connection_allowed(self):
client_ip = self.client_address[0]
@@ -50,146 +60,169 @@ def connection_allowed(self):
log_lines.append('Whitelisted: |%s|' % str(conn_allowed))
if not conn_allowed:
- log_debug('HTTPServer: Connection from |%s| not allowed' % client_ip)
+ log_debug('HTTPServer: Connection from |{client_ip| not allowed'.
+ format(client_ip=client_ip))
elif self.path != '/ping':
log_debug(' '.join(log_lines))
return conn_allowed
# noinspection PyPep8Naming
def do_GET(self):
- addon = Addon('plugin.video.youtube')
- mpd_proxy_enabled = addon.getSetting('kodion.mpd.videos') == 'true' and addon.getSetting('kodion.video.quality.isa') == 'true'
- api_config_enabled = addon.getSetting('youtube.api.config.page') == 'true'
+ api_config_enabled = _settings.api_config_page()
# Strip trailing slash if present
- stripped_path = self.path.rstrip('/')
+ stripped_path = self.path.rstrip('/').lower()
+ if stripped_path != '/ping':
+ log_debug('HTTPServer: GET Request uri path |{stripped_path}|'
+ .format(stripped_path=stripped_path))
- if stripped_path == '/client_ip':
- client_json = json.dumps({"ip": "{ip}".format(ip=self.client_address[0])})
+ if not self.connection_allowed():
+ self.send_error(403)
+
+ elif stripped_path == '/client_ip':
+ client_json = json.dumps({"ip": "{ip}"
+ .format(ip=self.client_address[0])})
self.send_response(200)
self.send_header('Content-Type', 'application/json; charset=utf-8')
- self.send_header('Content-Length', len(client_json))
+ self.send_header('Content-Length', str(len(client_json)))
self.end_headers()
self.wfile.write(client_json.encode('utf-8'))
- if stripped_path != '/ping':
- log_debug('HTTPServer: GET Request uri path |{proxy_path}|'.format(proxy_path=self.path))
-
- if not self.connection_allowed():
- self.send_error(403)
- elif mpd_proxy_enabled and self.path.endswith('.mpd'):
- file_path = os.path.join(self.base_path, self.path.strip('/').strip('\\'))
+ elif _settings.use_mpd_videos() and stripped_path.endswith('.mpd'):
+ file_path = os.path.join(self.base_path,
+ self.path.strip('/').strip('\\'))
file_chunk = True
- log_debug('HTTPServer: Request file path |{file_path}|'.format(file_path=file_path.encode('utf-8')))
+ log_debug('HTTPServer: Request file path |{file_path}|'
+ .format(file_path=file_path))
try:
with open(file_path, 'rb') as f:
self.send_response(200)
self.send_header('Content-Type', 'application/xml+dash')
- self.send_header('Content-Length', os.path.getsize(file_path))
+ self.send_header('Content-Length',
+ str(os.path.getsize(file_path)))
self.end_headers()
while file_chunk:
file_chunk = f.read(self.chunk_size)
if file_chunk:
self.wfile.write(file_chunk)
except IOError:
- response = 'File Not Found: |{proxy_path}| -> |{file_path}|'.format(proxy_path=self.path, file_path=file_path.encode('utf-8'))
+ response = ('File Not Found: |{proxy_path}| -> |{file_path}|'
+ .format(proxy_path=self.path, file_path=file_path))
self.send_error(404, response)
- elif api_config_enabled and stripped_path.lower() == '/api':
+
+ elif api_config_enabled and stripped_path == '/api':
html = self.api_config_page()
html = html.encode('utf-8')
+
self.send_response(200)
self.send_header('Content-Type', 'text/html; charset=utf-8')
- self.send_header('Content-Length', len(html))
+ self.send_header('Content-Length', str(len(html)))
self.end_headers()
+
for chunk in self.get_chunks(html):
self.wfile.write(chunk)
+
elif api_config_enabled and stripped_path.startswith('/api_submit'):
- addon = Addon('plugin.video.youtube')
- i18n = addon.getLocalizedString
- xbmc.executebuiltin('Dialog.Close(addonsettings,true)')
- old_api_key = addon.getSetting('youtube.api.key')
- old_api_id = addon.getSetting('youtube.api.id')
- old_api_secret = addon.getSetting('youtube.api.secret')
+ executebuiltin('Dialog.Close(addonsettings, true)')
+
query = urlparse(self.path).query
params = parse_qs(query)
+ updated = []
+
api_key = params.get('api_key', [None])[0]
api_id = params.get('api_id', [None])[0]
api_secret = params.get('api_secret', [None])[0]
- footer = i18n(30638) if api_key and api_id and api_secret else ''
+ # Bookmark this page
+ footer = _i18n(30638) if api_key and api_id and api_secret else ''
+
if re.search(r'api_key=(?:&|$)', query):
api_key = ''
if re.search(r'api_id=(?:&|$)', query):
api_id = ''
if re.search(r'api_secret=(?:&|$)', query):
api_secret = ''
- updated = []
- if api_key is not None and api_key != old_api_key:
- addon.setSetting('youtube.api.key', api_key)
- updated.append(i18n(30201))
- if api_id is not None and api_id != old_api_id:
- addon.setSetting('youtube.api.id', api_id)
- updated.append(i18n(30202))
- if api_secret is not None and api_secret != old_api_secret:
- updated.append(i18n(30203))
- addon.setSetting('youtube.api.secret', api_secret)
- if addon.getSetting('youtube.api.key') and addon.getSetting('youtube.api.id') and \
- addon.getSetting('youtube.api.secret'):
- enabled = i18n(30636)
+
+ if api_key is not None and api_key != _settings.api_key():
+ _settings.api_key(new_key=api_key)
+ updated.append(_i18n(30201)) # API Key
+
+ if api_id is not None and api_id != _settings.api_id():
+ _settings.api_id(new_id=api_id)
+ updated.append(_i18n(30202)) # API ID
+
+ if api_secret is not None and api_secret != _settings.api_secret():
+ _settings.api_secret(new_secret=api_secret)
+ updated.append(_i18n(30203)) # API Secret
+
+ if api_key and api_id and api_secret:
+ enabled = _i18n(30636) # Personal keys enabled
else:
- enabled = i18n(30637)
- if not updated:
- updated = i18n(30635)
+ enabled = _i18n(30637) # Personal keys disabled
+
+ if updated:
+ # Successfully updated
+ updated = _i18n(30631) % ', '.join(updated)
else:
- updated = i18n(30631) % ', '.join(updated)
+ # No changes, not updated
+ updated = _i18n(30635)
+
html = self.api_submit_page(updated, enabled, footer)
html = html.encode('utf-8')
+
self.send_response(200)
self.send_header('Content-Type', 'text/html; charset=utf-8')
- self.send_header('Content-Length', len(html))
+ self.send_header('Content-Length', str(len(html)))
self.end_headers()
+
for chunk in self.get_chunks(html):
self.wfile.write(chunk)
+
elif stripped_path == '/ping':
self.send_error(204)
+
else:
self.send_error(501)
# noinspection PyPep8Naming
def do_HEAD(self):
- log_debug('HTTPServer: HEAD Request uri path |{proxy_path}|'.format(proxy_path=self.path))
+ log_debug('HTTPServer: HEAD Request uri path |{proxy_path}|'
+ .format(proxy_path=self.path))
if not self.connection_allowed():
self.send_error(403)
- else:
- addon = Addon('plugin.video.youtube')
- mpd_proxy_enabled = addon.getSetting('kodion.mpd.videos') == 'true' and addon.getSetting('kodion.video.quality.isa') == 'true'
- if mpd_proxy_enabled and self.path.endswith('.mpd'):
- file_path = os.path.join(self.base_path, self.path.strip('/').strip('\\'))
- if not os.path.isfile(file_path):
- response = 'File Not Found: |{proxy_path}| -> |{file_path}|'.format(proxy_path=self.path, file_path=file_path.encode('utf-8'))
- self.send_error(404, response)
- else:
- self.send_response(200)
- self.send_header('Content-Type', 'application/xml+dash')
- self.send_header('Content-Length', os.path.getsize(file_path))
- self.end_headers()
+ elif _settings.use_mpd_videos() and self.path.endswith('.mpd'):
+ file_path = os.path.join(self.base_path,
+ self.path.strip('/').strip('\\'))
+ if not os.path.isfile(file_path):
+ response = ('File Not Found: |{proxy_path}| -> |{file_path}|'
+ .format(proxy_path=self.path, file_path=file_path))
+ self.send_error(404, response)
else:
- self.send_error(501)
+ self.send_response(200)
+ self.send_header('Content-Type', 'application/xml+dash')
+ self.send_header('Content-Length',
+ str(os.path.getsize(file_path)))
+ self.end_headers()
+ else:
+ self.send_error(501)
# noinspection PyPep8Naming
def do_POST(self):
- log_debug('HTTPServer: Request uri path |{proxy_path}|'.format(proxy_path=self.path))
+ log_debug('HTTPServer: Request uri path |{proxy_path}|'
+ .format(proxy_path=self.path))
if not self.connection_allowed():
self.send_error(403)
elif self.path.startswith('/widevine'):
- license_url = xbmcgui.Window(10000).getProperty('plugin.video.youtube-license_url')
- license_token = xbmcgui.Window(10000).getProperty('plugin.video.youtube-license_token')
+ home = Window(10000)
- if not license_url:
+ lic_url = home.getProperty('plugin.video.youtube-license_url')
+ if not lic_url:
self.send_error(404)
return
- if not license_token:
+
+ lic_token = home.getProperty('plugin.video.youtube-license_token')
+ if not lic_token:
self.send_error(403)
return
@@ -200,29 +233,40 @@ def do_POST(self):
li_headers = {
'Content-Type': 'application/x-www-form-urlencoded',
- 'Authorization': 'Bearer %s' % license_token
+ 'Authorization': 'Bearer %s' % lic_token
}
- result = requests.post(url=license_url, headers=li_headers, data=post_data, stream=True)
+ response = _server_requests.request(lic_url,
+ method='POST',
+ headers=li_headers,
+ data=post_data,
+ stream=True)
- response_length = int(result.headers.get('content-length'))
- content = result.raw.read(response_length)
+ response_length = int(response.headers.get('content-length'))
+ content = response.raw.read(response_length)
content_split = content.split('\r\n\r\n'.encode('utf-8'))
response_header = content_split[0].decode('utf-8', 'ignore')
response_body = content_split[1]
- response_length = len(response_body)
- match = re.search(r'^Authorized-Format-Types:\s*(?P.+?)\r*$', response_header, re.MULTILINE)
+ match = re.search(r'^Authorized-Format-Types:\s*'
+ r'(?P.+?)\r*$',
+ response_header,
+ re.MULTILINE)
if match:
authorized_types = match.group('authorized_types').split(',')
- log_debug('HTTPServer: Found authorized formats |{authorized_fmts}|'.format(authorized_fmts=authorized_types))
-
- fmt_to_px = {'SD': (1280 * 528) - 1, 'HD720': 1280 * 720, 'HD': 7680 * 4320}
+ log_debug('HTTPServer: Found authorized formats |{auth_fmts}|'
+ .format(auth_fmts=authorized_types))
+
+ fmt_to_px = {
+ 'SD': (1280 * 528) - 1,
+ 'HD720': 1280 * 720,
+ 'HD': 7680 * 4320
+ }
if 'HD' in authorized_types:
size_limit = fmt_to_px['HD']
elif 'HD720' in authorized_types:
- if xbmc.getCondVisibility('system.platform.android') == 1:
+ if getCondVisibility('system.platform.android') == 1:
size_limit = fmt_to_px['HD720']
else:
size_limit = fmt_to_px['SD']
@@ -232,10 +276,11 @@ def do_POST(self):
self.send_response(200)
if size_limit:
- self.send_header('X-Limit-Video', 'max={size_limit}px'.format(size_limit=str(size_limit)))
- for header, value in result.headers.items():
+ self.send_header('X-Limit-Video',
+ 'max={0}px'.format(size_limit))
+ for header, value in response.headers.items():
if re.match('^[Cc]ontent-[Ll]ength$', header):
- self.send_header(header, response_length)
+ self.send_header(header, str(len(response_body)))
else:
self.send_header(header, value)
self.end_headers()
@@ -255,242 +300,265 @@ def get_chunks(self, data):
@staticmethod
def api_config_page():
- addon = Addon('plugin.video.youtube')
- i18n = addon.getLocalizedString
- api_key = addon.getSetting('youtube.api.key')
- api_id = addon.getSetting('youtube.api.id')
- api_secret = addon.getSetting('youtube.api.secret')
- html = Pages().api_configuration.get('html')
- css = Pages().api_configuration.get('css')
- html = html.format(css=css, title=i18n(30634), api_key_head=i18n(30201), api_id_head=i18n(30202),
- api_secret_head=i18n(30203), api_id_value=api_id, api_key_value=api_key,
- api_secret_value=api_secret, submit=i18n(30630), header=i18n(30634))
+ api_key = _settings.api_key()
+ api_id = _settings.api_id()
+ api_secret = _settings.api_secret()
+ html = Pages.api_configuration.get('html')
+ css = Pages.api_configuration.get('css')
+ html = html.format(
+ css=css,
+ title=_i18n(30634), # YouTube Add-on API Configuration
+ api_key_head=_i18n(30201), # API Key
+ api_id_head=_i18n(30202), # API ID
+ api_secret_head=_i18n(30203), # API Secret
+ api_id_value=api_id,
+ api_key_value=api_key,
+ api_secret_value=api_secret,
+ submit=_i18n(30630), # Save
+ header=_i18n(30634), # YouTube Add-on API Configuration
+ )
return html
@staticmethod
def api_submit_page(updated_keys, enabled, footer):
- addon = Addon('plugin.video.youtube')
- i18n = addon.getLocalizedString
- html = Pages().api_submit.get('html')
- css = Pages().api_submit.get('css')
- html = html.format(css=css, title=i18n(30634), updated=updated_keys, enabled=enabled, footer=footer, header=i18n(30634))
+ html = Pages.api_submit.get('html')
+ css = Pages.api_submit.get('css')
+ html = html.format(
+ css=css,
+ title=_i18n(30634), # YouTube Add-on API Configuration
+ updated=updated_keys,
+ enabled=enabled,
+ footer=footer,
+ header=_i18n(30634), # YouTube Add-on API Configuration
+ )
return html
class Pages(object):
api_configuration = {
- 'html':
- '\n\n'
- '\n\t\n'
- '\t{title}\n'
- '\t\n'
- '\n\n'
- '\t\n'
- '\t
{header}
\n'
- '\t\n'
- '\t\n'
- '\n',
-
- 'css':
- 'body {\n'
- ' background: #141718;\n'
- '}\n'
- '.center {\n'
- ' margin: auto;\n'
- ' width: 600px;\n'
- ' padding: 10px;\n'
- '}\n'
- '.config_form {\n'
- ' width: 575px;\n'
- ' height: 145px;\n'
- ' font-size: 16px;\n'
- ' background: #1a2123;\n'
- ' padding: 30px 30px 15px 30px;\n'
- ' border: 5px solid #1a2123;\n'
- '}\n'
- 'h5 {\n'
- ' font-family: Arial, Helvetica, sans-serif;\n'
- ' font-size: 16px;\n'
- ' color: #fff;\n'
- ' font-weight: 600;\n'
- ' width: 575px;\n'
- ' height: 20px;\n'
- ' background: #0f84a5;\n'
- ' padding: 5px 30px 5px 30px;\n'
- ' border: 5px solid #0f84a5;\n'
- ' margin: 0px;\n'
- '}\n'
- '.config_form input[type=submit],\n'
- '.config_form input[type=button],\n'
- '.config_form input[type=text],\n'
- '.config_form textarea,\n'
- '.config_form label {\n'
- ' font-family: Arial, Helvetica, sans-serif;\n'
- ' font-size: 16px;\n'
- ' color: #fff;\n'
- '}\n'
- '.config_form label {\n'
- ' display:block;\n'
- ' margin-bottom: 10px;\n'
- '}\n'
- '.config_form label > span {\n'
- ' display: inline-block;\n'
- ' float: left;\n'
- ' width: 150px;\n'
- '}\n'
- '.config_form input[type=text] {\n'
- ' background: transparent;\n'
- ' border: none;\n'
- ' border-bottom: 1px solid #147a96;\n'
- ' width: 400px;\n'
- ' outline: none;\n'
- ' padding: 0px 0px 0px 0px;\n'
- '}\n'
- '.config_form input[type=text]:focus {\n'
- ' border-bottom: 1px dashed #0f84a5;\n'
- '}\n'
- '.config_form input[type=submit],\n'
- '.config_form input[type=button] {\n'
- ' width: 150px;\n'
- ' background: #141718;\n'
- ' border: none;\n'
- ' padding: 8px 0px 8px 10px;\n'
- ' border-radius: 5px;\n'
- ' color: #fff;\n'
- ' margin-top: 10px\n'
- '}\n'
- '.config_form input[type=submit]:hover,\n'
- '.config_form input[type=button]:hover {\n'
- ' background: #0f84a5;\n'
- '}\n'
+ 'html': dedent('''\
+
+
+
+
+
+ {title}
+
+
+
+
+
{header}
+
+
+
+
+ '''),
+ 'css': ''.join('\t\t\t'.expandtabs(2) + line for line in dedent('''
+ body {
+ background: #141718;
+ }
+ .center {
+ margin: auto;
+ width: 600px;
+ padding: 10px;
+ }
+ .config_form {
+ width: 575px;
+ height: 145px;
+ font-size: 16px;
+ background: #1a2123;
+ padding: 30px 30px 15px 30px;
+ border: 5px solid #1a2123;
+ }
+ h5 {
+ font-family: Arial, Helvetica, sans-serif;
+ font-size: 16px;
+ color: #fff;
+ font-weight: 600;
+ width: 575px;
+ height: 20px;
+ background: #0f84a5;
+ padding: 5px 30px 5px 30px;
+ border: 5px solid #0f84a5;
+ margin: 0px;
+ }
+ .config_form input[type=submit],
+ .config_form input[type=button],
+ .config_form input[type=text],
+ .config_form textarea,
+ .config_form label {
+ font-family: Arial, Helvetica, sans-serif;
+ font-size: 16px;
+ color: #fff;
+ }
+ .config_form label {
+ display:block;
+ margin-bottom: 10px;
+ }
+ .config_form label > span {
+ display: inline-block;
+ float: left;
+ width: 150px;
+ }
+ .config_form input[type=text] {
+ background: transparent;
+ border: none;
+ border-bottom: 1px solid #147a96;
+ width: 400px;
+ outline: none;
+ padding: 0px 0px 0px 0px;
+ }
+ .config_form input[type=text]:focus {
+ border-bottom: 1px dashed #0f84a5;
+ }
+ .config_form input[type=submit],
+ .config_form input[type=button] {
+ width: 150px;
+ background: #141718;
+ border: 1px solid #147a96;
+ padding: 8px 0px 8px 10px;
+ border-radius: 5px;
+ color: #fff;
+ margin-top: 10px
+ }
+ .config_form input[type=submit]:hover,
+ .config_form input[type=button]:hover {
+ background: #0f84a5;
+ }
+ ''').splitlines(True)) + '\t\t'.expandtabs(2)
}
api_submit = {
- 'html':
- '\n\n'
- '\n\t\n'
- '\t{title}\n'
- '\t\n'
- '\n\n'
- '\t\n'
- '\t
{header}
\n'
- '\t
\n'
- '\t\t
{updated}\n'
- '\t\t
{enabled}\n'
- '\t\t
\n'
- '\t\t
\n'
- '\t\t
\n'
- '\t\t
\n'
- '\t\t
\n'
- '\t\t\t{footer}\n'
- '\t\t
\n'
- '\t
\n'
- '\t
\n'
- '\n',
-
- 'css':
- 'body {\n'
- ' background: #141718;\n'
- '}\n'
- '.center {\n'
- ' margin: auto;\n'
- ' width: 600px;\n'
- ' padding: 10px;\n'
- '}\n'
- '.textcenter {\n'
- ' margin: auto;\n'
- ' width: 600px;\n'
- ' padding: 10px;\n'
- ' text-align: center;\n'
- '}\n'
- '.content {\n'
- ' width: 575px;\n'
- ' height: 145px;\n'
- ' background: #1a2123;\n'
- ' padding: 30px 30px 15px 30px;\n'
- ' border: 5px solid #1a2123;\n'
- '}\n'
- 'h5 {\n'
- ' font-family: Arial, Helvetica, sans-serif;\n'
- ' font-size: 16px;\n'
- ' color: #fff;\n'
- ' font-weight: 600;\n'
- ' width: 575px;\n'
- ' height: 20px;\n'
- ' background: #0f84a5;\n'
- ' padding: 5px 30px 5px 30px;\n'
- ' border: 5px solid #0f84a5;\n'
- ' margin: 0px;\n'
- '}\n'
- 'span {\n'
- ' font-family: Arial, Helvetica, sans-serif;\n'
- ' font-size: 16px;\n'
- ' color: #fff;\n'
- ' display: block;\n'
- ' float: left;\n'
- ' width: 575px;\n'
- '}\n'
- 'small {\n'
- ' font-family: Arial, Helvetica, sans-serif;\n'
- ' font-size: 12px;\n'
- ' color: #fff;\n'
- '}\n'
+ 'html': dedent('''\
+
+
+
+
+
+ {title}
+
+
+
+
+
{header}
+
+
{updated}
+
{enabled}
+
+ {footer}
+
+
+
+
+
+ '''),
+ 'css': ''.join('\t\t\t'.expandtabs(2) + line for line in dedent('''
+ body {
+ background: #141718;
+ }
+ .center {
+ margin: auto;
+ width: 600px;
+ padding: 10px;
+ }
+ .text_center {
+ margin: 2em auto auto;
+ width: 600px;
+ padding: 10px;
+ text-align: center;
+ }
+ .content {
+ width: 575px;
+ height: 145px;
+ background: #1a2123;
+ padding: 30px 30px 15px 30px;
+ border: 5px solid #1a2123;
+ }
+ h5 {
+ font-family: Arial, Helvetica, sans-serif;
+ font-size: 16px;
+ color: #fff;
+ font-weight: 600;
+ width: 575px;
+ height: 20px;
+ background: #0f84a5;
+ padding: 5px 30px 5px 30px;
+ border: 5px solid #0f84a5;
+ margin: 0px;
+ }
+ p {
+ font-family: Arial, Helvetica, sans-serif;
+ font-size: 16px;
+ color: #fff;
+ float: left;
+ width: 575px;
+ margin: 0.5em auto;
+ }
+ small {
+ font-family: Arial, Helvetica, sans-serif;
+ font-size: 12px;
+ color: #fff;
+ }
+ ''').splitlines(True)) + '\t\t'.expandtabs(2)
}
def get_http_server(address=None, port=None):
- addon = Addon(_addon_id)
- address = address if address else addon.getSetting('kodion.http.listen')
- address = address if re.match(r'^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$', address) else '0.0.0.0'
- port = int(port) if port else 50152
+ address = _settings.httpd_listen(for_request=False, ip_address=address)
+ port = _settings.httpd_port(port)
try:
- server = BaseHTTPServer.HTTPServer((address, port), YouTubeProxyRequestHandler)
+ server = BaseHTTPServer.HTTPServer((address, port),
+ YouTubeProxyRequestHandler)
return server
- except socket.error as e:
- log_debug('HTTPServer: Failed to start |{address}:{port}| |{response}|'.format(address=address, port=port, response=str(e)))
- xbmcgui.Dialog().notification(addon.getAddonInfo('name'), str(e),
- addon.getAddonInfo('icon'),
- 5000, False)
+ except socket_error as e:
+ log_debug('HTTPServer: Failed to start |{address}:{port}| |{response}|'
+ .format(address=address, port=port, response=str(e)))
+ Dialog().notification(_addon.getAddonInfo('name'),
+ str(e),
+ _addon.getAddonInfo('icon'),
+ time=5000,
+ sound=False)
return None
def is_httpd_live(address=None, port=None):
- addon = Addon(_addon_id)
- address = address if address else addon.getSetting('kodion.http.listen')
- address = '127.0.0.1' if address == '0.0.0.0' else address
- address = address if re.match(r'^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$', address) else '127.0.0.1'
- port = int(port) if port else 50152
+ address = _settings.httpd_listen(for_request=True, ip_address=address)
+ port = _settings.httpd_port(port=port)
url = 'http://{address}:{port}/ping'.format(address=address, port=port)
try:
- response = requests.get(url)
+ response = _server_requests.request(url)
result = response.status_code == 204
if not result:
- log_debug('HTTPServer: Ping |{address}:{port}| |{response}|'.format(address=address, port=port, response=response.status_code))
+ log_debug('HTTPServer: Ping |{address}:{port}| |{response}|'
+ .format(address=address,
+ port=port,
+ response=response.status_code))
return result
except:
- log_debug('HTTPServer: Ping |{address}:{port}| |{response}|'.format(address=address, port=port, response='failed'))
+ log_debug('HTTPServer: Ping |{address}:{port}| |{response}|'
+ .format(address=address, port=port, response='failed'))
return False
def get_client_ip_address(address=None, port=None):
- addon = Addon(_addon_id)
- address = address if address else addon.getSetting('kodion.http.listen')
- address = '127.0.0.1' if address == '0.0.0.0' else address
- address = address if re.match(r'^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$', address) else '127.0.0.1'
- port = int(port) if port else 50152
+ address = _settings.httpd_listen(for_request=True, ip_address=address)
+ port = _settings.httpd_port(port=port)
url = 'http://{address}:{port}/client_ip'.format(address=address, port=port)
- response = requests.get(url)
+ response = _server_requests.request(url)
ip_address = None
if response.status_code == 200:
response_json = response.json()
diff --git a/resources/lib/youtube_plugin/kodion/settings/abstract_settings.py b/resources/lib/youtube_plugin/kodion/settings/abstract_settings.py
index 79a32cd6b..e5cabea63 100644
--- a/resources/lib/youtube_plugin/kodion/settings/abstract_settings.py
+++ b/resources/lib/youtube_plugin/kodion/settings/abstract_settings.py
@@ -173,7 +173,7 @@ def use_mpd_live_streams(self):
def httpd_port(self, port=None):
default_port = 50152
- if not port:
+ if port is None:
port = self.get_int(SETTINGS.HTTPD_PORT, default_port)
try:
diff --git a/resources/lib/youtube_plugin/kodion/utils/monitor.py b/resources/lib/youtube_plugin/kodion/utils/monitor.py
index 96cb77bea..1cd981580 100644
--- a/resources/lib/youtube_plugin/kodion/utils/monitor.py
+++ b/resources/lib/youtube_plugin/kodion/utils/monitor.py
@@ -14,37 +14,41 @@
from urllib.parse import unquote
import xbmc
-import xbmcaddon
import xbmcvfs
+from xbmcaddon import Addon
+from ..logger import log_debug
from ..network import get_http_server, is_httpd_live
-from .. import logger
+from ..settings import Settings
class YouTubeMonitor(xbmc.Monitor):
+ _addon_id = 'plugin.video.youtube'
+ _addon = Addon(_addon_id)
+ _settings = Settings(_addon)
# noinspection PyUnusedLocal,PyMissingConstructor
def __init__(self, *args, **kwargs):
- self.addon_id = 'plugin.video.youtube'
- addon = xbmcaddon.Addon(self.addon_id)
- self._whitelist = addon.getSetting('kodion.http.ip.whitelist')
- self._httpd_port = int(addon.getSetting('kodion.mpd.proxy.port'))
- self._old_httpd_port = self._httpd_port
- self._use_httpd = (addon.getSetting('kodion.mpd.videos') == 'true' and addon.getSetting('kodion.video.quality.isa') == 'true') or \
- (addon.getSetting('youtube.api.config.page') == 'true')
- self._httpd_address = addon.getSetting('kodion.http.listen')
- self._old_httpd_address = self._httpd_address
+ settings = self._settings
+ self._whitelist = settings.httpd_whitelist()
+ self._old_httpd_port = self._httpd_port = int(settings.httpd_port())
+ self._use_httpd = (settings.use_mpd_videos()
+ or settings.api_config_page())
+ self._old_httpd_address = self._httpd_address = settings.httpd_listen()
self.httpd = None
self.httpd_thread = None
if self.use_httpd():
self.start_httpd()
- del addon
+ super(YouTubeMonitor, self).__init__()
def onNotification(self, sender, method, data):
- if sender == 'plugin.video.youtube' and method.endswith('.check_settings'):
- data = json.loads(data)
- data = json.loads(unquote(data[0]))
- logger.log_debug('onNotification: |check_settings| -> |%s|' % json.dumps(data))
+ if (sender == 'plugin.video.youtube'
+ and method.endswith('.check_settings')):
+ if not isinstance(data, dict):
+ data = json.loads(data)
+ data = json.loads(unquote(data[0]))
+ log_debug('onNotification: |check_settings| -> |{data}|'
+ .format(data=data))
_use_httpd = data.get('use_httpd')
_httpd_port = data.get('httpd_port')
@@ -55,32 +59,48 @@ def onNotification(self, sender, method, data):
port_changed = self._httpd_port != _httpd_port
address_changed = self._httpd_address != _httpd_address
- if _whitelist != self._whitelist:
+ if whitelist_changed:
self._whitelist = _whitelist
if self._use_httpd != _use_httpd:
self._use_httpd = _use_httpd
- if self._httpd_port != _httpd_port:
+ if port_changed:
self._old_httpd_port = self._httpd_port
self._httpd_port = _httpd_port
- if self._httpd_address != _httpd_address:
+ if address_changed:
self._old_httpd_address = self._httpd_address
self._httpd_address = _httpd_address
- if self.use_httpd() and not self.httpd:
+ if not _use_httpd:
+ if self.httpd:
+ self.shutdown_httpd()
+ elif not self.httpd:
self.start_httpd()
- elif self.use_httpd() and (port_changed or whitelist_changed or address_changed):
+ elif port_changed or whitelist_changed or address_changed:
if self.httpd:
self.restart_httpd()
else:
self.start_httpd()
- elif not self.use_httpd() and self.httpd:
- self.shutdown_httpd()
elif sender == 'plugin.video.youtube':
- logger.log_debug('onNotification: |unknown method|')
+ log_debug('onNotification: |unhandled method| -> |{method}|'
+ .format(method=method))
+
+ def onSettingsChanged(self):
+ YouTubeMonitor._addon = Addon(self._addon_id)
+ YouTubeMonitor._settings = Settings(self._addon)
+ data = {
+ 'use_httpd': (self._settings.use_mpd_videos()
+ or self._settings.api_config_page()),
+ 'httpd_port': self._settings.httpd_port(),
+ 'whitelist': self._settings.httpd_whitelist(),
+ 'httpd_address': self._settings.httpd_listen()
+ }
+ self.onNotification('plugin.video.youtube',
+ 'Other.check_settings',
+ data)
def use_httpd(self):
return self._use_httpd
@@ -104,12 +124,11 @@ def start_httpd(self):
if self.httpd:
return
- logger.log_debug('HTTPServer: Starting |{ip}:{port}|'.format(
- ip=self.httpd_address(),
- port=str(self.httpd_port())
- ))
+ log_debug('HTTPServer: Starting |{ip}:{port}|'
+ .format(ip=self.httpd_address(), port=str(self.httpd_port())))
self.httpd_port_sync()
- self.httpd = get_http_server(address=self.httpd_address(), port=self.httpd_port())
+ self.httpd = get_http_server(address=self.httpd_address(),
+ port=self.httpd_port())
if not self.httpd:
return
@@ -117,15 +136,16 @@ def start_httpd(self):
self.httpd_thread.daemon = True
self.httpd_thread.start()
sock_name = self.httpd.socket.getsockname()
- logger.log_debug('HTTPServer: Serving on |{ip}:{port}|'.format(
+ log_debug('HTTPServer: Serving on |{ip}:{port}|'.format(
ip=str(sock_name[0]),
port=str(sock_name[1])
))
def shutdown_httpd(self):
if self.httpd:
- logger.log_debug('HTTPServer: Shutting down |{ip}:{port}|'.format(ip=self.old_httpd_address(),
- port=str(self.old_httpd_port())))
+ log_debug('HTTPServer: Shutting down |{ip}:{port}|'
+ .format(ip=self.old_httpd_address(),
+ port=self.old_httpd_port()))
self.httpd_port_sync()
self.httpd.shutdown()
self.httpd.socket.close()
@@ -134,9 +154,11 @@ def shutdown_httpd(self):
self.httpd = None
def restart_httpd(self):
- logger.log_debug('HTTPServer: Restarting... |{old_ip}:{old_port}| -> |{ip}:{port}|'
- .format(old_ip=self.old_httpd_address(), old_port=str(self.old_httpd_port()),
- ip=self.httpd_address(), port=str(self.httpd_port())))
+ log_debug('HTTPServer: Restarting |{old_ip}:{old_port}| > |{ip}:{port}|'
+ .format(old_ip=self.old_httpd_address(),
+ old_port=self.old_httpd_port(),
+ ip=self.httpd_address(),
+ port=self.httpd_port()))
self.shutdown_httpd()
self.start_httpd()
@@ -158,6 +180,8 @@ def remove_temp_dir(self):
pass
if os.path.isdir(path):
- logger.log_debug('Failed to remove directory: {dir}'.format(dir=path.encode('utf-8')))
+ log_debug('Failed to remove directory: {path}'.format(
+ path=path
+ ))
return False
return True
diff --git a/resources/lib/youtube_plugin/youtube/client/__config__.py b/resources/lib/youtube_plugin/youtube/client/__config__.py
index ed780b264..c52bc2757 100644
--- a/resources/lib/youtube_plugin/youtube/client/__config__.py
+++ b/resources/lib/youtube_plugin/youtube/client/__config__.py
@@ -206,7 +206,6 @@ def _strip_api_keys(self, api_key, client_id, client_secret):
notification_data = {'use_httpd': (__settings.use_mpd_videos()
- or __settings.use_mpd_live_streams()
or __settings.api_config_page()),
'httpd_port': __settings.httpd_port(),
'whitelist': __settings.httpd_whitelist(),
diff --git a/resources/settings.xml b/resources/settings.xml
index 4c06c207f..f13b761e1 100644
--- a/resources/settings.xml
+++ b/resources/settings.xml
@@ -790,9 +790,13 @@
true
-
+
0
50152
+
+ 0
+ 65535
+
true
From 33371f97fc907c1b0a6541e63df74170e8764ca4 Mon Sep 17 00:00:00 2001
From: MoojMidge <56883549+MoojMidge@users.noreply.github.com>
Date: Thu, 7 Dec 2023 16:16:19 +1100
Subject: [PATCH 062/141] Fix regressions in live channels following 72480b5
---
.../youtube/helper/url_resolver.py | 12 +-
.../youtube/helper/url_to_item_converter.py | 115 ++++++++++--------
.../lib/youtube_plugin/youtube/provider.py | 2 +-
3 files changed, 73 insertions(+), 56 deletions(-)
diff --git a/resources/lib/youtube_plugin/youtube/helper/url_resolver.py b/resources/lib/youtube_plugin/youtube/helper/url_resolver.py
index 3033bf834..0a28d1f93 100644
--- a/resources/lib/youtube_plugin/youtube/helper/url_resolver.py
+++ b/resources/lib/youtube_plugin/youtube/helper/url_resolver.py
@@ -97,7 +97,7 @@ def supports_url(self, url, url_components):
return 'GET' if len(path) == 1 and path[0] else False
def resolve(self, url, url_components, method='HEAD'):
- path = url_components.path.lower()
+ path = url_components.path.rstrip('/').lower()
if path == '/redirect':
params = dict(parse_qsl(url_components.query))
url = params['q']
@@ -171,7 +171,15 @@ def resolve(self, url, url_components, method='HEAD'):
elif method == 'GET':
match = self._RE_CHANNEL_URL.search(response.text)
if match:
- return match.group('channel_url')
+ url = match.group('channel_url')
+ if path.endswith(('/live', '/streams')):
+ url_components = urlparse(unescape(url))
+ params = dict(parse_qsl(url_components.query))
+ params['live'] = 1
+ return url_components._replace(
+ query=urlencode(params)
+ ).geturl()
+ return url
return response.url
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 6b564163d..dbe3f50be 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
@@ -17,7 +17,7 @@
class UrlToItemConverter(object):
- RE_PATH_ID = re.compile(r'/\w+/(?P[^/?#]+)', re.I)
+ RE_PATH_ID = re.compile(r'/[^/]+/(?P[^/?#]+)', re.I)
VALID_HOSTNAMES = {
'youtube.com',
'www.youtube.com',
@@ -62,13 +62,13 @@ def add_url(self, url, provider, context):
}
path = parsed_url.path.rstrip('/').lower()
- channel_id = video_id = None
if path.startswith(('/playlist', '/watch')):
pass
elif path.startswith('/channel/'):
re_match = self.RE_PATH_ID.match(parsed_url.path)
- channel_id = re_match.group('id')
- if path.endswith(('/live', '/streams')):
+ new_params['channel_id'] = re_match.group('id')
+ if ('live' not in new_params
+ and path.endswith(('/live', '/streams'))):
new_params['live'] = 1
elif path.startswith(('/clip/', '/embed/', '/live/', '/shorts/')):
re_match = self.RE_PATH_ID.match(parsed_url.path)
@@ -81,6 +81,7 @@ def add_url(self, url, provider, context):
if 'video_id' in new_params:
video_id = new_params['video_id']
+
video_item = VideoItem(
'', context.create_uri(['play'], new_params)
)
@@ -88,6 +89,7 @@ def add_url(self, url, provider, context):
elif 'playlist_id' in new_params:
playlist_id = new_params['playlist_id']
+
if self._flatten:
self._playlist_ids.append(playlist_id)
return
@@ -98,12 +100,17 @@ def add_url(self, url, provider, context):
playlist_item.set_fanart(provider.get_fanart(context))
self._playlist_id_dict[playlist_id] = playlist_item
- elif channel_id:
- if self._flatten:
+ elif 'channel_id' in new_params:
+ channel_id = new_params['channel_id']
+ live = new_params.get('live')
+
+ if not live and self._flatten:
self._channel_ids.append(channel_id)
return
- channel_item = DirectoryItem(
+ channel_item = VideoItem(
+ '', context.create_uri(['play'], new_params)
+ ) if live else DirectoryItem(
'', context.create_uri(['channel', channel_id], new_params)
)
channel_item.set_fanart(provider.get_fanart(context))
@@ -116,10 +123,10 @@ def add_urls(self, urls, provider, context):
for url in urls:
self.add_url(url, provider, context)
- def get_items(self, provider, context, title_required=True):
+ def get_items(self, provider, context, skip_title=False):
result = []
- if self._flatten and self._channel_ids:
+ if self._channel_ids:
# remove duplicates
self._channel_ids = list(set(self._channel_ids))
@@ -133,7 +140,7 @@ def get_items(self, provider, context, title_required=True):
channels_item.set_fanart(provider.get_fanart(context))
result.append(channels_item)
- if self._flatten and self._playlist_ids:
+ if self._playlist_ids:
# remove duplicates
self._playlist_ids = list(set(self._playlist_ids))
@@ -153,61 +160,63 @@ def get_items(self, provider, context, title_required=True):
playlists_item.set_fanart(provider.get_fanart(context))
result.append(playlists_item)
- if not self._flatten:
- result.extend(self.get_channel_items(provider, context))
+ if self._channel_id_dict:
+ result += self.get_channel_items(provider, context, skip_title)
- if not self._flatten:
- result.extend(self.get_playlist_items(provider, context))
+ if self._playlist_id_dict:
+ result += self.get_playlist_items(provider, context, skip_title)
- # add videos
- result.extend(self.get_video_items(provider, context, title_required))
+ if self._video_id_dict:
+ result += self.get_video_items(provider, context, skip_title)
return result
- def get_video_items(self, provider, context, title_required=True):
- incognito = context.get_param('incognito', False)
- use_play_data = not incognito
+ def get_video_items(self, provider, context, skip_title=False):
+ if self._video_items:
+ return self._video_items
- if not self._video_items:
- 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_fanarts(provider, context, channel_id_dict)
+ use_play_data = not context.get_param('incognito', False)
- self._video_items = [
- video_item
- for video_item in self._video_id_dict.values()
- if not title_required or video_item.get_title()
- ]
+ 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_fanarts(provider, context, channel_id_dict)
+ self._video_items = [
+ video_item
+ for video_item in self._video_id_dict.values()
+ if skip_title or video_item.get_title()
+ ]
return self._video_items
- def get_playlist_items(self, provider, context):
- if not self._playlist_items:
- channel_id_dict = {}
- utils.update_playlist_infos(provider, context,
- self._playlist_id_dict,
- channel_items_dict=channel_id_dict)
- utils.update_fanarts(provider, context, channel_id_dict)
-
- self._playlist_items = [
- playlist_item
- for playlist_item in self._playlist_id_dict.values()
- if playlist_item.get_name()
- ]
-
+ def get_playlist_items(self, provider, context, skip_title=False):
+ if self._playlist_items:
+ return self._playlist_items
+
+ channel_id_dict = {}
+ utils.update_playlist_infos(provider, context,
+ self._playlist_id_dict,
+ channel_items_dict=channel_id_dict)
+ utils.update_fanarts(provider, context, channel_id_dict)
+
+ self._playlist_items = [
+ playlist_item
+ for playlist_item in self._playlist_id_dict.values()
+ if skip_title or playlist_item.get_title()
+ ]
return self._playlist_items
- def get_channel_items(self, provider, context):
- if not self._channel_items:
- channel_id_dict = {}
- utils.update_fanarts(provider, context, channel_id_dict)
+ def get_channel_items(self, provider, context, skip_title=False):
+ if self._channel_items:
+ return self._channel_items
- self._channel_items = [
- channel_item
- for channel_item in self._channel_id_dict.values()
- if channel_item.get_name()
- ]
+ channel_id_dict = {}
+ utils.update_fanarts(provider, context, channel_id_dict)
+ self._channel_items = [
+ channel_item
+ for channel_item in self._channel_id_dict.values()
+ if skip_title or channel_item.get_title()
+ ]
return self._channel_items
diff --git a/resources/lib/youtube_plugin/youtube/provider.py b/resources/lib/youtube_plugin/youtube/provider.py
index fa87b85bb..1baa3caf4 100644
--- a/resources/lib/youtube_plugin/youtube/provider.py
+++ b/resources/lib/youtube_plugin/youtube/provider.py
@@ -272,7 +272,7 @@ def on_uri2addon(self, context, re_match):
res_url = resolver.resolve(uri)
url_converter = UrlToItemConverter(flatten=True)
url_converter.add_url(res_url, self, context)
- items = url_converter.get_items(self, context, title_required=False)
+ items = url_converter.get_items(self, context, skip_title=True)
if items:
return items[0]
From 6115f9073a5a608b4618e7cfe493d4f13b6a1506 Mon Sep 17 00:00:00 2001
From: MoojMidge <56883549+MoojMidge@users.noreply.github.com>
Date: Thu, 7 Dec 2023 16:21:28 +1100
Subject: [PATCH 063/141] Remove duplicate code in VideoInfo._get_error_details
---
.../youtube/helper/video_info.py | 24 +++----------------
1 file changed, 3 insertions(+), 21 deletions(-)
diff --git a/resources/lib/youtube_plugin/youtube/helper/video_info.py b/resources/lib/youtube_plugin/youtube/helper/video_info.py
index 580f722b3..12c9d50bb 100644
--- a/resources/lib/youtube_plugin/youtube/helper/video_info.py
+++ b/resources/lib/youtube_plugin/youtube/helper/video_info.py
@@ -986,8 +986,7 @@ def _process_url_params(self, url):
urlencode(query, doseq=True),
parts.fragment))
- @staticmethod
- def _get_error_details(playability_status, details=None):
+ def _get_error_details(self, playability_status, details=None):
if not playability_status:
return None
if not details:
@@ -997,26 +996,9 @@ def _get_error_details(playability_status, details=None):
('reason', 'title')
)
- result = playability_status
- for keys in details:
- is_dict = isinstance(result, dict)
- if not is_dict and not isinstance(result, list):
- return None
-
- if not isinstance(keys, (list, tuple)):
- keys = [keys]
- for key in keys:
- if is_dict:
- if key not in result:
- continue
- elif not isinstance(key, int) or len(result) <= key:
- continue
- result = result[key]
- break
- else:
- return None
+ result = self.json_traverse(playability_status, details)
- if 'runs' not in result:
+ if not result or 'runs' not in result:
return result
detail_texts = [
From 000b415d3d81dc6a95e1572ae21f24dbbd496d3c Mon Sep 17 00:00:00 2001
From: MoojMidge <56883549+MoojMidge@users.noreply.github.com>
Date: Thu, 7 Dec 2023 17:48:53 +1100
Subject: [PATCH 064/141] Update LoginClient and YouTubeRequestClient
- Update formatting
- Use LoginException as default exception type for LoginClient
- Fix for possible undefined instance variables
- TODO: use build_client for LoginClient
---
.../youtube/client/login_client.py | 204 +++++++++++-------
.../youtube/client/request_client.py | 17 +-
2 files changed, 132 insertions(+), 89 deletions(-)
diff --git a/resources/lib/youtube_plugin/youtube/client/login_client.py b/resources/lib/youtube_plugin/youtube/client/login_client.py
index f713e16ea..70edd8319 100644
--- a/resources/lib/youtube_plugin/youtube/client/login_client.py
+++ b/resources/lib/youtube_plugin/youtube/client/login_client.py
@@ -31,6 +31,21 @@
class LoginClient(YouTubeRequestClient):
api_keys_changed = keys_changed
+ ANDROID_CLIENT_AUTH_URL = 'https://android.clients.google.com/auth'
+ DEVICE_CODE_URL = 'https://accounts.google.com/o/oauth2/device/code'
+ REVOKE_URL = 'https://accounts.google.com/o/oauth2/revoke'
+ SERVICE_URLS = 'oauth2:' + 'https://www.googleapis.com/auth/'.join((
+ 'youtube '
+ 'youtube.force-ssl '
+ 'plus.me '
+ 'emeraldsea.mobileapps.doritos.cookie '
+ 'plus.stream.read '
+ 'plus.stream.write '
+ 'plus.pages.manage '
+ 'identity.plus.page.impersonation',
+ ))
+ TOKEN_URL = 'https://www.googleapis.com/oauth2/v4/token'
+
CONFIGS = {
'youtube-tv': {
'system': 'YouTube TV',
@@ -63,24 +78,23 @@ def __init__(self, config=None, language='en-US', region='',
self._log_error_callback = None
- super(LoginClient, self).__init__()
+ super(LoginClient, self).__init__(exc_type=LoginException)
@staticmethod
def _login_json_hook(response):
- json_data = None
try:
json_data = response.json()
if 'error' in json_data:
raise YouTubeException('"error" in response JSON data',
json_data=json_data,
- response=response,)
+ response=response)
except ValueError as error:
raise InvalidJSONError(error, response=response)
response.raise_for_status()
return json_data
@staticmethod
- def _login_error_hook(error, response):
+ def _login_error_hook(error, _response):
json_data = getattr(error, 'json_data', None)
if not json_data:
return None, None, None, None, LoginException
@@ -112,29 +126,36 @@ def set_access_token_tv(self, access_token_tv=''):
def revoke(self, refresh_token):
# https://developers.google.com/youtube/v3/guides/auth/devices
headers = {'Host': 'accounts.google.com',
- 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.100 Safari/537.36',
+ 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)'
+ ' AppleWebKit/537.36 (KHTML, like Gecko)'
+ ' Chrome/61.0.3163.100 Safari/537.36',
'Content-Type': 'application/x-www-form-urlencoded'}
post_data = {'token': refresh_token}
- self.request('https://accounts.google.com/o/oauth2/revoke',
- method='POST', data=post_data, headers=headers,
- response_hook=self._login_json_hook,
- error_hook=self._login_error_hook,
- error_title='Logout Failed',
- error_info='Revoke failed: {exc}',
- raise_exc=LoginException
- )
+ self.request(self.REVOKE_URL,
+ method='POST',
+ data=post_data,
+ headers=headers,
+ response_hook=self._login_json_hook,
+ error_hook=self._login_error_hook,
+ error_title='Logout Failed',
+ error_info='Revoke failed: {exc}',
+ raise_exc=True)
def refresh_token_tv(self, refresh_token):
client_id = str(self.CONFIGS['youtube-tv']['id'])
client_secret = str(self.CONFIGS['youtube-tv']['secret'])
- return self.refresh_token(refresh_token, client_id=client_id, client_secret=client_secret)
+ return self.refresh_token(refresh_token,
+ client_id=client_id,
+ client_secret=client_secret)
def refresh_token(self, refresh_token, client_id='', client_secret=''):
# https://developers.google.com/youtube/v3/guides/auth/devices
headers = {'Host': 'www.googleapis.com',
- 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.100 Safari/537.36',
+ 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)'
+ ' AppleWebKit/537.36 (KHTML, like Gecko)'
+ ' Chrome/61.0.3163.100 Safari/537.36',
'Content-Type': 'application/x-www-form-urlencoded'}
client_id = client_id or self._config['id']
@@ -145,21 +166,24 @@ def refresh_token(self, refresh_token, client_id='', client_secret=''):
'grant_type': 'refresh_token'}
config_type = self._get_config_type(client_id, client_secret)
- client_summary = ''.join([
+ client = ''.join([
'(config_type: |', config_type, '|',
' client_id: |', client_id[:5], '...|',
' client_secret: |', client_secret[:5], '...|)'
])
- log_debug('Refresh token for ' + client_summary)
-
- json_data = self.request('https://www.googleapis.com/oauth2/v4/token',
- method='POST', data=post_data, headers=headers,
- response_hook=self._login_json_hook,
- error_hook=self._login_error_hook,
- error_title='Login Failed',
- error_info='Refresh failed for ' + client_summary + ': {exc}',
- raise_exc=LoginException
- )
+ log_debug('Refresh token for {0}'.format(client))
+
+ json_data = self.request(self.TOKEN_URL,
+ method='POST',
+ data=post_data,
+ headers=headers,
+ response_hook=self._login_json_hook,
+ error_hook=self._login_error_hook,
+ error_title='Login Failed',
+ error_info=('Refresh token failed'
+ ' {client}: {{exc}}'
+ .format(client=client)),
+ raise_exc=True)
if json_data:
access_token = json_data['access_token']
@@ -170,12 +194,16 @@ def refresh_token(self, refresh_token, client_id='', client_secret=''):
def request_access_token_tv(self, code, client_id='', client_secret=''):
client_id = client_id or self.CONFIGS['youtube-tv']['id']
client_secret = client_secret or self.CONFIGS['youtube-tv']['secret']
- return self.request_access_token(code, client_id=client_id, client_secret=client_secret)
+ return self.request_access_token(code,
+ client_id=client_id,
+ client_secret=client_secret)
def request_access_token(self, code, client_id='', client_secret=''):
# https://developers.google.com/youtube/v3/guides/auth/devices
headers = {'Host': 'www.googleapis.com',
- 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.100 Safari/537.36',
+ 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)'
+ ' AppleWebKit/537.36 (KHTML, like Gecko)'
+ ' Chrome/61.0.3163.100 Safari/537.36',
'Content-Type': 'application/x-www-form-urlencoded'}
client_id = client_id or self._config['id']
@@ -186,21 +214,24 @@ def request_access_token(self, code, client_id='', client_secret=''):
'grant_type': 'http://oauth.net/grant_type/device/1.0'}
config_type = self._get_config_type(client_id, client_secret)
- client_summary = ''.join([
+ client = ''.join([
'(config_type: |', config_type, '|',
' client_id: |', client_id[:5], '...|',
' client_secret: |', client_secret[:5], '...|)'
])
- log_debug('Requesting access token for ' + client_summary)
-
- json_data = self.request('https://www.googleapis.com/oauth2/v4/token',
- method='POST', data=post_data, headers=headers,
- response_hook=self._login_json_hook,
- error_hook=self._login_error_hook,
- error_title='Login Failed',
- error_info='Access token request failed for ' + client_summary + ': {exc}',
- raise_exc=LoginException('Login Failed: Unknown response')
- )
+ log_debug('Requesting access token for {0}'.format(client))
+
+ json_data = self.request(self.TOKEN_URL,
+ method='POST',
+ data=post_data,
+ headers=headers,
+ response_hook=self._login_json_hook,
+ error_hook=self._login_error_hook,
+ error_title='Login Failed: Unknown response',
+ error_info=('Access token request failed'
+ ' {client}: {{exc}}'
+ .format(client=client)),
+ raise_exc=True)
return json_data
def request_device_and_user_code_tv(self):
@@ -210,7 +241,9 @@ def request_device_and_user_code_tv(self):
def request_device_and_user_code(self, client_id=''):
# https://developers.google.com/youtube/v3/guides/auth/devices
headers = {'Host': 'accounts.google.com',
- 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.100 Safari/537.36',
+ 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)'
+ ' AppleWebKit/537.36 (KHTML, like Gecko)'
+ ' Chrome/61.0.3163.100 Safari/537.36',
'Content-Type': 'application/x-www-form-urlencoded'}
client_id = client_id or self._config['id']
@@ -218,20 +251,23 @@ def request_device_and_user_code(self, client_id=''):
'scope': 'https://www.googleapis.com/auth/youtube'}
config_type = self._get_config_type(client_id)
- client_summary = ''.join([
+ client = ''.join([
'(config_type: |', config_type, '|',
' client_id: |', client_id[:5], '...|)',
])
- log_debug('Requesting device and user code for ' + client_summary)
-
- json_data = self.request('https://accounts.google.com/o/oauth2/device/code',
- method='POST', data=post_data, headers=headers,
- response_hook=self._login_json_hook,
- error_hook=self._login_error_hook,
- error_title='Login Failed',
- error_info='Requesting device and user code failed for ' + client_summary + ': {exc}',
- raise_exc=LoginException('Login Failed: Unknown response')
- )
+ log_debug('Requesting device and user code for {0}'.format(client))
+
+ json_data = self.request(self.DEVICE_CODE_URL,
+ method='POST',
+ data=post_data,
+ headers=headers,
+ response_hook=self._login_json_hook,
+ error_hook=self._login_error_hook,
+ error_title='Login Failed: Unknown response',
+ error_info=('Device/user code request failed'
+ ' {client}: {{exc}}'
+ .format(client=client)),
+ raise_exc=True)
return json_data
def get_access_token(self):
@@ -246,34 +282,30 @@ def authenticate(self, username, password):
'Connection': 'Keep-Alive',
'Accept-Encoding': 'gzip'}
- post_data = {'device_country': self._region.lower(),
- 'operatorCountry': self._region.lower(),
- 'lang': self._language.replace('-', '_'),
- 'sdk_version': '19',
- # 'google_play_services_version': '6188034',
- 'accountType': 'HOSTED_OR_GOOGLE',
- 'Email': username.encode('utf-8'),
- 'service': 'oauth2:https://www.googleapis.com/auth/youtube '
- 'https://www.googleapis.com/auth/youtube.force-ssl '
- 'https://www.googleapis.com/auth/plus.me '
- 'https://www.googleapis.com/auth/emeraldsea.mobileapps.doritos.cookie '
- 'https://www.googleapis.com/auth/plus.stream.read '
- 'https://www.googleapis.com/auth/plus.stream.write '
- 'https://www.googleapis.com/auth/plus.pages.manage '
- 'https://www.googleapis.com/auth/identity.plus.page.impersonation',
- 'source': 'android',
- 'androidId': '38c6ee9a82b8b10a',
- 'app': 'com.google.android.youtube',
- # 'client_sig': '24bb24c05e47e0aefa68a58a766179d9b613a600',
- 'callerPkg': 'com.google.android.youtube',
- # 'callerSig': '24bb24c05e47e0aefa68a58a766179d9b613a600',
- 'Passwd': password.encode('utf-8')}
-
- result = self.request('https://android.clients.google.com/auth',
- method='POST', data=post_data, headers=headers,
- error_title='Login Failed',
- raise_exc=LoginException
- )
+ post_data = {
+ 'device_country': self._region.lower(),
+ 'operatorCountry': self._region.lower(),
+ 'lang': self._language.replace('-', '_'),
+ 'sdk_version': '19',
+ # 'google_play_services_version': '6188034',
+ 'accountType': 'HOSTED_OR_GOOGLE',
+ 'Email': username.encode('utf-8'),
+ 'service': self.SERVICE_URLS,
+ 'source': 'android',
+ 'androidId': '38c6ee9a82b8b10a',
+ 'app': 'com.google.android.youtube',
+ # 'client_sig': '24bb24c05e47e0aefa68a58a766179d9b613a600',
+ 'callerPkg': 'com.google.android.youtube',
+ # 'callerSig': '24bb24c05e47e0aefa68a58a766179d9b613a600',
+ 'Passwd': password.encode('utf-8')
+ }
+
+ result = self.request(self.ANDROID_CLIENT_AUTH_URL,
+ method='POST',
+ data=post_data,
+ headers=headers,
+ error_title='Login Failed',
+ raise_exc=True)
lines = result.text.replace('\n', '&')
params = dict(parse_qsl(lines))
@@ -287,11 +319,17 @@ def authenticate(self, username, password):
def _get_config_type(self, client_id, client_secret=None):
"""used for logging"""
if client_secret is None:
- using_conf_tv = (client_id == self.CONFIGS['youtube-tv'].get('id'))
- using_conf_main = (client_id == self.CONFIGS['main'].get('id'))
+ using_conf_tv = client_id == self.CONFIGS['youtube-tv'].get('id')
+ using_conf_main = client_id == self.CONFIGS['main'].get('id')
else:
- using_conf_tv = ((client_id == self.CONFIGS['youtube-tv'].get('id')) and (client_secret == self.CONFIGS['youtube-tv'].get('secret')))
- using_conf_main = ((client_id == self.CONFIGS['main'].get('id')) and (client_secret == self.CONFIGS['main'].get('secret')))
+ using_conf_tv = (
+ client_secret == self.CONFIGS['youtube-tv'].get('secret')
+ and client_id == self.CONFIGS['youtube-tv'].get('id')
+ )
+ using_conf_main = (
+ client_secret == self.CONFIGS['main'].get('secret')
+ and client_id == self.CONFIGS['main'].get('id')
+ )
if not using_conf_main and not using_conf_tv:
return 'None'
if using_conf_tv:
diff --git a/resources/lib/youtube_plugin/youtube/client/request_client.py b/resources/lib/youtube_plugin/youtube/client/request_client.py
index 0bfb2771d..0c31c0291 100644
--- a/resources/lib/youtube_plugin/youtube/client/request_client.py
+++ b/resources/lib/youtube_plugin/youtube/client/request_client.py
@@ -255,8 +255,10 @@ class YouTubeRequestClient(BaseRequestsClass):
},
}
- def __init__(self):
- super(YouTubeRequestClient, self).__init__(exc_type=YouTubeException)
+ def __init__(self, exc_type=YouTubeException):
+ super(YouTubeRequestClient, self).__init__(exc_type=exc_type)
+ self._access_token = None
+ self.video_id = None
@staticmethod
def json_traverse(json_data, path):
@@ -286,7 +288,7 @@ def json_traverse(json_data, path):
return None
return result
- def build_client(self, client_name, auth_header=False):
+ def build_client(self, client_name, auth_header=False, data=None):
def _merge_dicts(item1, item2, _=Ellipsis):
if not isinstance(item1, dict) or not isinstance(item2, dict):
return item1 if item2 is _ else item2
@@ -301,11 +303,14 @@ def _merge_dicts(item1, item2, _=Ellipsis):
_format['{0}.{1}'.format(id(new), key)] = (new, key, value)
new[key] = value
return new or _
+
_format = {}
client = (self.CLIENTS.get(client_name) or self.CLIENTS['web']).copy()
client = _merge_dicts(self.CLIENTS['_common'], client)
+ if data:
+ client.update(data)
client['json']['videoId'] = self.video_id
if auth_header and self._access_token:
client['_access_token'] = self._access_token
@@ -313,8 +318,8 @@ def _merge_dicts(item1, item2, _=Ellipsis):
elif 'Authorization' in client['headers']:
del client['headers']['Authorization']
- for values, key, value in _format.values():
- if key in values:
- values[key] = value.format(**client)
+ for values, value_key, template_value in _format.values():
+ if value_key in values:
+ values[value_key] = template_value.format(**client)
return client
From ca18f6a89a096ce6ff2c8e14c8510ea55af1c350 Mon Sep 17 00:00:00 2001
From: MoojMidge <56883549+MoojMidge@users.noreply.github.com>
Date: Fri, 8 Dec 2023 08:10:11 +1100
Subject: [PATCH 065/141] Updates to listing display
- Remove unused date/time formatting code
- Use locale appropriate date/time display rather than ISO format
- Standardise stats item separator to match label mask
- Don't bother including all data in label2 as it just duplicates details in skins that use it
- Fix non-upcoming videos showing date/time in italics
- Fix showing zero value stats
- Fix stats with a value of 1 referred to as plural/multiple
- Fix incorrect date sorting after e16c7a0 (Kodi datetime need 'T' as seperator)
---
.../kodion/context/abstract_context.py | 4 +-
.../kodion/context/xbmc/xbmc_context.py | 37 ++++++-------------
.../youtube_plugin/kodion/items/base_item.py | 14 +++----
.../youtube_plugin/kodion/items/video_item.py | 4 +-
.../kodion/ui/xbmc/info_labels.py | 2 +-
.../kodion/ui/xbmc/xbmc_items.py | 19 ++--------
.../kodion/utils/datetime_parser.py | 31 ++++++----------
.../youtube_plugin/youtube/helper/utils.py | 33 +++++++++++------
.../lib/youtube_plugin/youtube/provider.py | 10 ++---
9 files changed, 67 insertions(+), 87 deletions(-)
diff --git a/resources/lib/youtube_plugin/kodion/context/abstract_context.py b/resources/lib/youtube_plugin/kodion/context/abstract_context.py
index 497e949c9..0dd7e2769 100644
--- a/resources/lib/youtube_plugin/kodion/context/abstract_context.py
+++ b/resources/lib/youtube_plugin/kodion/context/abstract_context.py
@@ -117,10 +117,10 @@ def __init__(self, path='/', params=None, plugin_name='', plugin_id=''):
self.parse_params()
self._uri = self.create_uri(self._path, self._params)
- def format_date_short(self, date_obj):
+ def format_date_short(self, date_obj, str_format=None):
raise NotImplementedError()
- def format_time(self, time_obj):
+ def format_time(self, time_obj, str_format=None):
raise NotImplementedError()
def get_language(self):
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 a95f6a431..ecfbb28e9 100644
--- a/resources/lib/youtube_plugin/kodion/context/xbmc/xbmc_context.py
+++ b/resources/lib/youtube_plugin/kodion/context/xbmc/xbmc_context.py
@@ -8,7 +8,6 @@
See LICENSES/GPL-2.0-only for more information.
"""
-import datetime
import json
import os
import sys
@@ -21,11 +20,11 @@
import xbmcvfs
from ..abstract_context import AbstractContext
-from ...player.xbmc.xbmc_playlist import XbmcPlaylist
+from ... import utils
from ...player.xbmc.xbmc_player import XbmcPlayer
+from ...player.xbmc.xbmc_playlist import XbmcPlaylist
from ...settings.xbmc.xbmc_plugin_settings import XbmcPluginSettings
from ...ui.xbmc.xbmc_context_ui import XbmcContextUI
-from ... import utils
class XbmcContext(AbstractContext):
@@ -302,30 +301,18 @@ def is_plugin_path(self, uri, uri_path=''):
return uri.startswith('plugin://%s/%s' % (self.get_id(), uri_path))
@staticmethod
- def format_date_short(date_obj, short_isoformat=False):
- if short_isoformat:
- if isinstance(date_obj, datetime.datetime):
- date_obj = date_obj.date()
- return date_obj.isoformat()
-
- date_format = xbmc.getRegion('dateshort')
- _date_obj = date_obj
- if isinstance(_date_obj, datetime.date):
- _date_obj = datetime.datetime(_date_obj.year, _date_obj.month, _date_obj.day)
-
- return _date_obj.strftime(date_format)
+ def format_date_short(date_obj, str_format=None):
+ if str_format is None:
+ str_format = xbmc.getRegion('dateshort')
+ return date_obj.strftime(str_format)
@staticmethod
- def format_time(time_obj, short_isoformat=False):
- if short_isoformat:
- return '{:02d}:{:02d}'.format(time_obj.hour, time_obj.minute)
-
- time_format = xbmc.getRegion('time')
- _time_obj = time_obj
- if isinstance(_time_obj, datetime.time):
- _time_obj = datetime.time(_time_obj.hour, _time_obj.minute, _time_obj.second)
-
- return _time_obj.strftime(time_format.replace("%H%H", "%H"))
+ def format_time(time_obj, str_format=None):
+ if str_format is None:
+ str_format = (xbmc.getRegion('time')
+ .replace("%H%H", "%H")
+ .replace(':%S', ''))
+ return time_obj.strftime(str_format)
def get_language(self):
"""
diff --git a/resources/lib/youtube_plugin/kodion/items/base_item.py b/resources/lib/youtube_plugin/kodion/items/base_item.py
index 49da304e7..5054f3a29 100644
--- a/resources/lib/youtube_plugin/kodion/items/base_item.py
+++ b/resources/lib/youtube_plugin/kodion/items/base_item.py
@@ -56,10 +56,10 @@ def get_id(self):
Returns a unique id of the item.
:return: unique id of the item.
"""
- m = hashlib.md5()
- m.update(self._name.encode('utf-8'))
- m.update(self._uri.encode('utf-8'))
- return m.hexdigest()
+ md5_hash = hashlib.md5()
+ md5_hash.update(self._name.encode('utf-8'))
+ md5_hash.update(self._uri.encode('utf-8'))
+ return md5_hash.hexdigest()
def get_name(self):
"""
@@ -113,9 +113,9 @@ def get_date(self, as_text=True, short=False):
if not self._date:
return ''
if short:
- return self._date.date().isoformat()
+ return self._date.date().strftime('%x')
if as_text:
- return self._date.isoformat(sep=' ')
+ return self._date.strftime('%x %X')
return self._date
def set_dateadded(self, year, month, day, hour=0, minute=0, second=0):
@@ -133,7 +133,7 @@ def get_dateadded(self, as_text=True):
if not self._dateadded:
return ''
if as_text:
- return self._dateadded.isoformat(sep=' ')
+ return self._dateadded.strftime('%x %X')
return self._dateadded
def set_added_utc(self, date_time):
diff --git a/resources/lib/youtube_plugin/kodion/items/video_item.py b/resources/lib/youtube_plugin/kodion/items/video_item.py
index 35521f098..234b5c160 100644
--- a/resources/lib/youtube_plugin/kodion/items/video_item.py
+++ b/resources/lib/youtube_plugin/kodion/items/video_item.py
@@ -116,7 +116,7 @@ def get_premiered(self, as_text=True):
if not self._premiered:
return ''
if as_text:
- return self._premiered.isoformat()
+ return self._premiered.strftime('%x')
return self._premiered
def set_plot(self, plot):
@@ -200,7 +200,7 @@ def get_aired(self, as_text=True):
if not self._aired:
return ''
if as_text:
- return self._aired.isoformat()
+ return self._aired.strftime('%x')
return self._aired
def set_scheduled_start_utc(self, date_time):
diff --git a/resources/lib/youtube_plugin/kodion/ui/xbmc/info_labels.py b/resources/lib/youtube_plugin/kodion/ui/xbmc/info_labels.py
index 9012179e2..35d74db9c 100644
--- a/resources/lib/youtube_plugin/kodion/ui/xbmc/info_labels.py
+++ b/resources/lib/youtube_plugin/kodion/ui/xbmc/info_labels.py
@@ -19,7 +19,7 @@ def _process_date_value(info_labels, name, param):
def _process_datetime_value(info_labels, name, param):
if param:
- info_labels[name] = param.isoformat(' ')
+ info_labels[name] = param.isoformat('T')
def _process_int_value(info_labels, name, param):
diff --git a/resources/lib/youtube_plugin/kodion/ui/xbmc/xbmc_items.py b/resources/lib/youtube_plugin/kodion/ui/xbmc/xbmc_items.py
index a511ca982..e16416ee6 100644
--- a/resources/lib/youtube_plugin/kodion/ui/xbmc/xbmc_items.py
+++ b/resources/lib/youtube_plugin/kodion/ui/xbmc/xbmc_items.py
@@ -11,9 +11,10 @@
from xbmcgui import ListItem
from . import info_labels
-from ...items import VideoItem, AudioItem, UriItem
+from ...items import AudioItem, UriItem, VideoItem
from ...utils import datetime_parser
+
try:
from infotagger.listitem import set_info_tag
except ImportError:
@@ -62,13 +63,7 @@ def video_playback_item(context, video_item):
else:
kwargs = {
'label': video_item.get_title() or video_item.get_name(),
- 'label2': ' | '.join((part
- for part in (
- video_item.get_code(),
- video_item.get_date(short=True),
- video_item.get_duration(as_text=True),
- )
- if part)),
+ 'label2': video_item.get_short_details(),
'path': uri,
'offscreen': True,
}
@@ -221,13 +216,7 @@ def video_listitem(context, video_item):
kwargs = {
'label': video_item.get_title() or video_item.get_name(),
- 'label2': ' | '.join((part
- for part in (
- video_item.get_code(),
- video_item.get_date(short=True),
- video_item.get_duration(as_text=True),
- )
- if part)),
+ 'label2': video_item.get_short_details(),
'path': uri,
'offscreen': True,
}
diff --git a/resources/lib/youtube_plugin/kodion/utils/datetime_parser.py b/resources/lib/youtube_plugin/kodion/utils/datetime_parser.py
index 1f6c4ea33..438cfe511 100644
--- a/resources/lib/youtube_plugin/kodion/utils/datetime_parser.py
+++ b/resources/lib/youtube_plugin/kodion/utils/datetime_parser.py
@@ -15,21 +15,17 @@
from ..exceptions import KodionException
-__RE_MATCH_TIME_ONLY__ = re.compile(r'^(?P[0-9]{2})([:]?(?P[0-9]{2})([:]?(?P[0-9]{2}))?)?$')
-__RE_MATCH_DATE_ONLY__ = re.compile(r'^(?P[0-9]{4})[-]?(?P[0-9]{2})[-]?(?P[0-9]{2})$')
-__RE_MATCH_DATETIME__ = re.compile(r'^(?P[0-9]{4})[-]?(?P[0-9]{2})[-]?(?P[0-9]{2})["T ](?P[0-9]{2})[:]?(?P[0-9]{2})[:]?(?P[0-9]{2})')
+__RE_MATCH_TIME_ONLY__ = re.compile(r'^(?P[0-9]{2})(:?(?P[0-9]{2})(:?(?P[0-9]{2}))?)?$')
+__RE_MATCH_DATE_ONLY__ = re.compile(r'^(?P[0-9]{4})[-/.]?(?P[0-9]{2})[-/.]?(?P[0-9]{2})$')
+__RE_MATCH_DATETIME__ = re.compile(r'^(?P[0-9]{4})[-/.]?(?P[0-9]{2})[-/.]?(?P[0-9]{2})["T ](?P[0-9]{2}):?(?P[0-9]{2}):?(?P[0-9]{2})')
__RE_MATCH_PERIOD__ = re.compile(r'P((?P\d+)Y)?((?P\d+)M)?((?P\d+)D)?(T((?P\d+)H)?((?P\d+)M)?((?P\d+)S)?)?')
__RE_MATCH_ABBREVIATED__ = re.compile(r'(\w+), (?P\d+) (?P\w+) (?P\d+) (?P\d+):(?P\d+):(?P\d+)')
-now = time.time()
-__LOCAL_OFFSET__ = datetime.fromtimestamp(now) - datetime.utcfromtimestamp(now)
+__LOCAL_OFFSET__ = datetime.now() - datetime.utcnow()
__EPOCH_DT__ = datetime.fromtimestamp(0)
-now = datetime.now
-
-
def parse(datetime_string, as_utc=True):
offset = 0 if as_utc else None
@@ -74,7 +70,8 @@ def _to_int(value):
offset=offset
)
- # period - at the moment we support only hours, minutes and seconds (e.g. videos and audio)
+ # period - at the moment we support only hours, minutes and seconds
+ # e.g. videos and audio
period_match = __RE_MATCH_PERIOD__.match(datetime_string)
if period_match:
return timedelta(hours=_to_int(period_match.group('hours')),
@@ -97,24 +94,19 @@ def _to_int(value):
offset=offset
)
- raise KodionException("Could not parse iso 8601 timestamp '%s'" % datetime_string)
+ raise KodionException('Could not parse |{datetime}| as ISO 8601'
+ .format(datetime=datetime_string))
def get_scheduled_start(context, datetime_object, local=True):
now = datetime.now() if local else datetime.utcnow()
if datetime_object.date() == now.date():
return '@ {start_time}'.format(
- start_time=context.format_time(
- datetime_object.timetz(), short_isoformat=True
- )
+ start_time=context.format_time(datetime_object.time())
)
return '@ {start_date}, {start_time}'.format(
- start_time=context.format_time(
- datetime_object.timetz(), short_isoformat=True
- ),
- start_date=context.format_date_short(
- datetime_object.date(), short_isoformat=True
- )
+ start_time=context.format_time(datetime_object.time()),
+ start_date=context.format_date_short(datetime_object.date())
)
@@ -189,6 +181,7 @@ def strptime(s, fmt='%Y-%m-%dT%H:%M:%S.%fZ'):
fmt = '%Y-%m-%dT%H:%M:%S.%fZ'
import _strptime
+
try:
time.strptime('01 01 2012', '%d %m %Y') # dummy call
except:
diff --git a/resources/lib/youtube_plugin/youtube/helper/utils.py b/resources/lib/youtube_plugin/youtube/helper/utils.py
index 35a58ff17..c7a3d3acf 100644
--- a/resources/lib/youtube_plugin/youtube/helper/utils.py
+++ b/resources/lib/youtube_plugin/youtube/helper/utils.py
@@ -422,22 +422,32 @@ def update_video_infos(provider, context, video_id_dict,
if 'statistics' in yt_item:
for stat, value in yt_item['statistics'].items():
label = context.LOCAL_MAP.get('stats.' + stat)
- if label:
- color = __COLOR_MAP.get(stat, 'white')
- str_value, value = utils.friendly_number(value)
- label_stats.append(ui.color(color, str_value))
- stats.append(ui.color(color, ui.bold(' '.join((
- str_value, context.localize(label)
- )))))
- else:
+ if not label:
+ continue
+
+ str_value, value = utils.friendly_number(value)
+ if not value:
continue
+
+ color = __COLOR_MAP.get(stat, 'white')
+ label = context.localize(label)
+ if value == 1:
+ label = label.rstrip('s')
+
+ label_stats.append(ui.color(color, str_value))
+ stats.append(ui.color(color, ui.bold(' '.join((
+ str_value, label
+ )))))
+
if stat == 'likeCount':
rating[0] = value
elif stat == 'viewCount':
rating[1] = value
video_item.set_count(value)
- label_stats = '|'.join(label_stats)
- stats = '|'.join(stats)
+
+ label_stats = ' | '.join(label_stats)
+ stats = ' | '.join(stats)
+
if 0 < rating[0] <= rating[1]:
if rating[0] == rating[1]:
rating = 10
@@ -482,7 +492,8 @@ def update_video_infos(provider, context, video_id_dict,
description = ''.join((
ui.bold(channel_name, cr_after=2) if channel_name else '',
ui.new_line(stats, cr_after=1) if stats else '',
- ui.italic(start_at, cr_after=1) if start_at else '',
+ (ui.italic(start_at, cr_after=1) if video_item.upcoming
+ else ui.new_line(start_at, cr_after=1)) if start_at else '',
ui.new_line() if stats or start_at else '',
description,
))
diff --git a/resources/lib/youtube_plugin/youtube/provider.py b/resources/lib/youtube_plugin/youtube/provider.py
index 1baa3caf4..4cfe07f5f 100644
--- a/resources/lib/youtube_plugin/youtube/provider.py
+++ b/resources/lib/youtube_plugin/youtube/provider.py
@@ -1446,18 +1446,18 @@ def on_root(self, context, re_match):
def set_content_type(context, content_type):
context.set_content_type(content_type)
context.add_sort_method(
- (constants.sort_method.UNSORTED, '%T', '%P | %J | %D'),
- (constants.sort_method.LABEL_IGNORE_THE, '%T', '%P | %J | %D'),
+ (constants.sort_method.UNSORTED, '%T \u2022 %P', '%D | %J'),
+ (constants.sort_method.LABEL_IGNORE_THE, '%T \u2022 %P', '%D | %J'),
)
if content_type != constants.content_type.VIDEOS:
return
context.add_sort_method(
- (constants.sort_method.PROGRAM_COUNT, '%T \u2022 %P | %J | %D', '%C'),
- (constants.sort_method.VIDEO_RATING, '%T \u2022 %P | %J | %D', '%R'),
+ (constants.sort_method.PROGRAM_COUNT, '%T \u2022 %P | %D | %J', '%C'),
+ (constants.sort_method.VIDEO_RATING, '%T \u2022 %P | %D | %J', '%R'),
(constants.sort_method.DATE, '%T \u2022 %P | %D', '%J'),
(constants.sort_method.DATEADDED, '%T \u2022 %P | %D', '%a'),
(constants.sort_method.VIDEO_RUNTIME, '%T \u2022 %P | %J', '%D'),
- (constants.sort_method.TRACKNUM, '[%N. ]%T', '%P | %J | %D'),
+ (constants.sort_method.TRACKNUM, '[%N. ]%T \u2022 %P', '%D | %J'),
)
def handle_exception(self, context, exception_to_handle):
From cceb4c90d3b9dce744869d07401a6569fcdb75ec Mon Sep 17 00:00:00 2001
From: MoojMidge <56883549+MoojMidge@users.noreply.github.com>
Date: Fri, 8 Dec 2023 08:51:25 +1100
Subject: [PATCH 066/141] Disable Opus audio by default
- Workaround for #537
---
resources/settings.xml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/resources/settings.xml b/resources/settings.xml
index f13b761e1..bf0da9d9b 100644
--- a/resources/settings.xml
+++ b/resources/settings.xml
@@ -263,7 +263,7 @@
0
- avc1,vp9,av01,hdr,hfr,vorbis,opus,mp4a,ssa,ac-3,ec-3,dts,filter
+ avc1,vp9,av01,hdr,hfr,vorbis,mp4a,ssa,ac-3,ec-3,dts,filter
From e26e85927bb436d803e2007fd70d4fe94a94ec89 Mon Sep 17 00:00:00 2001
From: MoojMidge <56883549+MoojMidge@users.noreply.github.com>
Date: Fri, 8 Dec 2023 09:58:59 +1100
Subject: [PATCH 067/141] Remove debugging code
---
resources/lib/default.py | 8 +-------
resources/lib/youtube_plugin/kodion/logger.py | 2 +-
2 files changed, 2 insertions(+), 8 deletions(-)
diff --git a/resources/lib/default.py b/resources/lib/default.py
index bbc54e2c9..10385f513 100644
--- a/resources/lib/default.py
+++ b/resources/lib/default.py
@@ -8,14 +8,8 @@
See LICENSES/GPL-2.0-only for more information.
"""
-from xbmc import log
-
-from youtube_plugin import youtube
from youtube_plugin.kodion import runner
-from youtube_plugin.kodion.debug import Profiler
-
+from youtube_plugin import youtube
-profiler = Profiler(enabled=True, lazy=False)
__provider__ = youtube.Provider()
runner.run(__provider__)
-log(profiler.get_stats(), 1)
diff --git a/resources/lib/youtube_plugin/kodion/logger.py b/resources/lib/youtube_plugin/kodion/logger.py
index c97587d21..df709b3bb 100644
--- a/resources/lib/youtube_plugin/kodion/logger.py
+++ b/resources/lib/youtube_plugin/kodion/logger.py
@@ -11,7 +11,7 @@
import xbmc
import xbmcaddon
-DEBUG = xbmc.LOGINFO
+DEBUG = xbmc.LOGDEBUG
INFO = xbmc.LOGINFO
NOTICE = INFO
WARNING = xbmc.LOGWARNING
From 0111bf6404d1ac97dad106bbd1f71fb08a6c775c Mon Sep 17 00:00:00 2001
From: MoojMidge <56883549+MoojMidge@users.noreply.github.com>
Date: Sat, 9 Dec 2023 18:26:03 +1100
Subject: [PATCH 068/141] Fix incorrect method signatures
---
.../lib/youtube_plugin/kodion/context/abstract_context.py | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/resources/lib/youtube_plugin/kodion/context/abstract_context.py b/resources/lib/youtube_plugin/kodion/context/abstract_context.py
index 0dd7e2769..72fe8361f 100644
--- a/resources/lib/youtube_plugin/kodion/context/abstract_context.py
+++ b/resources/lib/youtube_plugin/kodion/context/abstract_context.py
@@ -117,10 +117,12 @@ def __init__(self, path='/', params=None, plugin_name='', plugin_id=''):
self.parse_params()
self._uri = self.create_uri(self._path, self._params)
- def format_date_short(self, date_obj, str_format=None):
+ @staticmethod
+ def format_date_short(date_obj, str_format=None):
raise NotImplementedError()
- def format_time(self, time_obj, str_format=None):
+ @staticmethod
+ def format_time(time_obj, str_format=None):
raise NotImplementedError()
def get_language(self):
From 928745d30828a0e67b25165fa698da674de00b4c Mon Sep 17 00:00:00 2001
From: MoojMidge <56883549+MoojMidge@users.noreply.github.com>
Date: Sat, 9 Dec 2023 18:40:43 +1100
Subject: [PATCH 069/141] Fix datetime_parser.now being removed
- Still used in YouTube._get_recommendations_for_home
- TODO: remove once _get_recommendations_for_home is fixed
---
.../kodion/utils/datetime_parser.py | 20 ++++++++++---------
1 file changed, 11 insertions(+), 9 deletions(-)
diff --git a/resources/lib/youtube_plugin/kodion/utils/datetime_parser.py b/resources/lib/youtube_plugin/kodion/utils/datetime_parser.py
index 438cfe511..16d4cefbb 100644
--- a/resources/lib/youtube_plugin/kodion/utils/datetime_parser.py
+++ b/resources/lib/youtube_plugin/kodion/utils/datetime_parser.py
@@ -15,13 +15,15 @@
from ..exceptions import KodionException
+now = datetime.now
+
__RE_MATCH_TIME_ONLY__ = re.compile(r'^(?P[0-9]{2})(:?(?P[0-9]{2})(:?(?P[0-9]{2}))?)?$')
__RE_MATCH_DATE_ONLY__ = re.compile(r'^(?P[0-9]{4})[-/.]?(?P[0-9]{2})[-/.]?(?P[0-9]{2})$')
__RE_MATCH_DATETIME__ = re.compile(r'^(?P[0-9]{4})[-/.]?(?P[0-9]{2})[-/.]?(?P[0-9]{2})["T ](?P[0-9]{2}):?(?P[0-9]{2}):?(?P[0-9]{2})')
__RE_MATCH_PERIOD__ = re.compile(r'P((?P\d+)Y)?((?P\d+)M)?((?P\d+)D)?(T((?P\d+)H)?((?P\d+)M)?((?P\d+)S)?)?')
__RE_MATCH_ABBREVIATED__ = re.compile(r'(\w+), (?P\d+) (?P\w+) (?P\d+) (?P\d+):(?P\d+):(?P\d+)')
-__LOCAL_OFFSET__ = datetime.now() - datetime.utcnow()
+__LOCAL_OFFSET__ = now() - datetime.utcnow()
__EPOCH_DT__ = datetime.fromtimestamp(0)
@@ -99,8 +101,8 @@ def _to_int(value):
def get_scheduled_start(context, datetime_object, local=True):
- now = datetime.now() if local else datetime.utcnow()
- if datetime_object.date() == now.date():
+ _now = now() if local else datetime.utcnow()
+ if datetime_object.date() == _now:
return '@ {start_time}'.format(
start_time=context.format_time(datetime_object.time())
)
@@ -116,12 +118,12 @@ def utc_to_local(dt, offset=None):
def datetime_to_since(context, dt):
- now = datetime.now()
- diff = now - dt
- yesterday = now - timedelta(days=1)
- yyesterday = now - timedelta(days=2)
- use_yesterday = (now - yesterday).total_seconds() > 10800
- today = now.date()
+ _now = now()
+ diff = _now - dt
+ yesterday = _now - timedelta(days=1)
+ yyesterday = _now - timedelta(days=2)
+ use_yesterday = (_now - yesterday).total_seconds() > 10800
+ today = _now.date()
tomorrow = today + timedelta(days=1)
seconds = diff.total_seconds()
From 446b85aaa185fcd6a01ea5f5bd6221471aa6ef5f Mon Sep 17 00:00:00 2001
From: MoojMidge <56883549+MoojMidge@users.noreply.github.com>
Date: Sun, 10 Dec 2023 01:09:07 +1100
Subject: [PATCH 070/141] Add settings methods for WL and HL
---
.../kodion/constants/const_settings.py | 3 +++
.../kodion/settings/abstract_settings.py | 12 ++++++++++++
2 files changed, 15 insertions(+)
diff --git a/resources/lib/youtube_plugin/kodion/constants/const_settings.py b/resources/lib/youtube_plugin/kodion/constants/const_settings.py
index da0308bf4..0b299f170 100644
--- a/resources/lib/youtube_plugin/kodion/constants/const_settings.py
+++ b/resources/lib/youtube_plugin/kodion/constants/const_settings.py
@@ -57,3 +57,6 @@
API_SECRET = 'youtube.api.secret' # (string)
CLIENT_SELECTION = 'youtube.client.selection' # (int)
+
+WATCH_LATER_PLAYLIST = 'youtube.folder.watch_later.playlist' # (str)
+HISTORY_PLAYLIST = 'youtube.folder.history.playlist' # (str)
diff --git a/resources/lib/youtube_plugin/kodion/settings/abstract_settings.py b/resources/lib/youtube_plugin/kodion/settings/abstract_settings.py
index e5cabea63..6e1c65e97 100644
--- a/resources/lib/youtube_plugin/kodion/settings/abstract_settings.py
+++ b/resources/lib/youtube_plugin/kodion/settings/abstract_settings.py
@@ -319,3 +319,15 @@ def show_detailed_description(self):
def get_language(self):
return self.get_string(SETTINGS.LANGUAGE, 'en_US').replace('_', '-')
+
+ def get_watch_later_playlist(self):
+ return self.get_string(SETTINGS.WATCH_LATER_PLAYLIST, '').strip()
+
+ def set_watch_later_playlist(self, value):
+ return self.set_string(SETTINGS.WATCH_LATER_PLAYLIST, value)
+
+ def get_history_playlist(self):
+ return self.get_string(SETTINGS.HISTORY_PLAYLIST, '').strip()
+
+ def set_history_playlist(self, value):
+ return self.set_string(SETTINGS.HISTORY_PLAYLIST, value)
From 737d31dfc4f28ce82aaaa12cdb48caaf46c41094 Mon Sep 17 00:00:00 2001
From: MoojMidge <56883549+MoojMidge@users.noreply.github.com>
Date: Sun, 10 Dec 2023 01:14:14 +1100
Subject: [PATCH 071/141] Better handle failed playlist request
- Fix #545
- Also consolidate progress dialog methods
---
.../kodion/ui/abstract_progress_dialog.py | 27 +++++++++++--
.../kodion/ui/xbmc/xbmc_context_ui.py | 3 +-
.../kodion/ui/xbmc/xbmc_progress_dialog.py | 32 ++++++---------
.../kodion/ui/xbmc/xbmc_progress_dialog_bg.py | 40 -------------------
.../youtube_plugin/youtube/helper/yt_play.py | 9 +++--
5 files changed, 43 insertions(+), 68 deletions(-)
delete mode 100644 resources/lib/youtube_plugin/kodion/ui/xbmc/xbmc_progress_dialog_bg.py
diff --git a/resources/lib/youtube_plugin/kodion/ui/abstract_progress_dialog.py b/resources/lib/youtube_plugin/kodion/ui/abstract_progress_dialog.py
index 743711ba6..0af140254 100644
--- a/resources/lib/youtube_plugin/kodion/ui/abstract_progress_dialog.py
+++ b/resources/lib/youtube_plugin/kodion/ui/abstract_progress_dialog.py
@@ -10,9 +10,14 @@
class AbstractProgressDialog(object):
- def __init__(self, total=100):
+ def __init__(self, dialog, heading, text, total=100):
+ self._dialog = dialog()
+ self._dialog.create(heading, text)
+
+ # simple reset because KODI won't do it :(
self._total = int(total)
- self._position = 0
+ self._position = 1
+ self.update(steps=-1)
def get_total(self):
return self._total
@@ -21,13 +26,27 @@ def get_position(self):
return self._position
def close(self):
- raise NotImplementedError()
+ if self._dialog:
+ self._dialog.close()
+ self._dialog = None
def set_total(self, total):
self._total = int(total)
def update(self, steps=1, text=None):
- raise NotImplementedError()
+ self._position += steps
+
+ if not self._total:
+ position = 0
+ elif self._position >= self._total:
+ position = 100
+ else:
+ position = int(100 * self._position / self._total)
+
+ if isinstance(text, str):
+ self._dialog.update(percent=position, message=text)
+ else:
+ self._dialog.update(percent=position)
def is_aborted(self):
raise NotImplementedError()
diff --git a/resources/lib/youtube_plugin/kodion/ui/xbmc/xbmc_context_ui.py b/resources/lib/youtube_plugin/kodion/ui/xbmc/xbmc_context_ui.py
index 032380c87..ce6eb53cc 100644
--- a/resources/lib/youtube_plugin/kodion/ui/xbmc/xbmc_context_ui.py
+++ b/resources/lib/youtube_plugin/kodion/ui/xbmc/xbmc_context_ui.py
@@ -11,8 +11,7 @@
import xbmc
import xbmcgui
-from .xbmc_progress_dialog import XbmcProgressDialog
-from .xbmc_progress_dialog_bg import XbmcProgressDialogBG
+from .xbmc_progress_dialog import XbmcProgressDialog, XbmcProgressDialogBG
from ..abstract_context_ui import AbstractContextUI
from ... import utils
diff --git a/resources/lib/youtube_plugin/kodion/ui/xbmc/xbmc_progress_dialog.py b/resources/lib/youtube_plugin/kodion/ui/xbmc/xbmc_progress_dialog.py
index a1f061b99..fda3cde27 100644
--- a/resources/lib/youtube_plugin/kodion/ui/xbmc/xbmc_progress_dialog.py
+++ b/resources/lib/youtube_plugin/kodion/ui/xbmc/xbmc_progress_dialog.py
@@ -14,27 +14,21 @@
class XbmcProgressDialog(AbstractProgressDialog):
def __init__(self, heading, text):
- super(XbmcProgressDialog, self).__init__(100)
- self._dialog = xbmcgui.DialogProgress()
- self._dialog.create(heading, text)
+ super(XbmcProgressDialog, self).__init__(xbmcgui.DialogProgress,
+ heading,
+ text,
+ 100)
- # simple reset because KODI won't do it :(
- self._position = 1
- self.update(steps=-1)
-
- def close(self):
- if self._dialog:
- self._dialog.close()
- self._dialog = None
+ def is_aborted(self):
+ return self._dialog.iscanceled()
- def update(self, steps=1, text=None):
- self._position += steps
- position = int(float((100.0 // self._total)) * self._position)
- if isinstance(text, str):
- self._dialog.update(position, text)
- else:
- self._dialog.update(position)
+class XbmcProgressDialogBG(AbstractProgressDialog):
+ def __init__(self, heading, text):
+ super(XbmcProgressDialogBG, self).__init__(xbmcgui.DialogProgressBG,
+ heading,
+ text,
+ 100)
def is_aborted(self):
- return self._dialog.iscanceled()
+ return False
diff --git a/resources/lib/youtube_plugin/kodion/ui/xbmc/xbmc_progress_dialog_bg.py b/resources/lib/youtube_plugin/kodion/ui/xbmc/xbmc_progress_dialog_bg.py
deleted file mode 100644
index 222cde7f7..000000000
--- a/resources/lib/youtube_plugin/kodion/ui/xbmc/xbmc_progress_dialog_bg.py
+++ /dev/null
@@ -1,40 +0,0 @@
-# -*- coding: utf-8 -*-
-"""
-
- Copyright (C) 2014-2016 bromix (plugin.video.youtube)
- Copyright (C) 2016-2018 plugin.video.youtube
-
- SPDX-License-Identifier: GPL-2.0-only
- See LICENSES/GPL-2.0-only for more information.
-"""
-
-import xbmcgui
-from ..abstract_progress_dialog import AbstractProgressDialog
-
-
-class XbmcProgressDialogBG(AbstractProgressDialog):
- def __init__(self, heading, text):
- super(XbmcProgressDialogBG, self).__init__(100)
- self._dialog = xbmcgui.DialogProgressBG()
- self._dialog.create(heading, text)
-
- # simple reset because KODI won't do it :(
- self._position = 1
- self.update(steps=-1)
-
- def close(self):
- if self._dialog:
- self._dialog.close()
- self._dialog = None
-
- def update(self, steps=1, text=None):
- self._position += steps
- position = int((100.0 / float(self._total)) * float(self._position))
-
- if isinstance(text, str):
- self._dialog.update(percent=position, message=text)
- else:
- self._dialog.update(percent=position)
-
- def is_aborted(self):
- return False
diff --git a/resources/lib/youtube_plugin/youtube/helper/yt_play.py b/resources/lib/youtube_plugin/youtube/helper/yt_play.py
index 130e7d1e0..07d22c2c4 100644
--- a/resources/lib/youtube_plugin/youtube/helper/yt_play.py
+++ b/resources/lib/youtube_plugin/youtube/helper/yt_play.py
@@ -159,11 +159,14 @@ def play_playlist(provider, context):
while page_token is not None:
json_data = client.get_playlist_items(playlist_id, page_token)
if not v3.handle_error(context, json_data):
- return None
+ break
if page_token == 0:
- total += int(json_data.get('pageInfo', {})
- .get('totalResults', 0))
+ playlist_total = int(json_data.get('pageInfo', {})
+ .get('totalResults', 0))
+ if not playlist_total:
+ break
+ total += playlist_total
progress_dialog.set_total(total)
result = v3.response_to_items(provider,
From a3dc87986cc84c19848a386ee9b2043b47e554db Mon Sep 17 00:00:00 2001
From: MoojMidge <56883549+MoojMidge@users.noreply.github.com>
Date: Sun, 10 Dec 2023 01:28:16 +1100
Subject: [PATCH 072/141] Better handle seeking out of a Clip
- This may need to be tweaked after more testing
---
resources/lib/youtube_plugin/kodion/utils/player.py | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/resources/lib/youtube_plugin/kodion/utils/player.py b/resources/lib/youtube_plugin/kodion/utils/player.py
index 1e0e40ab9..b2dcce605 100644
--- a/resources/lib/youtube_plugin/kodion/utils/player.py
+++ b/resources/lib/youtube_plugin/kodion/utils/player.py
@@ -507,9 +507,9 @@ def onPlayBackError(self):
self.onPlayBackEnded()
def onPlayBackSeek(self, time, seekOffset):
- time_s = time // 1000
+ time_s = time / 1000
self.seek_time = None
- if ((self.end_time and time_s > self.end_time)
- or (self.start_time and time_s < self.start_time)):
+ if ((self.end_time and time_s > self.end_time + 1)
+ or (self.start_time and time_s < self.start_time - 1)):
self.start_time = None
self.end_time = None
From ea63dfdad61cc7120d7a9bbee645f002461a3dc6 Mon Sep 17 00:00:00 2001
From: MoojMidge <56883549+MoojMidge@users.noreply.github.com>
Date: Sun, 10 Dec 2023 01:30:14 +1100
Subject: [PATCH 073/141] Make merge_dicts a common utils method
---
.../youtube_plugin/kodion/utils/__init__.py | 2 ++
.../youtube_plugin/kodion/utils/methods.py | 18 +++++++++++++
.../youtube/client/request_client.py | 26 +++++--------------
3 files changed, 26 insertions(+), 20 deletions(-)
diff --git a/resources/lib/youtube_plugin/kodion/utils/__init__.py b/resources/lib/youtube_plugin/kodion/utils/__init__.py
index 0cd6e0e08..bffb440ce 100644
--- a/resources/lib/youtube_plugin/kodion/utils/__init__.py
+++ b/resources/lib/youtube_plugin/kodion/utils/__init__.py
@@ -18,6 +18,7 @@
friendly_number,
loose_version,
make_dirs,
+ merge_dicts,
seconds_to_duration,
select_stream,
strip_html_from_text,
@@ -47,6 +48,7 @@
'friendly_number',
'loose_version',
'make_dirs',
+ 'merge_dicts',
'seconds_to_duration',
'select_stream',
'strip_html_from_text',
diff --git a/resources/lib/youtube_plugin/kodion/utils/methods.py b/resources/lib/youtube_plugin/kodion/utils/methods.py
index a88e2475b..a648a81eb 100644
--- a/resources/lib/youtube_plugin/kodion/utils/methods.py
+++ b/resources/lib/youtube_plugin/kodion/utils/methods.py
@@ -27,6 +27,7 @@
'friendly_number',
'loose_version',
'make_dirs',
+ 'merge_dicts',
'print_items',
'seconds_to_duration',
'select_stream',
@@ -297,3 +298,20 @@ def duration_to_seconds(duration):
def seconds_to_duration(seconds):
return str(timedelta(seconds=seconds))
+
+
+def merge_dicts(item1, item2, templates=None, _=Ellipsis):
+ if not isinstance(item1, dict) or not isinstance(item2, dict):
+ return item1 if item2 is _ else item2
+ new = {}
+ keys = set(item1)
+ keys.update(item2)
+ for key in keys:
+ value = merge_dicts(item1.get(key, _), item2.get(key, _), templates)
+ if value is _:
+ continue
+ if (templates is not None
+ and isinstance(value, str) and '{' in value):
+ templates['{0}.{1}'.format(id(new), key)] = (new, key, value)
+ new[key] = value
+ return new or _
diff --git a/resources/lib/youtube_plugin/youtube/client/request_client.py b/resources/lib/youtube_plugin/youtube/client/request_client.py
index 0c31c0291..603bc3233 100644
--- a/resources/lib/youtube_plugin/youtube/client/request_client.py
+++ b/resources/lib/youtube_plugin/youtube/client/request_client.py
@@ -7,6 +7,7 @@
See LICENSES/GPL-2.0-only for more information.
"""
+from ...kodion.utils import merge_dicts
from ...kodion.network import BaseRequestsClass
from ...youtube.youtube_exceptions import YouTubeException
@@ -289,25 +290,10 @@ def json_traverse(json_data, path):
return result
def build_client(self, client_name, auth_header=False, data=None):
- def _merge_dicts(item1, item2, _=Ellipsis):
- if not isinstance(item1, dict) or not isinstance(item2, dict):
- return item1 if item2 is _ else item2
- new = {}
- keys = set(item1)
- keys.update(item2)
- for key in keys:
- value = _merge_dicts(item1.get(key, _), item2.get(key, _))
- if value is _:
- continue
- if isinstance(value, str) and '{' in value:
- _format['{0}.{1}'.format(id(new), key)] = (new, key, value)
- new[key] = value
- return new or _
-
- _format = {}
+ templates = {}
client = (self.CLIENTS.get(client_name) or self.CLIENTS['web']).copy()
- client = _merge_dicts(self.CLIENTS['_common'], client)
+ client = merge_dicts(self.CLIENTS['_common'], client, templates)
if data:
client.update(data)
@@ -318,8 +304,8 @@ def _merge_dicts(item1, item2, _=Ellipsis):
elif 'Authorization' in client['headers']:
del client['headers']['Authorization']
- for values, value_key, template_value in _format.values():
- if value_key in values:
- values[value_key] = template_value.format(**client)
+ for values, template_id, template in templates.values():
+ if template_id in values:
+ values[template_id] = template.format(**client)
return client
From f5742c8077e94262ba4e3d2e6ac32ee1a5ee2014 Mon Sep 17 00:00:00 2001
From: MoojMidge <56883549+MoojMidge@users.noreply.github.com>
Date: Sun, 10 Dec 2023 01:54:03 +1100
Subject: [PATCH 074/141] Make AccessManager inherit from JSONStore directly
- Remove LoginTokenStore and login_tokens.py
- Prevents issues with possible circular imports
- Move access_manager out of kodion.utils and into kodion.json_store
- Remove JSONStore from kodion.json_store.__all__
- JSONStore.save can now partially update data
- JSONStore.load/save/get_data can now process data after JSON decoding
- TODO: Fix usage of AccessManager in youtube.client.__config__.APICheck to avoid keeping two copies of the data
- TODO: Fix usage of APIKeyStore to avoid keeping two copies of the data
---
.../kodion/context/abstract_context.py | 2 +-
.../kodion/json_store/__init__.py | 5 +-
.../kodion/json_store/access_manager.py | 599 ++++++++++++++++++
.../kodion/json_store/api_keys.py | 2 +-
.../kodion/json_store/json_store.py | 34 +-
.../kodion/json_store/login_tokens.py | 99 ---
.../youtube_plugin/kodion/utils/__init__.py | 2 -
.../kodion/utils/access_manager.py | 418 ------------
.../youtube/client/__config__.py | 14 +-
.../lib/youtube_plugin/youtube/provider.py | 6 +-
10 files changed, 633 insertions(+), 548 deletions(-)
create mode 100644 resources/lib/youtube_plugin/kodion/json_store/access_manager.py
delete mode 100644 resources/lib/youtube_plugin/kodion/json_store/login_tokens.py
delete mode 100644 resources/lib/youtube_plugin/kodion/utils/access_manager.py
diff --git a/resources/lib/youtube_plugin/kodion/context/abstract_context.py b/resources/lib/youtube_plugin/kodion/context/abstract_context.py
index 72fe8361f..99af7c3fb 100644
--- a/resources/lib/youtube_plugin/kodion/context/abstract_context.py
+++ b/resources/lib/youtube_plugin/kodion/context/abstract_context.py
@@ -13,10 +13,10 @@
from .. import constants
from .. import logger
+from ..json_store import AccessManager
from ..utils import (
create_path,
create_uri_path,
- AccessManager,
DataCache,
FavoriteList,
FunctionCache,
diff --git a/resources/lib/youtube_plugin/kodion/json_store/__init__.py b/resources/lib/youtube_plugin/kodion/json_store/__init__.py
index 1b8044317..96383f1c0 100644
--- a/resources/lib/youtube_plugin/kodion/json_store/__init__.py
+++ b/resources/lib/youtube_plugin/kodion/json_store/__init__.py
@@ -7,9 +7,8 @@
See LICENSES/GPL-2.0-only for more information.
"""
-from .json_store import JSONStore
+from .access_manager import AccessManager
from .api_keys import APIKeyStore
-from .login_tokens import LoginTokenStore
-__all__ = ('APIKeyStore', 'JSONStore', 'LoginTokenStore',)
+__all__ = ('AccessManager', 'APIKeyStore',)
diff --git a/resources/lib/youtube_plugin/kodion/json_store/access_manager.py b/resources/lib/youtube_plugin/kodion/json_store/access_manager.py
new file mode 100644
index 000000000..c5264ca67
--- /dev/null
+++ b/resources/lib/youtube_plugin/kodion/json_store/access_manager.py
@@ -0,0 +1,599 @@
+# -*- coding: utf-8 -*-
+"""
+
+ Copyright (C) 2014-2016 bromix (plugin.video.youtube)
+ Copyright (C) 2016-2018 plugin.video.youtube
+
+ SPDX-License-Identifier: GPL-2.0-only
+ See LICENSES/GPL-2.0-only for more information.
+"""
+
+import time
+import uuid
+from hashlib import md5
+
+from .json_store import JSONStore
+
+
+__author__ = 'bromix'
+
+
+class AccessManager(JSONStore):
+ DEFAULT_NEW_USER = {
+ 'access_token': '',
+ 'refresh_token': '',
+ 'token_expires': -1,
+ 'last_key_hash': '',
+ 'name': 'Default',
+ 'watch_later': ' WL',
+ 'watch_history': 'HL'
+ }
+
+ def __init__(self, context):
+ super(AccessManager, self).__init__('access_manager.json')
+ self._settings = context.get_settings()
+ access_manager_data = self._data['access_manager']
+ self._user = access_manager_data.get('current_user', 0)
+ self._last_origin = access_manager_data.get('last_origin',
+ 'plugin.video.youtube')
+
+ def set_defaults(self, reset=False):
+ data = {} if reset else self.get_data()
+ if 'access_manager' not in data:
+ data = {
+ 'access_manager': {
+ 'users': {
+ 0: self.DEFAULT_NEW_USER.copy()
+ }
+ }
+ }
+ if 'users' not in data['access_manager']:
+ data['access_manager']['users'] = {
+ 0: self.DEFAULT_NEW_USER.copy()
+ }
+ if 0 not in data['access_manager']['users']:
+ data['access_manager']['users'][0] = self.DEFAULT_NEW_USER.copy()
+ if 'current_user' not in data['access_manager']:
+ data['access_manager']['current_user'] = 0
+ if 'last_origin' not in data['access_manager']:
+ data['access_manager']['last_origin'] = 'plugin.video.youtube'
+ if 'developers' not in data['access_manager']:
+ data['access_manager']['developers'] = {}
+
+ # clean up
+ if data['access_manager']['current_user'] == 'default':
+ data['access_manager']['current_user'] = 0
+ if 'access_token' in data['access_manager']:
+ del data['access_manager']['access_token']
+ if 'refresh_token' in data['access_manager']:
+ del data['access_manager']['refresh_token']
+ if 'token_expires' in data['access_manager']:
+ del data['access_manager']['token_expires']
+ if 'default' in data['access_manager']:
+ if ((data['access_manager']['default'].get('access_token')
+ or data['access_manager']['default'].get('refresh_token'))
+ and not data['access_manager']['users'][0].get(
+ 'access_token')
+ and not data['access_manager']['users'][0].get(
+ 'refresh_token')):
+ if 'name' not in data['access_manager']['default']:
+ data['access_manager']['default']['name'] = 'Default'
+ data['access_manager']['users'][0] = data['access_manager'][
+ 'default']
+ del data['access_manager']['default']
+ # end clean up
+
+ current_user = data['access_manager']['current_user']
+ if 'watch_later' not in data['access_manager']['users'][current_user]:
+ data['access_manager']['users'][current_user]['watch_later'] = ' WL'
+ if 'watch_history' not in data['access_manager']['users'][current_user]:
+ data['access_manager']['users'][current_user][
+ 'watch_history'] = 'HL'
+
+ # ensure all users have uuid
+ uuids = set()
+ for user in data['access_manager']['users'].values():
+ c_uuid = user.get('id')
+ while not c_uuid or c_uuid in uuids:
+ c_uuid = uuid.uuid4().hex
+ uuids.add(c_uuid)
+ user['id'] = c_uuid
+ # end uuid check
+
+ self.save(data)
+
+ @staticmethod
+ def _process_data(data):
+ # process users, change str keys (old format) to int (current format)
+ users = data['access_manager']['users']
+ if '0' in users:
+ data['access_manager']['users'] = {
+ int(key): value
+ for key, value in users.items()
+ }
+ return data
+
+ def get_data(self, process=_process_data.__func__):
+ return super(AccessManager, self).get_data(process)
+
+ def load(self, process=_process_data.__func__):
+ return super(AccessManager, self).load(process)
+
+ def save(self, data, update=False, process=_process_data.__func__):
+ return super(AccessManager, self).save(data, update, process)
+
+ def get_current_user_details(self):
+ """
+ :return: current user
+ """
+ return self.get_users()[self._user]
+
+ def get_current_user_id(self):
+ """
+ :return: uuid of the current user
+ """
+ return self.get_users()[self._user]['id']
+
+ def get_new_user(self, username=''):
+ """
+ :param username: string, users name
+ :return: a new user dict
+ """
+ uuids = [
+ user.get('id')
+ for user in self.get_users().values()
+ ]
+ new_uuid = None
+ while not new_uuid or new_uuid in uuids:
+ new_uuid = uuid.uuid4().hex
+ return {
+ 'access_token': '',
+ 'refresh_token': '',
+ 'token_expires': -1,
+ 'last_key_hash': '',
+ 'name': username,
+ 'id': new_uuid,
+ 'watch_later': ' WL',
+ 'watch_history': 'HL'
+ }
+
+ def get_users(self):
+ """
+ Returns users
+ :return: users
+ """
+ return self._data['access_manager'].get('users', {})
+
+ def add_user(self, username='', user=None):
+ """
+ Add single new user to users collection
+ :param username: str, chosen name of new user
+ :param user: int, optional index for new user
+ :return: tuple, (index, details) of newly added user
+ """
+ users = self.get_users()
+ new_user_details = self.get_new_user(username)
+ new_user = max(users) + 1 if users and user is None else user or 0
+ users[new_user] = new_user_details
+ data = {
+ 'access_manager': {
+ 'users': users
+ }
+ }
+ self.save(data, update=True)
+ return new_user, new_user_details
+
+ def remove_user(self, user):
+ """
+ Remove user from collection of current users
+ :param user: int, user index
+ :return:
+ """
+ users = self.get_users()
+ if user in users:
+ del users[user]
+ data = {
+ 'access_manager': {
+ 'users': users
+ }
+ }
+ self.save(data, update=True)
+
+ def set_users(self, users):
+ """
+ Updates all users
+ :param users: dict, users
+ :return:
+ """
+ data = self.get_data()
+ data['access_manager']['users'] = users
+ self.save(data)
+
+ def set_user(self, user, switch_to=False):
+ """
+ Updates the user
+ :param user: string, username
+ :param switch_to: boolean, change current user
+ :return:
+ """
+ self._user = user
+ if switch_to:
+ data = {
+ 'access_manager': {
+ 'current_user': user
+ }
+ }
+ self.save(data, update=True)
+
+ def get_current_user(self):
+ """
+ Returns the current user
+ :return: user
+ """
+ return self._user
+
+ def get_username(self, user=None):
+ """
+ Returns the username of the current or nominated user
+ :return: username
+ """
+ if user is None:
+ user = self._user
+ users = self.get_users()
+ if user in users:
+ return users[user].get('name')
+ return ''
+
+ def set_username(self, user, username):
+ """
+ Sets the username of the nominated user
+ :return: True if username was set, false otherwise
+ """
+ users = self.get_users()
+ if user in users:
+ users[user]['name'] = username
+ data = {
+ 'access_manager': {
+ 'users': users
+ }
+ }
+ self.save(data, update=True)
+ return True
+ return False
+
+ def get_watch_later_id(self):
+ """
+ Returns the current users watch later playlist id
+ :return: the current users watch later playlist id
+ """
+ updated = False
+ watch_later_ids = ('wl', ' wl')
+
+ current_user = self.get_current_user_details()
+ current_playlist_id = current_user.get('watch_later', '')
+ settings_playlist_id = self._settings.get_watch_later_playlist()
+
+ if settings_playlist_id.lower().startswith(watch_later_ids):
+ self._settings.set_watch_later_playlist('')
+ settings_playlist_id = ''
+
+ if current_playlist_id.lower().startswith(watch_later_ids):
+ updated = True
+ current_user['watch_later'] = settings_playlist_id
+ self._settings.set_watch_later_playlist('')
+ settings_playlist_id = ''
+
+ if settings_playlist_id and current_playlist_id != settings_playlist_id:
+ updated = True
+ current_user['watch_later'] = settings_playlist_id
+ self._settings.set_watch_later_playlist('')
+
+ if updated:
+ data = {
+ 'access_manager': {
+ 'users': {
+ self._user: current_user
+ }
+ }
+ }
+ self.save(data, update=True)
+
+ return current_user.get('watch_later', '')
+
+ def set_watch_later_id(self, playlist_id):
+ """
+ Sets the current users watch later playlist id
+ :param playlist_id: string, watch later playlist id
+ :return:
+ """
+ if playlist_id.lower() == 'wl' or playlist_id.lower() == ' wl':
+ playlist_id = ''
+
+ current_user = self.get_current_user_details()
+ current_user['watch_later'] = playlist_id
+ self._settings.set_watch_later_playlist('')
+ data = {
+ 'access_manager': {
+ 'users': {
+ self._user: current_user
+ }
+ }
+ }
+ self.save(data, update=True)
+
+ def get_watch_history_id(self):
+ """
+ Returns the current users watch history playlist id
+ :return: the current users watch history playlist id
+ """
+ current_user = self.get_current_user_details()
+ current_playlist_id = current_user.get('watch_history', 'HL')
+ settings_playlist_id = self._settings.get_history_playlist()
+
+ if settings_playlist_id and current_playlist_id != settings_playlist_id:
+ current_user['watch_history'] = settings_playlist_id
+ self._settings.set_history_playlist('')
+ data = {
+ 'access_manager': {
+ 'users': {
+ self._user: current_user
+ }
+ }
+ }
+ self.save(data, update=True)
+
+ return current_user.get('watch_history', 'HL')
+
+ def set_watch_history_id(self, playlist_id):
+ """
+ Sets the current users watch history playlist id
+ :param playlist_id: string, watch history playlist id
+ :return:
+ """
+ current_user = self.get_current_user_details()
+ current_user['watch_history'] = playlist_id
+ self._settings.set_history_playlist('')
+ data = {
+ 'access_manager': {
+ 'users': {
+ self._user: current_user
+ }
+ }
+ }
+ self.save(data, update=True)
+
+ def set_last_origin(self, origin):
+ """
+ Updates the origin
+ :param origin: string, origin
+ :return:
+ """
+ self._last_origin = origin
+ data = {
+ 'access_manager': {
+ 'last_origin': origin
+ }
+ }
+ self.save(data, update=True)
+
+ def get_last_origin(self):
+ """
+ Returns the last origin
+ :return:
+ """
+ return self._last_origin
+
+ def get_access_token(self):
+ """
+ Returns the access token for some API
+ :return: access_token
+ """
+ return self.get_current_user_details().get('access_token', '')
+
+ def get_refresh_token(self):
+ """
+ Returns the refresh token
+ :return: refresh token
+ """
+ return self.get_current_user_details().get('refresh_token', '')
+
+ def has_refresh_token(self):
+ return self.get_refresh_token() != ''
+
+ def is_access_token_expired(self):
+ """
+ Returns True if the access_token is expired otherwise False.
+ If no expiration date was provided and an access_token exists
+ this method will always return True
+ :return:
+ """
+ current_user = self.get_current_user_details()
+ access_token = current_user.get('access_token', '')
+ expires = int(current_user.get('token_expires', -1))
+
+ # with no access_token it must be expired
+ if not access_token:
+ return True
+
+ # in this case no expiration date was set
+ if expires == -1:
+ return False
+
+ now = int(time.time())
+ return expires <= now
+
+ def update_access_token(self,
+ access_token,
+ unix_timestamp=None,
+ refresh_token=None):
+ """
+ Updates the old access token with the new one.
+ :param access_token:
+ :param unix_timestamp:
+ :param refresh_token:
+ :return:
+ """
+ current_user = self.get_current_user_details()
+ current_user['access_token'] = access_token
+
+ if unix_timestamp is not None:
+ current_user['token_expires'] = int(unix_timestamp)
+
+ if refresh_token is not None:
+ current_user['refresh_token'] = refresh_token
+
+ data = {
+ 'access_manager': {
+ 'users': {
+ self._user: current_user
+ }
+ }
+ }
+ self.save(data, update=True)
+
+ @staticmethod
+ def get_new_developer():
+ """
+ :return: a new developer dict
+ """
+ return {
+ 'access_token': '',
+ 'refresh_token': '',
+ 'token_expires': -1,
+ 'last_key_hash': ''
+ }
+
+ def get_developers(self):
+ """
+ Returns developers
+ :return: dict, developers
+ """
+ return self._data['access_manager'].get('developers', {})
+
+ def get_developer(self, addon_id):
+ return self.get_developers().get(addon_id, {})
+
+ def set_developers(self, developers):
+ """
+ Updates the users
+ :param developers: dict, developers
+ :return:
+ """
+ data = self.get_data()
+ data['access_manager']['developers'] = developers
+ self.save(data)
+
+ def get_dev_access_token(self, addon_id):
+ """
+ Returns the access token for some API
+ :param addon_id: addon id
+ :return: access_token
+ """
+ return self.get_developer(addon_id).get('access_token', '')
+
+ def get_dev_refresh_token(self, addon_id):
+ """
+ Returns the refresh token
+ :return: refresh token
+ """
+ return self.get_developer(addon_id).get('refresh_token', '')
+
+ def developer_has_refresh_token(self, addon_id):
+ return self.get_dev_refresh_token(addon_id) != ''
+
+ def is_dev_access_token_expired(self, addon_id):
+ """
+ Returns True if the access_token is expired otherwise False.
+ If no expiration date was provided and an access_token exists
+ this method will always return True
+ :return:
+ """
+ developer = self.get_developer(addon_id)
+ access_token = developer.get('access_token', '')
+ expires = int(developer.get('token_expires', -1))
+
+ # with no access_token it must be expired
+ if not access_token:
+ return True
+
+ # in this case no expiration date was set
+ if expires == -1:
+ return False
+
+ now = int(time.time())
+ return expires <= now
+
+ def update_dev_access_token(self,
+ addon_id,
+ access_token,
+ unix_timestamp=None,
+ refresh_token=None):
+ """
+ Updates the old access token with the new one.
+ :param addon_id:
+ :param access_token:
+ :param unix_timestamp:
+ :param refresh_token:
+ :return:
+ """
+ developer = self.get_developer(addon_id)
+ developer['access_token'] = access_token
+
+ if unix_timestamp is not None:
+ developer['token_expires'] = int(unix_timestamp)
+
+ if refresh_token is not None:
+ developer['refresh_token'] = refresh_token
+
+ data = {
+ 'access_manager': {
+ 'developers': {
+ addon_id: developer
+ }
+ }
+ }
+ self.save(data, update=True)
+
+ def get_dev_last_key_hash(self, addon_id):
+ return self.get_developer(addon_id).get('last_key_hash', '')
+
+ def set_dev_last_key_hash(self, addon_id, key_hash):
+ developer = self.get_developer(addon_id)
+ developer['last_key_hash'] = key_hash
+ data = {
+ 'access_manager': {
+ 'developers': {
+ addon_id: developer
+ }
+ }
+ }
+ self.save(data, update=True)
+
+ def dev_keys_changed(self, addon_id, api_key, client_id, client_secret):
+ last_hash = self.get_dev_last_key_hash(addon_id)
+ current_hash = self.__calc_key_hash(api_key, client_id, client_secret)
+
+ if not last_hash and current_hash:
+ self.set_dev_last_key_hash(addon_id, current_hash)
+ return False
+
+ if last_hash != current_hash:
+ self.set_dev_last_key_hash(addon_id, current_hash)
+ return True
+
+ return False
+
+ @staticmethod
+ def __calc_key_hash(api_key, client_id, client_secret):
+
+ md5_hash = md5()
+ try:
+ md5_hash.update(api_key.encode('utf-8'))
+ md5_hash.update(client_id.encode('utf-8'))
+ md5_hash.update(client_secret.encode('utf-8'))
+ except:
+ md5_hash.update(api_key)
+ md5_hash.update(client_id)
+ md5_hash.update(client_secret)
+
+ return md5_hash.hexdigest()
diff --git a/resources/lib/youtube_plugin/kodion/json_store/api_keys.py b/resources/lib/youtube_plugin/kodion/json_store/api_keys.py
index aeaf8d0cd..3c20a22c4 100644
--- a/resources/lib/youtube_plugin/kodion/json_store/api_keys.py
+++ b/resources/lib/youtube_plugin/kodion/json_store/api_keys.py
@@ -7,7 +7,7 @@
See LICENSES/GPL-2.0-only for more information.
"""
-from . import JSONStore
+from .json_store import JSONStore
class APIKeyStore(JSONStore):
diff --git a/resources/lib/youtube_plugin/kodion/json_store/json_store.py b/resources/lib/youtube_plugin/kodion/json_store/json_store.py
index 577f431d1..09d5e5f9b 100644
--- a/resources/lib/youtube_plugin/kodion/json_store/json_store.py
+++ b/resources/lib/youtube_plugin/kodion/json_store/json_store.py
@@ -10,19 +10,20 @@
import json
import os
-import xbmcaddon
+from xbmcaddon import Addon
import xbmcvfs
from ..logger import log_debug, log_error
-from ..utils import make_dirs
+from ..utils import make_dirs, merge_dicts
+
+
+_addon_id = 'plugin.video.youtube'
+_addon = Addon(_addon_id)
class JSONStore(object):
def __init__(self, filename):
- addon_id = 'plugin.video.youtube'
- addon = xbmcaddon.Addon(addon_id)
-
- self.base_path = xbmcvfs.translatePath(addon.getAddonInfo('profile'))
+ self.base_path = xbmcvfs.translatePath(_addon.getAddonInfo('profile'))
if not xbmcvfs.exists(self.base_path) and not make_dirs(self.base_path):
log_error('JSONStore.__init__ |{path}| invalid path'.format(
@@ -38,8 +39,10 @@ def __init__(self, filename):
def set_defaults(self, reset=False):
raise NotImplementedError
- def save(self, data):
- if data == self._data:
+ def save(self, data, update=False, process=None):
+ if update:
+ data = merge_dicts(self._data, data)
+ elif data == self._data:
log_debug('JSONStore.save |{filename}| data unchanged'.format(
filename=self.filename
))
@@ -53,7 +56,7 @@ def save(self, data):
_data = json.loads(json.dumps(data))
with open(self.filename, mode='w', encoding='utf-8') as jsonfile:
json.dump(_data, jsonfile, indent=4, sort_keys=True)
- self._data = _data
+ self._data = process(_data) if process is not None else _data
except (IOError, OSError):
log_error('JSONStore.save |{filename}| no access to file'.format(
filename=self.filename
@@ -65,7 +68,7 @@ def save(self, data):
))
self.set_defaults(reset=True)
- def load(self):
+ def load(self, process=None):
log_debug('JSONStore.load |{filename}|'.format(
filename=self.filename
))
@@ -74,7 +77,8 @@ def load(self):
data = jsonfile.read()
if not data:
raise ValueError
- self._data = json.loads(data)
+ _data = json.loads(data)
+ self._data = process(_data) if process is not None else _data
except (IOError, OSError):
log_error('JSONStore.load |{filename}| no access to file'.format(
filename=self.filename
@@ -84,14 +88,16 @@ def load(self):
data=data
))
- def get_data(self):
+ def get_data(self, process=None):
try:
if not self._data:
raise ValueError
- return json.loads(json.dumps(self._data))
+ _data = json.loads(json.dumps(self._data))
+ return process(_data) if process is not None else _data
except (TypeError, ValueError):
log_error('JSONStore.get_data |{data}| invalid data'.format(
data=self._data
))
self.set_defaults(reset=True)
- return json.loads(json.dumps(self._data))
+ _data = json.loads(json.dumps(self._data))
+ return process(_data) if process is not None else _data
diff --git a/resources/lib/youtube_plugin/kodion/json_store/login_tokens.py b/resources/lib/youtube_plugin/kodion/json_store/login_tokens.py
deleted file mode 100644
index dedd70a94..000000000
--- a/resources/lib/youtube_plugin/kodion/json_store/login_tokens.py
+++ /dev/null
@@ -1,99 +0,0 @@
-# -*- coding: utf-8 -*-
-"""
-
- Copyright (C) 2018-2018 plugin.video.youtube
-
- SPDX-License-Identifier: GPL-2.0-only
- See LICENSES/GPL-2.0-only for more information.
-"""
-
-import uuid
-from . import JSONStore
-
-
-# noinspection PyTypeChecker
-class LoginTokenStore(JSONStore):
- DEFAULT_NEW_USER = {
- 'access_token': '',
- 'refresh_token': '',
- 'token_expires': -1,
- 'last_key_hash': '',
- 'name': 'Default',
- 'watch_later': ' WL',
- 'watch_history': 'HL'
- }
-
- def __init__(self):
- super(LoginTokenStore, self).__init__('access_manager.json')
-
- def set_defaults(self, reset=False):
- data = {} if reset else self.get_data()
- if 'access_manager' not in data:
- data = {
- 'access_manager': {
- 'users': {
- 0: self.DEFAULT_NEW_USER.copy()
- }
- }
- }
- if 'users' not in data['access_manager']:
- data['access_manager']['users'] = {
- 0: self.DEFAULT_NEW_USER.copy()
- }
- if 0 not in data['access_manager']['users']:
- data['access_manager']['users'][0] = self.DEFAULT_NEW_USER.copy()
- if 'current_user' not in data['access_manager']:
- data['access_manager']['current_user'] = 0
- if 'last_origin' not in data['access_manager']:
- data['access_manager']['last_origin'] = 'plugin.video.youtube'
- if 'developers' not in data['access_manager']:
- data['access_manager']['developers'] = {}
-
- # clean up
- if data['access_manager']['current_user'] == 'default':
- data['access_manager']['current_user'] = 0
- if 'access_token' in data['access_manager']:
- del data['access_manager']['access_token']
- if 'refresh_token' in data['access_manager']:
- del data['access_manager']['refresh_token']
- if 'token_expires' in data['access_manager']:
- del data['access_manager']['token_expires']
- if 'default' in data['access_manager']:
- if ((data['access_manager']['default'].get('access_token')
- or data['access_manager']['default'].get('refresh_token'))
- and not data['access_manager']['users'][0].get('access_token')
- and not data['access_manager']['users'][0].get('refresh_token')):
- if 'name' not in data['access_manager']['default']:
- data['access_manager']['default']['name'] = 'Default'
- data['access_manager']['users'][0] = data['access_manager']['default']
- del data['access_manager']['default']
- # end clean up
-
- current_user = data['access_manager']['current_user']
- if 'watch_later' not in data['access_manager']['users'][current_user]:
- data['access_manager']['users'][current_user]['watch_later'] = ' WL'
- if 'watch_history' not in data['access_manager']['users'][current_user]:
- data['access_manager']['users'][current_user]['watch_history'] = 'HL'
-
- # ensure all users have uuid
- uuids = set()
- for user in data['access_manager']['users'].values():
- c_uuid = user.get('id')
- while not c_uuid or c_uuid in uuids:
- c_uuid = uuid.uuid4().hex
- uuids.add(c_uuid)
- user['id'] = c_uuid
- # end uuid check
-
- self.save(data)
-
- def get_data(self):
- data = super(LoginTokenStore, self).get_data()
- # process users, change str keys to int
- users = data['access_manager']['users']
- if '0' in users:
- data['access_manager']['users'] = {
- int(key): value
- for key, value in users.items()
- }
- return data
diff --git a/resources/lib/youtube_plugin/kodion/utils/__init__.py b/resources/lib/youtube_plugin/kodion/utils/__init__.py
index bffb440ce..9a5afdfac 100644
--- a/resources/lib/youtube_plugin/kodion/utils/__init__.py
+++ b/resources/lib/youtube_plugin/kodion/utils/__init__.py
@@ -30,7 +30,6 @@
from .favorite_list import FavoriteList
from .watch_later_list import WatchLaterList
from .function_cache import FunctionCache
-from .access_manager import AccessManager
from .monitor import YouTubeMonitor
from .player import YouTubePlayer
from .playback_history import PlaybackHistory
@@ -55,7 +54,6 @@
'to_str',
'to_unicode',
'to_utf8',
- 'AccessManager',
'DataCache',
'FavoriteList',
'FunctionCache',
diff --git a/resources/lib/youtube_plugin/kodion/utils/access_manager.py b/resources/lib/youtube_plugin/kodion/utils/access_manager.py
deleted file mode 100644
index c2f2d4d34..000000000
--- a/resources/lib/youtube_plugin/kodion/utils/access_manager.py
+++ /dev/null
@@ -1,418 +0,0 @@
-# -*- coding: utf-8 -*-
-"""
-
- Copyright (C) 2014-2016 bromix (plugin.video.youtube)
- Copyright (C) 2016-2018 plugin.video.youtube
-
- SPDX-License-Identifier: GPL-2.0-only
- See LICENSES/GPL-2.0-only for more information.
-"""
-
-import uuid
-import time
-
-from hashlib import md5
-
-from ..json_store import LoginTokenStore
-
-__author__ = 'bromix'
-
-
-class AccessManager(object):
- def __init__(self, context):
- self._settings = context.get_settings()
- self._jstore = LoginTokenStore()
- self._json = self._jstore.get_data()
- self._user = self._json['access_manager'].get('current_user', 0)
- self._last_origin = self._json['access_manager'].get('last_origin', 'plugin.video.youtube')
-
- def get_current_user_id(self):
- """
-
- :return: uuid of the current user
- """
- self._json = self._jstore.get_data()
- return self._json['access_manager']['users'][self.get_user()]['id']
-
- def get_new_user(self, username=''):
- """
- :param username: string, users name
- :return: a new user dict
- """
- uuids = [
- user.get('id')
- for user in self._json['access_manager']['users'].values()
- ]
- new_uuid = None
- while not new_uuid or new_uuid in uuids:
- new_uuid = uuid.uuid4().hex
- return {
- 'access_token': '',
- 'refresh_token': '',
- 'token_expires': -1,
- 'last_key_hash': '',
- 'name': username,
- 'id': new_uuid,
- 'watch_later': ' WL',
- 'watch_history': 'HL'
- }
-
- def get_users(self):
- """
- Returns users
- :return: users
- """
- return self._json['access_manager'].get('users', {})
-
- def add_user(self, username='', user=None):
- """
- Add single new user to users collection
- :param username: str, chosen name of new user
- :param user: int, optional index for new user
- :return: tuple, (index, details) of newly added user
- """
- users = self._json['access_manager'].get('users', {})
- new_user_details = self.get_new_user(username)
- new_user = max(users) + 1 if users and user is None else user or 0
- users[new_user] = new_user_details
- self._json['access_manager']['users'] = users
- self._jstore.save(self._json)
- return new_user, new_user_details
-
- def remove_user(self, user):
- """
- Remove user from collection of current users
- :param user: int, user index
- :return:
- """
- users = self._json['access_manager'].get('users', {})
- if user in users:
- del users[user]
- self._json['access_manager']['users'] = users
- self._jstore.save(self._json)
-
- def set_users(self, users):
- """
- Updates all users
- :param users: dict, users
- :return:
- """
- self._json = self._jstore.get_data()
- self._json['access_manager']['users'] = users
- self._jstore.save(self._json)
-
- def set_user(self, user, switch_to=False):
- """
- Updates the user
- :param user: string, username
- :param switch_to: boolean, change current user
- :return:
- """
- self._user = user
- if switch_to:
- self._json = self._jstore.get_data()
- self._json['access_manager']['current_user'] = user
- self._jstore.save(self._json)
-
- def get_user(self):
- """
- Returns the current user
- :return: user
- """
- return self._user
-
- def get_username(self, user=None):
- """
- Returns the username of the current or nominated user
- :return: username
- """
- if user is None:
- user = self._user
- users = self._json['access_manager'].get('users', {})
- if user in users:
- return users[user].get('name')
- return ''
-
- def set_username(self, user, username):
- """
- Sets the username of the nominated user
- :return: True if username was set, false otherwise
- """
- users = self._json['access_manager'].get('users', {})
- if user in users:
- users[user]['name'] = username
- self._json['access_manager']['users'] = users
- self._jstore.save(self._json)
- return True
- return False
-
- def get_watch_later_id(self):
- """
- Returns the current users watch later playlist id
- :return: the current users watch later playlist id
- """
-
- self._json = self._jstore.get_data()
- current_playlist_id = self._json['access_manager']['users'].get(self._user, {}).get('watch_later', '')
- settings_playlist_id = self._settings.get_string('youtube.folder.watch_later.playlist', '').strip()
-
- if settings_playlist_id.lower().startswith(('wl', ' wl')):
- self._settings.set_string('youtube.folder.watch_later.playlist', '')
- settings_playlist_id = ''
-
- if current_playlist_id.lower().startswith(('wl', ' wl')):
- self._json['access_manager']['users'][self._user]['watch_later'] = settings_playlist_id
- self._jstore.save(self._json)
-
- self._settings.set_string('youtube.folder.watch_later.playlist', '')
- settings_playlist_id = ''
-
- if settings_playlist_id and current_playlist_id != settings_playlist_id:
- self._json['access_manager']['users'][self._user]['watch_later'] = settings_playlist_id
- self._jstore.save(self._json)
-
- self._settings.set_string('youtube.folder.watch_later.playlist', '')
-
- return self._json['access_manager']['users'].get(self._user, {}).get('watch_later', '')
-
- def set_watch_later_id(self, playlist_id):
- """
- Sets the current users watch later playlist id
- :param playlist_id: string, watch later playlist id
- :return:
- """
- if playlist_id.lower() == 'wl' or playlist_id.lower() == ' wl':
- playlist_id = ''
-
- self._json = self._jstore.get_data()
- self._json['access_manager']['users'][self._user]['watch_later'] = playlist_id
- self._settings.set_string('youtube.folder.watch_later.playlist', '')
- self._jstore.save(self._json)
-
- def get_watch_history_id(self):
- """
- Returns the current users watch history playlist id
- :return: the current users watch history playlist id
- """
-
- self._json = self._jstore.get_data()
- current_playlist_id = self._json['access_manager']['users'].get(self._user, {}).get('watch_history', 'HL')
- settings_playlist_id = self._settings.get_string('youtube.folder.history.playlist', '').strip()
- if settings_playlist_id and (current_playlist_id != settings_playlist_id):
- self._json['access_manager']['users'][self._user]['watch_history'] = settings_playlist_id
- self._jstore.save(self._json)
- self._settings.set_string('youtube.folder.history.playlist', '')
- return self._json['access_manager']['users'].get(self._user, {}).get('watch_history', 'HL')
-
- def set_watch_history_id(self, playlist_id):
- """
- Sets the current users watch history playlist id
- :param playlist_id: string, watch history playlist id
- :return:
- """
-
- self._json = self._jstore.get_data()
- self._json['access_manager']['users'][self._user]['watch_history'] = playlist_id
- self._settings.set_string('youtube.folder.history.playlist', '')
- self._jstore.save(self._json)
-
- def set_last_origin(self, origin):
- """
- Updates the origin
- :param origin: string, origin
- :return:
- """
- self._last_origin = origin
- self._json = self._jstore.get_data()
- self._json['access_manager']['last_origin'] = origin
- self._jstore.save(self._json)
-
- def get_last_origin(self):
- """
- Returns the last origin
- :return:
- """
- return self._last_origin
-
- def get_access_token(self):
- """
- Returns the access token for some API
- :return: access_token
- """
- self._json = self._jstore.get_data()
- return self._json['access_manager']['users'].get(self._user, {}).get('access_token', '')
-
- def get_refresh_token(self):
- """
- Returns the refresh token
- :return: refresh token
- """
- self._json = self._jstore.get_data()
- return self._json['access_manager']['users'].get(self._user, {}).get('refresh_token', '')
-
- def has_refresh_token(self):
- return self.get_refresh_token() != ''
-
- def is_access_token_expired(self):
- """
- Returns True if the access_token is expired otherwise False.
- If no expiration date was provided and an access_token exists
- this method will always return True
- :return:
- """
- self._json = self._jstore.get_data()
- access_token = self._json['access_manager']['users'].get(self._user, {}).get('access_token', '')
- expires = int(self._json['access_manager']['users'].get(self._user, {}).get('token_expires', -1))
-
- # with no access_token it must be expired
- if not access_token:
- return True
-
- # in this case no expiration date was set
- if expires == -1:
- return False
-
- now = int(time.time())
- return expires <= now
-
- def update_access_token(self, access_token, unix_timestamp=None, refresh_token=None):
- """
- Updates the old access token with the new one.
- :param access_token:
- :param unix_timestamp:
- :param refresh_token:
- :return:
- """
- self._json = self._jstore.get_data()
- self._json['access_manager']['users'][self._user]['access_token'] = access_token
-
- if unix_timestamp is not None:
- self._json['access_manager']['users'][self._user]['token_expires'] = int(unix_timestamp)
-
- if refresh_token is not None:
- self._json['access_manager']['users'][self._user]['refresh_token'] = refresh_token
-
- self._jstore.save(self._json)
-
- @staticmethod
- def get_new_developer():
- """
- :return: a new developer dict
- """
-
- return {'access_token': '', 'refresh_token': '', 'token_expires': -1, 'last_key_hash': ''}
-
- def get_developers(self):
- """
- Returns developers
- :return: dict, developers
- """
- return self._json['access_manager'].get('developers', {})
-
- def set_developers(self, developers):
- """
- Updates the users
- :param developers: dict, developers
- :return:
- """
- self._json = self._jstore.get_data()
- self._json['access_manager']['developers'] = developers
- self._jstore.save(self._json)
-
- def get_dev_access_token(self, addon_id):
- """
- Returns the access token for some API
- :param addon_id: addon id
- :return: access_token
- """
- self._json = self._jstore.get_data()
- return self._json['access_manager']['developers'].get(addon_id, {}).get('access_token', '')
-
- def get_dev_refresh_token(self, addon_id):
- """
- Returns the refresh token
- :return: refresh token
- """
- self._json = self._jstore.get_data()
- return self._json['access_manager']['developers'].get(addon_id, {}).get('refresh_token', '')
-
- def developer_has_refresh_token(self, addon_id):
- return self.get_dev_refresh_token(addon_id) != ''
-
- def is_dev_access_token_expired(self, addon_id):
- """
- Returns True if the access_token is expired otherwise False.
- If no expiration date was provided and an access_token exists
- this method will always return True
- :return:
- """
- self._json = self._jstore.get_data()
- access_token = self._json['access_manager']['developers'].get(addon_id, {}).get('access_token', '')
- expires = int(self._json['access_manager']['developers'].get(addon_id, {}).get('token_expires', -1))
-
- # with no access_token it must be expired
- if not access_token:
- return True
-
- # in this case no expiration date was set
- if expires == -1:
- return False
-
- now = int(time.time())
- return expires <= now
-
- def update_dev_access_token(self, addon_id, access_token, unix_timestamp=None, refresh_token=None):
- """
- Updates the old access token with the new one.
- :param addon_id:
- :param access_token:
- :param unix_timestamp:
- :param refresh_token:
- :return:
- """
- self._json = self._jstore.get_data()
- self._json['access_manager']['developers'][addon_id]['access_token'] = access_token
-
- if unix_timestamp is not None:
- self._json['access_manager']['developers'][addon_id]['token_expires'] = int(unix_timestamp)
-
- if refresh_token is not None:
- self._json['access_manager']['developers'][addon_id]['refresh_token'] = refresh_token
-
- self._jstore.save(self._json)
-
- def get_dev_last_key_hash(self, addon_id):
- self._json = self._jstore.get_data()
- return self._json['access_manager']['developers'][addon_id]['last_key_hash']
-
- def set_dev_last_key_hash(self, addon_id, key_hash):
- self._json = self._jstore.get_data()
- self._json['access_manager']['developers'][addon_id]['last_key_hash'] = key_hash
- self._jstore.save(self._json)
-
- def dev_keys_changed(self, addon_id, api_key, client_id, client_secret):
- self._json = self._jstore.get_data()
- last_hash = self._json['access_manager']['developers'][addon_id]['last_key_hash']
- current_hash = self.__calc_key_hash(api_key, client_id, client_secret)
- if not last_hash and current_hash:
- self.set_dev_last_key_hash(addon_id, current_hash)
- return False
- if last_hash != current_hash:
- self.set_dev_last_key_hash(addon_id, current_hash)
- return True
- return False
-
- @staticmethod
- def __calc_key_hash(api_key, client_id, client_secret):
-
- m = md5()
- try:
- m.update(api_key.encode('utf-8'))
- m.update(client_id.encode('utf-8'))
- m.update(client_secret.encode('utf-8'))
- except:
- m.update(api_key)
- m.update(client_id)
- m.update(client_secret)
-
- return m.hexdigest()
diff --git a/resources/lib/youtube_plugin/youtube/client/__config__.py b/resources/lib/youtube_plugin/youtube/client/__config__.py
index c52bc2757..656ea8f5b 100644
--- a/resources/lib/youtube_plugin/youtube/client/__config__.py
+++ b/resources/lib/youtube_plugin/youtube/client/__config__.py
@@ -9,7 +9,7 @@
from base64 import b64decode
from hashlib import md5
-from ...kodion.json_store import APIKeyStore, LoginTokenStore
+from ...kodion.json_store import AccessManager, APIKeyStore
from ...kodion import Context as __Context
from ... import key_sets
@@ -27,8 +27,8 @@ def __init__(self, context, settings):
self._ui = context.get_ui()
self._api_jstore = APIKeyStore()
self._json_api = self._api_jstore.get_data()
- self._am_jstore = LoginTokenStore()
- self._json_am = self._am_jstore.get_data()
+ self._access_manager = AccessManager(context)
+ self._json_am = self._access_manager.get_data()
self.changed = False
self._on_init()
@@ -92,7 +92,7 @@ def _on_init(self):
if (last_hash == self._get_key_set_hash('own', True)
or last_hash == own_key_hash):
self._json_am['access_manager']['users'][user]['last_key_hash'] = own_key_hash
- self._am_jstore.save(self._json_am)
+ self._access_manager.save(self._json_am)
if access_token or refresh_token or last_hash:
self._settings.set_string('kodion.access_token', '')
self._settings.set_string('kodion.refresh_token', '')
@@ -103,7 +103,7 @@ def _on_init(self):
if updated_hash:
self._context.log_warning('User: |%s| Switching API key set to |%s|' % (user, switch))
self._json_am['access_manager']['users'][user]['last_key_hash'] = updated_hash
- self._am_jstore.save(self._json_am)
+ self._access_manager.save(self._json_am)
self._context.log_debug('API key set changed: Signing out')
self._context.execute('RunPlugin(plugin://plugin.video.youtube/sign/out/?confirmed=true)')
else:
@@ -113,7 +113,7 @@ def get_current_switch(self):
return 'own'
def get_current_user(self):
- self._json_am = self._am_jstore.get_data()
+ self._json_am = self._access_manager.get_data()
return self._json_am['access_manager'].get('current_user', 0)
def has_own_api_keys(self):
@@ -142,7 +142,7 @@ def get_api_keys(self, switch):
return api_key, client_id, client_secret
def _api_keys_changed(self, switch):
- self._json_am = self._am_jstore.get_data()
+ self._json_am = self._access_manager.get_data()
user = self.get_current_user()
last_set_hash = self._json_am['access_manager']['users'].get(user, {}).get('last_key_hash', '')
current_set_hash = self._get_key_set_hash(switch)
diff --git a/resources/lib/youtube_plugin/youtube/provider.py b/resources/lib/youtube_plugin/youtube/provider.py
index 4cfe07f5f..777fd3812 100644
--- a/resources/lib/youtube_plugin/youtube/provider.py
+++ b/resources/lib/youtube_plugin/youtube/provider.py
@@ -691,7 +691,7 @@ def _on_users(self, context, re_match):
def select_user(reason, new_user=False):
current_users = access_manager.get_users()
- current_user = access_manager.get_user()
+ current_user = access_manager.get_current_user()
usernames = []
for user, details in sorted(current_users.items()):
username = details.get('name') or localize('user.unnamed')
@@ -733,7 +733,7 @@ def switch_to_user(user):
else:
user = user_index_map[result]
- if user is not None and user != access_manager.get_user():
+ if user is not None and user != access_manager.get_current_user():
switch_to_user(user)
elif action == 'add':
@@ -758,7 +758,7 @@ def switch_to_user(user):
if user == 0:
access_manager.add_user(username=localize('user.default'),
user=0)
- if user == access_manager.get_user():
+ if user == access_manager.get_current_user():
access_manager.set_user(0, switch_to=True)
ui.show_notification(localize('removed') % username,
localize('remove'))
From 40869c6764d51f9aa8fa3edc2665022715fe8d67 Mon Sep 17 00:00:00 2001
From: MoojMidge <56883549+MoojMidge@users.noreply.github.com>
Date: Sun, 10 Dec 2023 02:15:42 +1100
Subject: [PATCH 075/141] Move sql storage based classes to new
kodion.sql_store module
- Originally located in kodion.utils
- Prevents possible issues with circular imports
---
.../kodion/context/abstract_context.py | 9 ++---
.../kodion/sql_store/__init__.py | 25 ++++++++++++++
.../kodion/{utils => sql_store}/data_cache.py | 3 +-
.../{utils => sql_store}/favorite_list.py | 6 ++--
.../{utils => sql_store}/function_cache.py | 18 +++++-----
.../{utils => sql_store}/playback_history.py | 0
.../{utils => sql_store}/search_history.py | 10 +++---
.../kodion/{utils => sql_store}/storage.py | 34 +++++++++----------
.../{utils => sql_store}/watch_later_list.py | 4 +--
.../youtube_plugin/kodion/utils/__init__.py | 12 -------
10 files changed, 66 insertions(+), 55 deletions(-)
create mode 100644 resources/lib/youtube_plugin/kodion/sql_store/__init__.py
rename resources/lib/youtube_plugin/kodion/{utils => sql_store}/data_cache.py (93%)
rename resources/lib/youtube_plugin/kodion/{utils => sql_store}/favorite_list.py (84%)
rename resources/lib/youtube_plugin/kodion/{utils => sql_store}/function_cache.py (84%)
rename resources/lib/youtube_plugin/kodion/{utils => sql_store}/playback_history.py (100%)
rename resources/lib/youtube_plugin/kodion/{utils => sql_store}/search_history.py (87%)
rename resources/lib/youtube_plugin/kodion/{utils => sql_store}/storage.py (91%)
rename resources/lib/youtube_plugin/kodion/{utils => sql_store}/watch_later_list.py (91%)
diff --git a/resources/lib/youtube_plugin/kodion/context/abstract_context.py b/resources/lib/youtube_plugin/kodion/context/abstract_context.py
index 99af7c3fb..a671c34b9 100644
--- a/resources/lib/youtube_plugin/kodion/context/abstract_context.py
+++ b/resources/lib/youtube_plugin/kodion/context/abstract_context.py
@@ -11,20 +11,17 @@
import os
from urllib.parse import urlencode
-from .. import constants
-from .. import logger
+from .. import constants, logger
from ..json_store import AccessManager
-from ..utils import (
- create_path,
- create_uri_path,
+from ..sql_store import (
DataCache,
FavoriteList,
FunctionCache,
PlaybackHistory,
SearchHistory,
- SystemVersion,
WatchLaterList,
)
+from ..utils import (SystemVersion, create_path, create_uri_path)
class AbstractContext(object):
diff --git a/resources/lib/youtube_plugin/kodion/sql_store/__init__.py b/resources/lib/youtube_plugin/kodion/sql_store/__init__.py
new file mode 100644
index 000000000..46da38d54
--- /dev/null
+++ b/resources/lib/youtube_plugin/kodion/sql_store/__init__.py
@@ -0,0 +1,25 @@
+# -*- coding: utf-8 -*-
+"""
+
+ Copyright (C) 2023-present plugin.video.youtube
+
+ SPDX-License-Identifier: GPL-2.0-only
+ See LICENSES/GPL-2.0-only for more information.
+"""
+
+from .data_cache import DataCache
+from .favorite_list import FavoriteList
+from .function_cache import FunctionCache
+from .playback_history import PlaybackHistory
+from .search_history import SearchHistory
+from .watch_later_list import WatchLaterList
+
+
+__all__ = (
+ 'DataCache',
+ 'FavoriteList',
+ 'FunctionCache',
+ 'PlaybackHistory',
+ 'SearchHistory',
+ 'WatchLaterList',
+)
diff --git a/resources/lib/youtube_plugin/kodion/utils/data_cache.py b/resources/lib/youtube_plugin/kodion/sql_store/data_cache.py
similarity index 93%
rename from resources/lib/youtube_plugin/kodion/utils/data_cache.py
rename to resources/lib/youtube_plugin/kodion/sql_store/data_cache.py
index f3a29ed92..fb8ca4134 100644
--- a/resources/lib/youtube_plugin/kodion/utils/data_cache.py
+++ b/resources/lib/youtube_plugin/kodion/sql_store/data_cache.py
@@ -17,7 +17,8 @@
class DataCache(Storage):
def __init__(self, filename, max_file_size_mb=5):
max_file_size_kb = max_file_size_mb * 1024
- super(DataCache, self).__init__(filename, max_file_size_kb=max_file_size_kb)
+ super(DataCache, self).__init__(filename,
+ max_file_size_kb=max_file_size_kb)
def is_empty(self):
return self._is_empty()
diff --git a/resources/lib/youtube_plugin/kodion/utils/favorite_list.py b/resources/lib/youtube_plugin/kodion/sql_store/favorite_list.py
similarity index 84%
rename from resources/lib/youtube_plugin/kodion/utils/favorite_list.py
rename to resources/lib/youtube_plugin/kodion/sql_store/favorite_list.py
index d18a013f6..11504727b 100644
--- a/resources/lib/youtube_plugin/kodion/utils/favorite_list.py
+++ b/resources/lib/youtube_plugin/kodion/sql_store/favorite_list.py
@@ -9,7 +9,7 @@
"""
from .storage import Storage
-from .. import items
+from ..items import from_json, to_json
class FavoriteList(Storage):
@@ -24,11 +24,11 @@ def _sort_item(_item):
return _item[2].get_name().upper()
def get_items(self):
- result = self._get_by_ids(process=items.from_json)
+ result = self._get_by_ids(process=from_json)
return sorted(result, key=self._sort_item, reverse=False)
def add(self, base_item):
- item_json_data = items.to_json(base_item)
+ item_json_data = to_json(base_item)
self._set(base_item.get_id(), item_json_data)
def remove(self, base_item):
diff --git a/resources/lib/youtube_plugin/kodion/utils/function_cache.py b/resources/lib/youtube_plugin/kodion/sql_store/function_cache.py
similarity index 84%
rename from resources/lib/youtube_plugin/kodion/utils/function_cache.py
rename to resources/lib/youtube_plugin/kodion/sql_store/function_cache.py
index 3929878dc..7b71ad10a 100644
--- a/resources/lib/youtube_plugin/kodion/utils/function_cache.py
+++ b/resources/lib/youtube_plugin/kodion/sql_store/function_cache.py
@@ -9,7 +9,7 @@
"""
from functools import partial
-import hashlib
+from hashlib import md5
from .storage import Storage
@@ -45,12 +45,12 @@ def _create_id_from_func(partial_func):
:param partial_func:
:return: id for the given function
"""
- m = hashlib.md5()
- m.update(partial_func.func.__module__.encode('utf-8'))
- m.update(partial_func.func.__name__.encode('utf-8'))
- m.update(str(partial_func.args).encode('utf-8'))
- m.update(str(partial_func.keywords).encode('utf-8'))
- return m.hexdigest()
+ md5_hash = md5()
+ md5_hash.update(partial_func.func.__module__.encode('utf-8'))
+ md5_hash.update(partial_func.func.__name__.encode('utf-8'))
+ md5_hash.update(str(partial_func.args).encode('utf-8'))
+ md5_hash.update(str(partial_func.keywords).encode('utf-8'))
+ return md5_hash.hexdigest()
def _get_cached_data(self, partial_func):
cache_id = self._create_id_from_func(partial_func)
@@ -103,6 +103,6 @@ def get(self, func, seconds, *args, **keywords):
return cached_data
def _optimize_item_count(self):
- # override method from resources/lib/youtube_plugin/kodion/utils/storage.py
- # for function cache do not optimize by item count, using database size.
+ # override method Storage._optimize_item_count
+ # for function cache do not optimize by item count, use database size.
pass
diff --git a/resources/lib/youtube_plugin/kodion/utils/playback_history.py b/resources/lib/youtube_plugin/kodion/sql_store/playback_history.py
similarity index 100%
rename from resources/lib/youtube_plugin/kodion/utils/playback_history.py
rename to resources/lib/youtube_plugin/kodion/sql_store/playback_history.py
diff --git a/resources/lib/youtube_plugin/kodion/utils/search_history.py b/resources/lib/youtube_plugin/kodion/sql_store/search_history.py
similarity index 87%
rename from resources/lib/youtube_plugin/kodion/utils/search_history.py
rename to resources/lib/youtube_plugin/kodion/sql_store/search_history.py
index 9eae48318..a06d8c427 100644
--- a/resources/lib/youtube_plugin/kodion/utils/search_history.py
+++ b/resources/lib/youtube_plugin/kodion/sql_store/search_history.py
@@ -8,10 +8,10 @@
See LICENSES/GPL-2.0-only for more information.
"""
-import hashlib
+from hashlib import md5
from .storage import Storage
-from .methods import to_utf8
+from ..utils import to_utf8
class SearchHistory(Storage):
@@ -32,9 +32,9 @@ def clear(self):
@staticmethod
def _make_id(search_text):
- m = hashlib.md5()
- m.update(to_utf8(search_text))
- return m.hexdigest()
+ md5_hash = md5()
+ md5_hash.update(to_utf8(search_text))
+ return md5_hash.hexdigest()
def rename(self, old_search_text, new_search_text):
self.remove(old_search_text)
diff --git a/resources/lib/youtube_plugin/kodion/utils/storage.py b/resources/lib/youtube_plugin/kodion/sql_store/storage.py
similarity index 91%
rename from resources/lib/youtube_plugin/kodion/utils/storage.py
rename to resources/lib/youtube_plugin/kodion/sql_store/storage.py
index 0306ee053..28b7bc705 100644
--- a/resources/lib/youtube_plugin/kodion/utils/storage.py
+++ b/resources/lib/youtube_plugin/kodion/sql_store/storage.py
@@ -8,15 +8,15 @@
See LICENSES/GPL-2.0-only for more information.
"""
-import datetime
import json
import os
import pickle
import sqlite3
import time
-import traceback
+from datetime import datetime
+from traceback import print_exc
-from .. import logger
+from ..logger import log_error
class Storage(object):
@@ -155,7 +155,7 @@ def _sync(self):
def _set(self, item_id, item):
# add 1 microsecond, required for dbapi2
- now = datetime.datetime.now().timestamp() + 0.000001
+ now = datetime.now().timestamp() + 0.000001
self._open()
self._execute(True, self._set_query, values=[item_id,
now,
@@ -165,7 +165,7 @@ def _set(self, item_id, item):
def _set_all(self, items):
# add 1 microsecond, required for dbapi2
- now = datetime.datetime.now().timestamp() + 0.000001
+ now = datetime.now().timestamp() + 0.000001
self._open()
self._execute(True, self._set_query,
values=[(key, now, self._encode(json.dumps(item)))
@@ -278,27 +278,27 @@ def _convert_timestamp(cls, val):
val = val.decode('utf-8')
if '-' in val or ':' in val:
return cls._parse_datetime_string(val)
- return datetime.datetime.fromtimestamp(float(val))
+ return datetime.fromtimestamp(float(val))
@classmethod
def _parse_datetime_string(cls, current_stamp):
for stamp_format in ['%Y-%m-%d %H:%M:%S.%f', '%Y-%m-%d %H:%M:%S']:
try:
- stamp_datetime = datetime.datetime(
+ stamp_datetime = datetime(
*(cls.strptime(current_stamp, stamp_format)[0:6])
)
break
except ValueError: # current_stamp has no microseconds
continue
except TypeError:
- logger.log_error('Exception while parsing timestamp:\n'
- 'current_stamp |{cs}|{cst}|\n'
- 'stamp_format |{sf}|{sft}|\n{tb}'
- .format(cs=current_stamp,
- cst=type(current_stamp),
- sf=stamp_format,
- sft=type(stamp_format),
- tb=traceback.print_exc()))
+ log_error('Exception while parsing timestamp:\n'
+ 'current_stamp |{cs}|{cst}|\n'
+ 'stamp_format |{sf}|{sft}|\n{tb}'
+ .format(cs=current_stamp,
+ cst=type(current_stamp),
+ sf=stamp_format,
+ sft=type(stamp_format),
+ tb=print_exc()))
else:
return None
return stamp_datetime
@@ -307,8 +307,8 @@ def get_seconds_diff(self, current_stamp):
if not current_stamp:
return 86400 # 24 hrs
- current_datetime = datetime.datetime.now()
- if isinstance(current_stamp, datetime.datetime):
+ current_datetime = datetime.now()
+ if isinstance(current_stamp, datetime):
time_delta = current_datetime - current_stamp
return time_delta.total_seconds()
diff --git a/resources/lib/youtube_plugin/kodion/utils/watch_later_list.py b/resources/lib/youtube_plugin/kodion/sql_store/watch_later_list.py
similarity index 91%
rename from resources/lib/youtube_plugin/kodion/utils/watch_later_list.py
rename to resources/lib/youtube_plugin/kodion/sql_store/watch_later_list.py
index 20425d513..5da49a5d3 100644
--- a/resources/lib/youtube_plugin/kodion/utils/watch_later_list.py
+++ b/resources/lib/youtube_plugin/kodion/sql_store/watch_later_list.py
@@ -8,7 +8,7 @@
See LICENSES/GPL-2.0-only for more information.
"""
-import datetime
+from datetime import datetime
from .storage import Storage
from .. import items
@@ -30,7 +30,7 @@ def get_items(self):
return sorted(result, key=self._sort_item, reverse=False)
def add(self, base_item):
- base_item.set_date_from_datetime(datetime.datetime.now())
+ base_item.set_date_from_datetime(datetime.now())
item_json_data = items.to_json(base_item)
self._set(base_item.get_id(), item_json_data)
diff --git a/resources/lib/youtube_plugin/kodion/utils/__init__.py b/resources/lib/youtube_plugin/kodion/utils/__init__.py
index 9a5afdfac..af9d750b9 100644
--- a/resources/lib/youtube_plugin/kodion/utils/__init__.py
+++ b/resources/lib/youtube_plugin/kodion/utils/__init__.py
@@ -26,14 +26,8 @@
to_unicode,
to_utf8,
)
-from .search_history import SearchHistory
-from .favorite_list import FavoriteList
-from .watch_later_list import WatchLaterList
-from .function_cache import FunctionCache
from .monitor import YouTubeMonitor
from .player import YouTubePlayer
-from .playback_history import PlaybackHistory
-from .data_cache import DataCache
from .system_version import SystemVersion
@@ -54,13 +48,7 @@
'to_str',
'to_unicode',
'to_utf8',
- 'DataCache',
- 'FavoriteList',
- 'FunctionCache',
- 'PlaybackHistory',
- 'SearchHistory',
'SystemVersion',
- 'WatchLaterList',
'YouTubeMonitor',
'YouTubePlayer',
)
From e136f43d65bdbd977a7d67fb8b75efdc88ca3a3f Mon Sep 17 00:00:00 2001
From: MoojMidge <56883549+MoojMidge@users.noreply.github.com>
Date: Sun, 10 Dec 2023 19:57:47 +1100
Subject: [PATCH 076/141] Use single instance of SystemVersion
---
.../kodion/context/abstract_context.py | 4 +--
.../youtube_plugin/kodion/utils/__init__.py | 4 +--
.../kodion/utils/system_version.py | 27 ++++++++++++++-----
3 files changed, 25 insertions(+), 10 deletions(-)
diff --git a/resources/lib/youtube_plugin/kodion/context/abstract_context.py b/resources/lib/youtube_plugin/kodion/context/abstract_context.py
index a671c34b9..a46327767 100644
--- a/resources/lib/youtube_plugin/kodion/context/abstract_context.py
+++ b/resources/lib/youtube_plugin/kodion/context/abstract_context.py
@@ -21,7 +21,7 @@
SearchHistory,
WatchLaterList,
)
-from ..utils import (SystemVersion, create_path, create_uri_path)
+from ..utils import (create_path, create_uri_path, current_system_version)
class AbstractContext(object):
@@ -204,7 +204,7 @@ def get_ui(self):
def get_system_version(self):
if not self._system_version:
- self._system_version = SystemVersion(version='', releasename='', appname='')
+ self._system_version = current_system_version
return self._system_version
diff --git a/resources/lib/youtube_plugin/kodion/utils/__init__.py b/resources/lib/youtube_plugin/kodion/utils/__init__.py
index af9d750b9..9130dae36 100644
--- a/resources/lib/youtube_plugin/kodion/utils/__init__.py
+++ b/resources/lib/youtube_plugin/kodion/utils/__init__.py
@@ -28,12 +28,13 @@
)
from .monitor import YouTubeMonitor
from .player import YouTubePlayer
-from .system_version import SystemVersion
+from .system_version import current_system_version
__all__ = (
'create_path',
'create_uri_path',
+ 'current_system_version',
'datetime_parser',
'duration_to_seconds',
'find_best_fit',
@@ -48,7 +49,6 @@
'to_str',
'to_unicode',
'to_utf8',
- 'SystemVersion',
'YouTubeMonitor',
'YouTubePlayer',
)
diff --git a/resources/lib/youtube_plugin/kodion/utils/system_version.py b/resources/lib/youtube_plugin/kodion/utils/system_version.py
index de01118ce..d51db45fa 100644
--- a/resources/lib/youtube_plugin/kodion/utils/system_version.py
+++ b/resources/lib/youtube_plugin/kodion/utils/system_version.py
@@ -14,7 +14,7 @@
class SystemVersion(object):
- def __init__(self, version, releasename, appname):
+ def __init__(self, version=None, releasename=None, appname=None):
self._version = (
version if version and isinstance(version, tuple)
else (0, 0, 0, 0)
@@ -31,19 +31,25 @@ def __init__(self, version, releasename, appname):
)
try:
- json_query = xbmc.executeJSONRPC('{ "jsonrpc": "2.0", "method": "Application.GetProperties", '
- '"params": {"properties": ["version", "name"]}, "id": 1 }')
+ json_query = xbmc.executeJSONRPC(json.dumps({
+ 'jsonrpc': '2.0',
+ 'method': 'Application.GetProperties',
+ 'params': {
+ 'properties': ['version', 'name']
+ },
+ 'id': 1,
+ }))
json_query = str(json_query)
json_query = json.loads(json_query)
version_installed = json_query['result']['version']
- self._version = (version_installed.get('major', 1), version_installed.get('minor', 0))
+ self._version = (version_installed.get('major', 1),
+ version_installed.get('minor', 0))
self._appname = json_query['result']['name']
except:
self._version = (1, 0) # Frodo
self._appname = 'Unknown Application'
- self._releasename = 'Unknown Release'
if self._version >= (21, 0):
self._releasename = 'Omega'
elif self._version >= (20, 0):
@@ -64,9 +70,15 @@ def __init__(self, version, releasename, appname):
self._releasename = 'Gotham'
elif self._version >= (12, 0):
self._releasename = 'Frodo'
+ else:
+ self._releasename = 'Unknown Release'
def __str__(self):
- obj_str = "%s (%s-%s)" % (self._releasename, self._appname, '.'.join(map(str, self._version)))
+ obj_str = '{releasename} ({appname}-{version[0]}.{version[1]})'.format(
+ releasename=self._releasename,
+ appname=self._appname,
+ version=self._version
+ )
return obj_str
def get_release_name(self):
@@ -77,3 +89,6 @@ def get_version(self):
def get_app_name(self):
return self._appname
+
+
+current_system_version = SystemVersion()
From 7a0ce7b7a4fd7cef41b0ba6893826d54806bbfaf Mon Sep 17 00:00:00 2001
From: MoojMidge <56883549+MoojMidge@users.noreply.github.com>
Date: Sun, 10 Dec 2023 20:25:41 +1100
Subject: [PATCH 077/141] Add get_kodi_setting method
---
.../lib/youtube_plugin/kodion/utils/__init__.py | 2 ++
.../lib/youtube_plugin/kodion/utils/methods.py | 15 ++++++++++++++-
2 files changed, 16 insertions(+), 1 deletion(-)
diff --git a/resources/lib/youtube_plugin/kodion/utils/__init__.py b/resources/lib/youtube_plugin/kodion/utils/__init__.py
index 9130dae36..a74ac558a 100644
--- a/resources/lib/youtube_plugin/kodion/utils/__init__.py
+++ b/resources/lib/youtube_plugin/kodion/utils/__init__.py
@@ -16,6 +16,7 @@
find_best_fit,
find_video_id,
friendly_number,
+ get_kodi_setting,
loose_version,
make_dirs,
merge_dicts,
@@ -40,6 +41,7 @@
'find_best_fit',
'find_video_id',
'friendly_number',
+ 'get_kodi_setting',
'loose_version',
'make_dirs',
'merge_dicts',
diff --git a/resources/lib/youtube_plugin/kodion/utils/methods.py b/resources/lib/youtube_plugin/kodion/utils/methods.py
index a648a81eb..745603376 100644
--- a/resources/lib/youtube_plugin/kodion/utils/methods.py
+++ b/resources/lib/youtube_plugin/kodion/utils/methods.py
@@ -8,14 +8,16 @@
See LICENSES/GPL-2.0-only for more information.
"""
-import os
import copy
+import json
+import os
import re
from datetime import timedelta
from math import floor, log
from urllib.parse import quote
import xbmcvfs
+from xbmc import executeJSONRPC
__all__ = (
@@ -25,6 +27,7 @@
'find_best_fit',
'find_video_id',
'friendly_number',
+ 'get_kodi_setting',
'loose_version',
'make_dirs',
'merge_dicts',
@@ -315,3 +318,13 @@ def merge_dicts(item1, item2, templates=None, _=Ellipsis):
templates['{0}.{1}'.format(id(new), key)] = (new, key, value)
new[key] = value
return new or _
+
+def get_kodi_setting(setting):
+ json_query = executeJSONRPC(json.dumps({
+ 'jsonrpc': '2.0',
+ 'method': 'Settings.GetSettingValue',
+ 'params': {'setting': setting},
+ 'id': 1,
+ }))
+ json_query = json.loads(json_query)
+ return json_query.get('result', {}).get('value')
From bbc8995d50cf313d50ad8fef7abb0dc4fb2eeba5 Mon Sep 17 00:00:00 2001
From: MoojMidge <56883549+MoojMidge@users.noreply.github.com>
Date: Sun, 10 Dec 2023 21:29:53 +1100
Subject: [PATCH 078/141] Update settings to use xbmcaddon.Settings class
- Used in Kodi v20+
- Backwards compatibility also maintained
- Values are cached, flushed when onSettingsChanged callback runs
- Values are logged when debug logging is enabled
- TODO: Use single instance of XbmcPluginSettings
---
.../kodion/settings/abstract_settings.py | 63 +++--
.../settings/xbmc/xbmc_plugin_settings.py | 215 +++++++++++++++++-
.../youtube_plugin/kodion/utils/monitor.py | 7 +-
3 files changed, 239 insertions(+), 46 deletions(-)
diff --git a/resources/lib/youtube_plugin/kodion/settings/abstract_settings.py b/resources/lib/youtube_plugin/kodion/settings/abstract_settings.py
index 6e1c65e97..e3bb7fd23 100644
--- a/resources/lib/youtube_plugin/kodion/settings/abstract_settings.py
+++ b/resources/lib/youtube_plugin/kodion/settings/abstract_settings.py
@@ -11,56 +11,49 @@
import sys
from ..constants import setting as SETTINGS
-from ..logger import log_debug
class AbstractSettings(object):
- def __init__(self):
- super(AbstractSettings, self).__init__()
+ VALUE_FROM_STR = {
+ 'false': False,
+ 'true': True,
+ }
- def get_string(self, setting_id, default_value=None):
- raise NotImplementedError()
+ _echo = False
+ _cache = {}
+ _funcs = {}
+ _store = None
- def set_string(self, setting_id, value):
+ @classmethod
+ def flush(cls, xbmc_addon):
raise NotImplementedError()
- def open_settings(self):
+ def get_bool(self, setting, default=None, echo=None):
raise NotImplementedError()
- def get_int(self, setting_id, default_value, converter=None):
- if not converter:
- def converter(x):
- return x
-
- value = self.get_string(setting_id)
- if value is None or value == '':
- return default_value
+ def set_bool(self, setting, value, echo=None):
+ raise NotImplementedError()
- try:
- return converter(int(value))
- except Exception as ex:
- log_debug("Failed to get setting '%s' as 'int' (%s)" % setting_id, ex.__str__())
+ def get_int(self, setting, default=-1, converter=None, echo=None):
+ raise NotImplementedError()
- return default_value
+ def set_int(self, setting, value, echo=None):
+ raise NotImplementedError()
- def set_int(self, setting_id, value):
- self.set_string(setting_id, str(value))
+ def get_string(self, setting, default='', echo=None):
+ raise NotImplementedError()
- def set_bool(self, setting_id, value):
- if value:
- self.set_string(setting_id, 'true')
- else:
- self.set_string(setting_id, 'false')
+ def set_string(self, setting, value, echo=None):
+ raise NotImplementedError()
- def get_bool(self, setting_id, default_value):
- value = self.get_string(setting_id)
- if value is None or value == '':
- return default_value
+ def get_string_list(self, setting, default=None, echo=None):
+ raise NotImplementedError()
- if value not in {'false', 'true'}:
- return default_value
+ def set_string_list(self, setting, value, echo=None):
+ raise NotImplementedError()
- return value == 'true'
+ def open_settings(self):
+ raise NotImplementedError()
def get_items_per_page(self):
return self.get_int(SETTINGS.ITEMS_PER_PAGE, 50)
@@ -293,7 +286,7 @@ def get_mpd_video_qualities(self):
if selected >= key]
def stream_features(self):
- return self.get_string(SETTINGS.MPD_STREAM_FEATURES, '').split(',')
+ return self.get_string_list(SETTINGS.MPD_STREAM_FEATURES)
_STREAM_SELECT = {
1: 'auto',
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 6e5632690..be5b888e7 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
@@ -8,20 +8,221 @@
See LICENSES/GPL-2.0-only for more information.
"""
+import xbmcaddon
+
from ..abstract_settings import AbstractSettings
+from ...logger import log_debug
+from ...utils.methods import get_kodi_setting
+from ...utils.system_version import current_system_version
class XbmcPluginSettings(AbstractSettings):
def __init__(self, xbmc_addon):
super(XbmcPluginSettings, self).__init__()
- self._xbmc_addon = xbmc_addon
+ self.flush(xbmc_addon)
+
+ if self._funcs:
+ return
+ if current_system_version.get_version() >= (20, 0):
+ _class = xbmcaddon.Settings
+
+ self._funcs.update({
+ 'get_bool': _class.getBool,
+ 'set_bool': _class.setBool,
+ 'get_int': _class.getInt,
+ 'set_int': _class.setInt,
+ 'get_str': _class.getString,
+ 'set_str': _class.setString,
+ 'get_str_list': _class.getStringList,
+ 'set_str_list': _class.setStringList,
+ })
+ else:
+ _class = xbmcaddon.Addon
+
+ def _get_string_list(store, setting):
+ return _class.getSettingString(store, setting).split(',')
+
+ def _set_string_list(store, setting, value):
+ value = ','.join(value)
+ return _class.setSettingString(store, setting, value)
+
+ self._funcs.update({
+ 'get_bool': _class.getSettingBool,
+ 'set_bool': _class.setSettingBool,
+ 'get_int': _class.getSettingInt,
+ 'set_int': _class.setSettingInt,
+ 'get_str': _class.getSettingString,
+ 'set_str': _class.setSettingString,
+ 'get_str_list': _get_string_list,
+ 'set_str_list': _set_string_list,
+ })
+
+ @classmethod
+ def flush(cls, xbmc_addon):
+ cls._echo = get_kodi_setting('debug.showloginfo')
+ cls._cache = {}
+ if current_system_version.get_version() >= (20, 0):
+ cls._store = xbmc_addon.getSettings()
+ else:
+ cls._store = xbmc_addon
+
+ def get_bool(self, setting, default=None, echo=None):
+ if setting in self._cache:
+ return self._cache[setting]
+
+ error = False
+ try:
+ value = bool(self._funcs['get_bool'](self._store, setting))
+ except (AttributeError, TypeError) as ex:
+ error = ex
+ value = self.get_string(setting, echo=False)
+ value = AbstractSettings.VALUE_FROM_STR.get(value.lower(), default)
+ except RuntimeError as ex:
+ error = ex
+ value = default
+
+ if self._echo and echo is not False:
+ log_debug('Get |{setting}|: {value} (bool, {status})'.format(
+ setting=setting,
+ value=value,
+ status=error if error else 'success'
+ ))
+ self._cache[setting] = value
+ return value
+
+ def set_bool(self, setting, value, echo=None):
+ try:
+ error = not self._funcs['set_bool'](self._store, setting, value)
+ if not error:
+ self._cache[setting] = value
+ except RuntimeError as ex:
+ error = ex
+
+ if self._echo and echo is not False:
+ log_debug('Set |{setting}|: {value} (bool, {status})'.format(
+ setting=setting,
+ value=value,
+ status=error if error else 'success'
+ ))
+ return not error
+
+ def get_int(self, setting, default=-1, process=None, echo=None):
+ if setting in self._cache:
+ return self._cache[setting]
+
+ error = False
+ try:
+ value = int(self._funcs['get_int'](self._store, setting))
+ if process:
+ value = process(value)
+ except (AttributeError, TypeError, ValueError) as ex:
+ error = ex
+ value = self.get_string(setting, echo=False)
+ try:
+ value = int(value)
+ except (TypeError, ValueError) as ex:
+ error = ex
+ value = default
+ except RuntimeError as ex:
+ error = ex
+ value = default
+
+ if self._echo and echo is not False:
+ log_debug('Get |{setting}|: {value} (int, {status})'.format(
+ setting=setting,
+ value=value,
+ status=error if error else 'success'
+ ))
+ self._cache[setting] = value
+ return value
+
+ def set_int(self, setting, value, echo=None):
+ try:
+ error = not self._funcs['set_int'](self._store, setting, value)
+ if not error:
+ self._cache[setting] = value
+ except RuntimeError as ex:
+ error = ex
+
+ if self._echo and echo is not False:
+ log_debug('Set |{setting}|: {value} (int, {status})'.format(
+ setting=setting,
+ value=value,
+ status=error if error else 'success'
+ ))
+ return not error
+
+ def get_string(self, setting, default='', echo=None):
+ if setting in self._cache:
+ return self._cache[setting]
+
+ error = False
+ try:
+ value = self._funcs['get_str'](self._store, setting) or default
+ except RuntimeError as ex:
+ error = ex
+ value = default
+
+ if self._echo and echo is not False:
+ log_debug('Get |{setting}|: "{value}" (str, {status})'.format(
+ setting=setting,
+ value=value,
+ status=error if error else 'success'
+ ))
+ self._cache[setting] = value
+ return value
+
+ def set_string(self, setting, value, echo=None):
+ try:
+ error = not self._funcs['set_str'](self._store, setting, value)
+ if not error:
+ self._cache[setting] = value
+ except RuntimeError as ex:
+ error = ex
+
+ if self._echo and echo is not False:
+ log_debug('Set |{setting}|: "{value}" (str, {status})'.format(
+ setting=setting,
+ value=value,
+ status=error if error else 'success'
+ ))
+ return not error
+
+ def get_string_list(self, setting, default=None, echo=None):
+ if setting in self._cache:
+ return self._cache[setting]
+
+ error = False
+ try:
+ value = self._funcs['get_str_list'](self._store, setting)
+ if not value:
+ value = [] if default is None else default
+ except RuntimeError as ex:
+ error = ex
+ value = default
- def get_string(self, setting_id, default_value=None):
- return self._xbmc_addon.getSetting(setting_id)
+ if self._echo and echo is not False:
+ log_debug('Get |{setting}|: "{value}" (str list, {status})'.format(
+ setting=setting,
+ value=value,
+ status=error if error else 'success'
+ ))
+ self._cache[setting] = value
+ return value
- def set_string(self, setting_id, value):
- self._xbmc_addon.setSetting(setting_id, value)
+ def set_string_list(self, setting, value, echo=None):
+ try:
+ error = not self._funcs['set_str_list'](self._store, setting, value)
+ if not error:
+ self._cache[setting] = value
+ except RuntimeError as ex:
+ error = ex
- def open_settings(self):
- self._xbmc_addon.openSetting()
+ if self._echo and echo is not False:
+ log_debug('Set |{setting}|: "{value}" (str list, {status})'.format(
+ setting=setting,
+ value=value,
+ status=error if error else 'success'
+ ))
+ return not error
diff --git a/resources/lib/youtube_plugin/kodion/utils/monitor.py b/resources/lib/youtube_plugin/kodion/utils/monitor.py
index 1cd981580..e25e22bdc 100644
--- a/resources/lib/youtube_plugin/kodion/utils/monitor.py
+++ b/resources/lib/youtube_plugin/kodion/utils/monitor.py
@@ -24,8 +24,7 @@
class YouTubeMonitor(xbmc.Monitor):
_addon_id = 'plugin.video.youtube'
- _addon = Addon(_addon_id)
- _settings = Settings(_addon)
+ _settings = Settings(Addon(_addon_id))
# noinspection PyUnusedLocal,PyMissingConstructor
def __init__(self, *args, **kwargs):
@@ -89,8 +88,8 @@ def onNotification(self, sender, method, data):
.format(method=method))
def onSettingsChanged(self):
- YouTubeMonitor._addon = Addon(self._addon_id)
- YouTubeMonitor._settings = Settings(self._addon)
+ self._settings.flush(Addon(self._addon_id))
+
data = {
'use_httpd': (self._settings.use_mpd_videos()
or self._settings.api_config_page()),
From b3b08d2d3148992e926ca9e13384930b20d8f281 Mon Sep 17 00:00:00 2001
From: MoojMidge <56883549+MoojMidge@users.noreply.github.com>
Date: Sun, 10 Dec 2023 23:18:25 +1100
Subject: [PATCH 079/141] Make better use of new AccessManager
- Update youtube.client.__config__
- Follow up to f5742c8
---
.../kodion/constants/const_settings.py | 5 +
.../kodion/json_store/access_manager.py | 7 +-
.../kodion/settings/abstract_settings.py | 24 +++
.../youtube/client/__config__.py | 182 ++++++++++--------
4 files changed, 134 insertions(+), 84 deletions(-)
diff --git a/resources/lib/youtube_plugin/kodion/constants/const_settings.py b/resources/lib/youtube_plugin/kodion/constants/const_settings.py
index 0b299f170..70501103b 100644
--- a/resources/lib/youtube_plugin/kodion/constants/const_settings.py
+++ b/resources/lib/youtube_plugin/kodion/constants/const_settings.py
@@ -55,6 +55,11 @@
API_KEY = 'youtube.api.key' # (string)
API_ID = 'youtube.api.id' # (string)
API_SECRET = 'youtube.api.secret' # (string)
+API_LAST_HASH = 'youtube.api.last.hash' # (string)
+
+USER_ACCESS_TOKEN = 'kodion.access_token' # (string)
+USER_REFRESH_TOKEN = 'kodion.refresh_token' # (string)
+USER_TOKEN_EXPIRATION = 'kodion.access_token.expires' # (int)
CLIENT_SELECTION = 'youtube.client.selection' # (int)
diff --git a/resources/lib/youtube_plugin/kodion/json_store/access_manager.py b/resources/lib/youtube_plugin/kodion/json_store/access_manager.py
index c5264ca67..e54b0dc62 100644
--- a/resources/lib/youtube_plugin/kodion/json_store/access_manager.py
+++ b/resources/lib/youtube_plugin/kodion/json_store/access_manager.py
@@ -425,12 +425,14 @@ def is_access_token_expired(self):
def update_access_token(self,
access_token,
unix_timestamp=None,
- refresh_token=None):
+ refresh_token=None,
+ last_key_hash=None):
"""
Updates the old access token with the new one.
:param access_token:
:param unix_timestamp:
:param refresh_token:
+ :param last_key_hash:
:return:
"""
current_user = self.get_current_user_details()
@@ -442,6 +444,9 @@ def update_access_token(self,
if refresh_token is not None:
current_user['refresh_token'] = refresh_token
+ if last_key_hash is not None:
+ current_user['last_key_hash'] = last_key_hash
+
data = {
'access_manager': {
'users': {
diff --git a/resources/lib/youtube_plugin/kodion/settings/abstract_settings.py b/resources/lib/youtube_plugin/kodion/settings/abstract_settings.py
index e3bb7fd23..15a7d9b28 100644
--- a/resources/lib/youtube_plugin/kodion/settings/abstract_settings.py
+++ b/resources/lib/youtube_plugin/kodion/settings/abstract_settings.py
@@ -228,6 +228,30 @@ def api_secret(self, new_secret=None):
return new_secret
return self.get_string(SETTINGS.API_SECRET)
+ def api_last_hash(self, new_hash=None):
+ if new_hash is not None:
+ self.set_string(SETTINGS.API_LAST_HASH, new_hash)
+ return new_hash
+ return self.get_string(SETTINGS.API_LAST_HASH, '')
+
+ def user_access_token(self, new_access_token=None):
+ if new_access_token is not None:
+ self.set_string(SETTINGS.USER_ACCESS_TOKEN, new_access_token)
+ return new_access_token
+ return self.get_string(SETTINGS.USER_ACCESS_TOKEN, '')
+
+ def user_refresh_token(self, new_refresh_token=None):
+ if new_refresh_token is not None:
+ self.set_string(SETTINGS.USER_REFRESH_TOKEN, new_refresh_token)
+ return new_refresh_token
+ return self.get_string(SETTINGS.USER_REFRESH_TOKEN, '')
+
+ def user_token_expiration(self, new_token_expiration=None):
+ if new_token_expiration is not None:
+ self.set_int(SETTINGS.USER_TOKEN_EXPIRATION, new_token_expiration)
+ return new_token_expiration
+ return self.get_int(SETTINGS.USER_TOKEN_EXPIRATION, -1)
+
def get_location(self):
location = self.get_string(SETTINGS.LOCATION, '').replace(' ', '').strip()
coords = location.split(',')
diff --git a/resources/lib/youtube_plugin/youtube/client/__config__.py b/resources/lib/youtube_plugin/youtube/client/__config__.py
index 656ea8f5b..239475da3 100644
--- a/resources/lib/youtube_plugin/youtube/client/__config__.py
+++ b/resources/lib/youtube_plugin/youtube/client/__config__.py
@@ -9,26 +9,30 @@
from base64 import b64decode
from hashlib import md5
-from ...kodion.json_store import AccessManager, APIKeyStore
-from ...kodion import Context as __Context
+
from ... import key_sets
+from ...kodion import Context
+from ...kodion.json_store import APIKeyStore, AccessManager
-DEFAULT_SWITCH = 1
-__context = __Context(plugin_id='plugin.video.youtube')
-__settings = __context.get_settings()
+DEFAULT_SWITCH = 1
class APICheck(object):
-
- def __init__(self, context, settings):
+ def __init__(self, context):
+ settings = context.get_settings()
+ context.send_notification('check_settings', {
+ 'use_httpd': settings.use_mpd_videos() or settings.api_config_page(),
+ 'httpd_port': settings.httpd_port(),
+ 'whitelist': settings.httpd_whitelist(),
+ 'httpd_address': settings.httpd_listen()
+ })
self._context = context
self._settings = settings
self._ui = context.get_ui()
self._api_jstore = APIKeyStore()
self._json_api = self._api_jstore.get_data()
self._access_manager = AccessManager(context)
- self._json_am = self._access_manager.get_data()
self.changed = False
self._on_init()
@@ -49,16 +53,16 @@ def _on_init(self):
self._json_api['keys']['personal'] = {'api_key': stripped_key, 'client_id': stripped_id, 'client_secret': stripped_secret}
self._api_jstore.save(self._json_api)
- original_key = self._settings.get_string('youtube.api.key')
- original_id = self._settings.get_string('youtube.api.id')
- original_secret = self._settings.get_string('youtube.api.secret')
+ original_key = self._settings.api_key()
+ original_id = self._settings.api_id()
+ original_secret = self._settings.api_secret()
if original_key and original_id and original_secret:
own_key, own_id, own_secret = self._strip_api_keys(original_key, original_id, original_secret)
if own_key and own_id and own_secret:
if (original_key != own_key) or (original_id != own_id) or (original_secret != own_secret):
- self._settings.set_string('youtube.api.key', own_key)
- self._settings.set_string('youtube.api.id', own_id)
- self._settings.set_string('youtube.api.secret', own_secret)
+ self._settings.api_key(own_key)
+ self._settings.api_id(own_id)
+ self._settings.api_secret(own_secret)
if (j_key != own_key) or (j_id != own_id) or (j_secret != own_secret):
self._json_api['keys']['personal'] = {'api_key': own_key, 'client_id': own_id, 'client_secret': own_secret}
@@ -69,52 +73,65 @@ def _on_init(self):
j_id = self._json_api['keys']['personal'].get('client_id', '')
j_secret = self._json_api['keys']['personal'].get('client_secret', '')
- if not original_key or not original_id or not original_secret and (j_key and j_secret and j_id):
- self._settings.set_string('youtube.api.key', j_key)
- self._settings.set_string('youtube.api.id', j_id)
- self._settings.set_string('youtube.api.secret', j_secret)
+ if (not original_key or not original_id or not original_secret
+ and j_key and j_secret and j_id):
+ self._settings.api_key(j_key)
+ self._settings.api_id(j_id)
+ self._settings.api_secret(j_secret)
switch = self.get_current_switch()
- user = self.get_current_user()
-
- access_token = self._settings.get_string('kodion.access_token', '')
- refresh_token = self._settings.get_string('kodion.refresh_token', '')
- token_expires = self._settings.get_int('kodion.access_token.expires', -1)
- last_hash = self._settings.get_string('youtube.api.last.hash', '')
- if ((not self._json_am['access_manager']['users'].get(user, {}).get('access_token')
- or not self._json_am['access_manager']['users'].get(user, {}).get('refresh_token'))
- and access_token and refresh_token):
- self._json_am['access_manager']['users'][user]['access_token'] = access_token
- self._json_am['access_manager']['users'][user]['refresh_token'] = refresh_token
- self._json_am['access_manager']['users'][user]['token_expires'] = token_expires
+ user_details = self._access_manager.get_current_user_details()
+
+ access_token = self._settings.user_access_token()
+ refresh_token = self._settings.user_refresh_token()
+ token_expires = self._settings.user_token_expiration()
+ last_hash = self._settings.api_last_hash()
+ updated_hash = self._api_keys_changed(switch)
+
+ if access_token or refresh_token or last_hash:
+ self._settings.user_access_token('')
+ self._settings.user_refresh_token('')
+ self._settings.user_token_expiration(-1)
+ self._settings.api_last_hash('')
+
+ if updated_hash or (access_token and refresh_token
+ and not (user_details.get('access_token')
+ and user_details.get('refresh_token'))):
if switch == 'own':
own_key_hash = self._get_key_set_hash('own')
if (last_hash == self._get_key_set_hash('own', True)
or last_hash == own_key_hash):
- self._json_am['access_manager']['users'][user]['last_key_hash'] = own_key_hash
- self._access_manager.save(self._json_am)
- if access_token or refresh_token or last_hash:
- self._settings.set_string('kodion.access_token', '')
- self._settings.set_string('kodion.refresh_token', '')
- self._settings.set_int('kodion.access_token.expires', -1)
- self._settings.set_string('youtube.api.last.hash', '')
-
- updated_hash = self._api_keys_changed(switch)
- if updated_hash:
- self._context.log_warning('User: |%s| Switching API key set to |%s|' % (user, switch))
- self._json_am['access_manager']['users'][user]['last_key_hash'] = updated_hash
- self._access_manager.save(self._json_am)
- self._context.log_debug('API key set changed: Signing out')
- self._context.execute('RunPlugin(plugin://plugin.video.youtube/sign/out/?confirmed=true)')
- else:
- self._context.log_debug('User: |%s| Using API key set: |%s|' % (user, switch))
-
- def get_current_switch(self):
+ last_hash = own_key_hash
+ else:
+ last_hash = None
+ else:
+ last_hash = None
+
+ if updated_hash:
+ last_hash = updated_hash
+ self._context.log_warning('User: |{user}|, '
+ 'Switching API key set to: |{switch}|'
+ .format(user=self.get_current_user(),
+ switch=switch))
+ self._context.log_debug('API key set changed: Signing out')
+ self._context.execute('RunPlugin(plugin://plugin.video.youtube/'
+ 'sign/out/?confirmed=true)')
+
+ self._access_manager.update_access_token(
+ access_token, token_expires, refresh_token, last_hash
+ )
+ elif not updated_hash:
+ self._context.log_debug('User: |{user}|, '
+ 'Using API key set: |{switch}|'
+ .format(user=self.get_current_user(),
+ switch=switch))
+
+ @staticmethod
+ def get_current_switch():
return 'own'
def get_current_user(self):
- self._json_am = self._access_manager.get_data()
- return self._json_am['access_manager'].get('current_user', 0)
+ return self._access_manager.get_current_user()
def has_own_api_keys(self):
self._json_api = self._api_jstore.get_data()
@@ -127,24 +144,36 @@ def get_api_keys(self, switch):
self._json_api = self._api_jstore.get_data()
if switch == 'developer':
return self._json_api['keys'][switch]
+
+ decode = True
if switch == 'youtube-tv':
- api_key = b64decode(key_sets[switch]['key']).decode('utf-8'),
- client_id = ''.join([b64decode(key_sets[switch]['id']).decode('utf-8'), '.apps.googleusercontent.com'])
- client_secret = b64decode(key_sets[switch]['secret']).decode('utf-8')
+ api_key = key_sets[switch]['key']
+ client_id = key_sets[switch]['id']
+ client_secret = key_sets[switch]['secret']
+
elif switch == 'own':
+ decode = False
api_key = self._json_api['keys']['personal']['api_key']
- client_id = ''.join([self._json_api['keys']['personal']['client_id'], '.apps.googleusercontent.com'])
+ client_id = self._json_api['keys']['personal']['client_id']
client_secret = self._json_api['keys']['personal']['client_secret']
+
else:
- api_key = b64decode(key_sets['provided'][switch]['key']).decode('utf-8')
- client_id = ''.join([b64decode(key_sets['provided'][switch]['id']).decode('utf-8'), '.apps.googleusercontent.com'])
- client_secret = b64decode(key_sets['provided'][switch]['secret']).decode('utf-8')
- return api_key, client_id, client_secret
+ api_key = key_sets['provided'][switch]['key']
+ client_id = key_sets['provided'][switch]['id']
+ client_secret = key_sets['provided'][switch]['secret']
+
+ if decode:
+ api_key = b64decode(api_key).decode('utf-8')
+ client_id = b64decode(client_id).decode('utf-8')
+ client_secret = b64decode(client_secret).decode('utf-8')
+
+ return {'key': api_key,
+ 'id': ''.join((client_id, '.apps.googleusercontent.com')),
+ 'secret': client_secret}
def _api_keys_changed(self, switch):
- self._json_am = self._access_manager.get_data()
- user = self.get_current_user()
- last_set_hash = self._json_am['access_manager']['users'].get(user, {}).get('last_key_hash', '')
+ user_details = self._access_manager.get_current_user_details()
+ last_set_hash = user_details.get('last_key_hash', '')
current_set_hash = self._get_key_set_hash(switch)
if last_set_hash != current_set_hash:
self.changed = True
@@ -156,12 +185,12 @@ def _get_key_set_hash(self, switch, old=False):
api_key, client_id, client_secret = self.get_api_keys(switch)
if old and switch == 'own':
client_id = client_id.replace('.apps.googleusercontent.com', '')
- m = md5()
- m.update(api_key.encode('utf-8'))
- m.update(client_id.encode('utf-8'))
- m.update(client_secret.encode('utf-8'))
+ md5_hash = md5()
+ md5_hash.update(api_key.encode('utf-8'))
+ md5_hash.update(client_id.encode('utf-8'))
+ md5_hash.update(client_secret.encode('utf-8'))
- return m.hexdigest()
+ return md5_hash.hexdigest()
def _strip_api_keys(self, api_key, client_id, client_secret):
@@ -205,24 +234,11 @@ def _strip_api_keys(self, api_key, client_id, client_secret):
return return_key, return_id, return_secret
-notification_data = {'use_httpd': (__settings.use_mpd_videos()
- or __settings.api_config_page()),
- 'httpd_port': __settings.httpd_port(),
- 'whitelist': __settings.httpd_whitelist(),
- 'httpd_address': __settings.httpd_listen()
- }
-
-__context.send_notification('check_settings', notification_data)
-
-_api_check = APICheck(__context, __settings)
+_api_check = APICheck(Context(plugin_id='plugin.video.youtube'))
keys_changed = _api_check.changed
current_user = _api_check.get_current_user()
-api = {}
-api['key'], api['id'], api['secret'] = _api_check.get_api_keys(_api_check.get_current_switch())
-
-youtube_tv = {}
-youtube_tv['key'], youtube_tv['id'], youtube_tv['secret'] = _api_check.get_api_keys('youtube-tv')
-
+api = _api_check.get_api_keys(_api_check.get_current_switch())
+youtube_tv = _api_check.get_api_keys('youtube-tv')
developer_keys = _api_check.get_api_keys('developer')
From 34851601fee177bc33cc35c4fdf8200e2b0f3f16 Mon Sep 17 00:00:00 2001
From: MoojMidge <56883549+MoojMidge@users.noreply.github.com>
Date: Mon, 11 Dec 2023 16:01:30 +1100
Subject: [PATCH 080/141] Remove/add Unicode encoding/decoding
- Additions required for Python 2/3 compatibility
- Removals not required for Python 3
- Will require use of kodi-six in Kodi Leia
---
.../kodion/abstract_provider.py | 22 ++++++-------
.../kodion/context/abstract_context.py | 2 +-
resources/lib/youtube_plugin/kodion/debug.py | 7 ++--
.../kodion/json_store/json_store.py | 12 ++++---
.../kodion/sql_store/search_history.py | 3 +-
.../kodion/sql_store/storage.py | 19 ++++++++---
.../kodion/ui/xbmc/xbmc_context_ui.py | 32 +++++++------------
.../youtube_plugin/kodion/utils/__init__.py | 2 --
.../youtube_plugin/kodion/utils/methods.py | 18 ++---------
9 files changed, 52 insertions(+), 65 deletions(-)
diff --git a/resources/lib/youtube_plugin/kodion/abstract_provider.py b/resources/lib/youtube_plugin/kodion/abstract_provider.py
index 61fec5695..cf49f8732 100644
--- a/resources/lib/youtube_plugin/kodion/abstract_provider.py
+++ b/resources/lib/youtube_plugin/kodion/abstract_provider.py
@@ -10,19 +10,18 @@
import json
import re
-from urllib.parse import quote
-from urllib.parse import unquote
+from urllib.parse import quote, unquote
+from . import constants
from .exceptions import KodionException
from .items import (
- from_json,
- to_jsons,
DirectoryItem,
NewSearchItem,
- SearchHistoryItem
+ SearchHistoryItem,
+ from_json,
+ to_jsons,
)
-from .utils import to_unicode, to_utf8
-from . import constants
+from .utils import to_unicode
class AbstractProvider(object):
@@ -245,12 +244,9 @@ def _internal_search(self, context, re_match):
incognito = context.get_param('incognito', False)
channel_id = context.get_param('channel_id', '')
- query = to_utf8(query)
- try:
- encoded = json.dumps({'query': quote(query)})
- except KeyError:
- encoded = json.dumps({'query': quote(query.encode('utf8'))})
- self._data_cache.set_item('search_query', encoded)
+ self._data_cache.set_item('search_query',
+ json.dumps({'query': quote(query)},
+ ensure_ascii=False))
if not incognito and not channel_id:
try:
diff --git a/resources/lib/youtube_plugin/kodion/context/abstract_context.py b/resources/lib/youtube_plugin/kodion/context/abstract_context.py
index a46327767..aff45b4d1 100644
--- a/resources/lib/youtube_plugin/kodion/context/abstract_context.py
+++ b/resources/lib/youtube_plugin/kodion/context/abstract_context.py
@@ -219,7 +219,7 @@ def create_uri(self, path='/', params=None):
uri = "%s://%s/" % ('plugin', str(self._plugin_id))
if params:
- uri = '?'.join([uri, urlencode(params, encoding='utf-8')])
+ uri = '?'.join([uri, urlencode(params)])
return uri
diff --git a/resources/lib/youtube_plugin/kodion/debug.py b/resources/lib/youtube_plugin/kodion/debug.py
index cf12d73b4..b1f22a02c 100644
--- a/resources/lib/youtube_plugin/kodion/debug.py
+++ b/resources/lib/youtube_plugin/kodion/debug.py
@@ -8,8 +8,9 @@
See LICENSES/GPL-2.0-only for more information.
"""
-import os
import json
+import os
+from io import open
from .logger import log_debug
@@ -41,10 +42,10 @@ def runtime(context, addon_version, elapsed, single_file=True):
with open(debug_file, 'a') as _:
pass # touch
- with open(debug_file, 'r') as f:
+ with open(debug_file, 'r', encoding='utf-8') as f:
contents = f.read()
- with open(debug_file, 'w') as f:
+ with open(debug_file, 'w', encoding='utf-8') as f:
contents = json.loads(contents) if contents else default_contents
if not single_file:
items = contents.get('runtimes', [])
diff --git a/resources/lib/youtube_plugin/kodion/json_store/json_store.py b/resources/lib/youtube_plugin/kodion/json_store/json_store.py
index 09d5e5f9b..87e1d6180 100644
--- a/resources/lib/youtube_plugin/kodion/json_store/json_store.py
+++ b/resources/lib/youtube_plugin/kodion/json_store/json_store.py
@@ -9,16 +9,17 @@
import json
import os
+from io import open
-from xbmcaddon import Addon
+import xbmcaddon
import xbmcvfs
from ..logger import log_debug, log_error
-from ..utils import make_dirs, merge_dicts
+from ..utils import make_dirs, merge_dicts, to_unicode
_addon_id = 'plugin.video.youtube'
-_addon = Addon(_addon_id)
+_addon = xbmcaddon.Addon(_addon_id)
class JSONStore(object):
@@ -55,7 +56,10 @@ def save(self, data, update=False, process=None):
raise ValueError
_data = json.loads(json.dumps(data))
with open(self.filename, mode='w', encoding='utf-8') as jsonfile:
- json.dump(_data, jsonfile, indent=4, sort_keys=True)
+ jsonfile.write(to_unicode(json.dumps(_data,
+ ensure_ascii=False,
+ indent=4,
+ sort_keys=True)))
self._data = process(_data) if process is not None else _data
except (IOError, OSError):
log_error('JSONStore.save |{filename}| no access to file'.format(
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 a06d8c427..90c8d1cb9 100644
--- a/resources/lib/youtube_plugin/kodion/sql_store/search_history.py
+++ b/resources/lib/youtube_plugin/kodion/sql_store/search_history.py
@@ -11,7 +11,6 @@
from hashlib import md5
from .storage import Storage
-from ..utils import to_utf8
class SearchHistory(Storage):
@@ -33,7 +32,7 @@ def clear(self):
@staticmethod
def _make_id(search_text):
md5_hash = md5()
- md5_hash.update(to_utf8(search_text))
+ md5_hash.update(search_text.encode('utf-8'))
return md5_hash.hexdigest()
def rename(self, old_search_text, new_search_text):
diff --git a/resources/lib/youtube_plugin/kodion/sql_store/storage.py b/resources/lib/youtube_plugin/kodion/sql_store/storage.py
index 28b7bc705..8983a00b7 100644
--- a/resources/lib/youtube_plugin/kodion/sql_store/storage.py
+++ b/resources/lib/youtube_plugin/kodion/sql_store/storage.py
@@ -26,6 +26,11 @@ class Storage(object):
ONE_WEEK = 7 * ONE_DAY
ONE_MONTH = 4 * ONE_WEEK
+ _key = str('key')
+ _time = str('time')
+ _value = str('value')
+ _timestamp = str('timestamp')
+
_table_name = 'storage'
_clear_query = 'DELETE FROM %s' % _table_name
_create_table_query = 'CREATE TABLE IF NOT EXISTS %s (key TEXT PRIMARY KEY, time TIMESTAMP, value BLOB)' % _table_name
@@ -51,7 +56,7 @@ def __init__(self, filename, max_item_count=-1, max_file_size_kb=-1):
self._table_created = False
self._needs_commit = False
- sqlite3.register_converter('timestamp', self._convert_timestamp)
+ sqlite3.register_converter(self._timestamp, self._convert_timestamp)
def set_max_item_count(self, max_item_count):
self._max_item_count = max_item_count
@@ -184,7 +189,8 @@ def _optimize_item_count(self):
query = self._optimize_item_query.format(self._max_item_count)
self._open()
item_ids = self._execute(False, query)
- item_ids = [item_id['key'] for item_id in item_ids]
+ key = self._key
+ item_ids = [item_id[key] for item_id in item_ids]
if item_ids:
self._remove_all(item_ids)
self._close()
@@ -210,7 +216,7 @@ def _is_empty(self):
@staticmethod
def _decode(obj, process=None):
- decoded_obj = pickle.loads(obj, encoding='utf-8')
+ decoded_obj = pickle.loads(obj)
if process:
return process(decoded_obj)
return decoded_obj
@@ -228,7 +234,7 @@ def _get(self, item_id):
result = result.fetchone()
self._close()
if result:
- return self._decode(result['value']), result['time']
+ return self._decode(result[self._value]), result[self._time]
return None
def _get_by_ids(self, item_ids=None, oldest_first=True, limit=-1,
@@ -246,8 +252,11 @@ def _get_by_ids(self, item_ids=None, oldest_first=True, limit=-1,
self._open()
result = self._execute(False, query, item_ids)
+ key = self._key
+ time = self._time
+ value = self._value
result = [
- (item['key'], item['time'], self._decode(item['value'], process))
+ (item[key], item[time], self._decode(item[value], process))
for item in result
]
self._close()
diff --git a/resources/lib/youtube_plugin/kodion/ui/xbmc/xbmc_context_ui.py b/resources/lib/youtube_plugin/kodion/ui/xbmc/xbmc_context_ui.py
index ce6eb53cc..954b3af18 100644
--- a/resources/lib/youtube_plugin/kodion/ui/xbmc/xbmc_context_ui.py
+++ b/resources/lib/youtube_plugin/kodion/ui/xbmc/xbmc_context_ui.py
@@ -110,35 +110,27 @@ def on_select(self, title, items=None):
return _dict.get(result, -1)
- def show_notification(self, message, header='', image_uri='', time_milliseconds=5000, audible=True):
+ def show_notification(self,
+ message,
+ header='',
+ image_uri='',
+ time_milliseconds=5000,
+ audible=True):
_header = header
if not _header:
_header = self._context.get_name()
- _header = utils.to_utf8(_header)
_image = image_uri
if not _image:
_image = self._context.get_icon()
- if isinstance(message, str):
- message = utils.to_unicode(message)
+ _message = message.replace(',', ' ').replace('\n', ' ')
- try:
- _message = utils.to_utf8(message.decode('unicode-escape'))
- except (AttributeError, UnicodeEncodeError):
- _message = utils.to_utf8(message)
-
- try:
- _message = _message.replace(',', ' ')
- _message = _message.replace('\n', ' ')
- except TypeError:
- _message = _message.replace(b',', b' ')
- _message = _message.replace(b'\n', b' ')
- _message = utils.to_unicode(_message)
- _header = utils.to_unicode(_header)
-
- # xbmc.executebuiltin("Notification(%s, %s, %d, %s)" % (_header, _message, time_milliseconds, _image))
- xbmcgui.Dialog().notification(_header, _message, _image, time_milliseconds, audible)
+ xbmcgui.Dialog().notification(_header,
+ _message,
+ _image,
+ time_milliseconds,
+ audible)
def open_settings(self):
self._xbmc_addon.openSettings()
diff --git a/resources/lib/youtube_plugin/kodion/utils/__init__.py b/resources/lib/youtube_plugin/kodion/utils/__init__.py
index a74ac558a..e586f8a59 100644
--- a/resources/lib/youtube_plugin/kodion/utils/__init__.py
+++ b/resources/lib/youtube_plugin/kodion/utils/__init__.py
@@ -25,7 +25,6 @@
strip_html_from_text,
to_str,
to_unicode,
- to_utf8,
)
from .monitor import YouTubeMonitor
from .player import YouTubePlayer
@@ -50,7 +49,6 @@
'strip_html_from_text',
'to_str',
'to_unicode',
- 'to_utf8',
'YouTubeMonitor',
'YouTubePlayer',
)
diff --git a/resources/lib/youtube_plugin/kodion/utils/methods.py b/resources/lib/youtube_plugin/kodion/utils/methods.py
index 745603376..adb6455e2 100644
--- a/resources/lib/youtube_plugin/kodion/utils/methods.py
+++ b/resources/lib/youtube_plugin/kodion/utils/methods.py
@@ -16,8 +16,8 @@
from math import floor, log
from urllib.parse import quote
+import xbmc
import xbmcvfs
-from xbmc import executeJSONRPC
__all__ = (
@@ -37,7 +37,6 @@
'strip_html_from_text',
'to_str',
'to_unicode',
- 'to_utf8',
)
@@ -54,23 +53,12 @@ def to_str(text):
return text
-def to_utf8(text):
- result = text
- if isinstance(text, str):
- try:
- result = text.encode('utf-8', 'ignore')
- except UnicodeDecodeError:
- pass
-
- return result
-
-
def to_unicode(text):
result = text
if isinstance(text, (bytes, str)):
try:
result = text.decode('utf-8', 'ignore')
- except (AttributeError, UnicodeEncodeError):
+ except (AttributeError, UnicodeError):
pass
return result
@@ -320,7 +308,7 @@ def merge_dicts(item1, item2, templates=None, _=Ellipsis):
return new or _
def get_kodi_setting(setting):
- json_query = executeJSONRPC(json.dumps({
+ json_query = xbmc.executeJSONRPC(json.dumps({
'jsonrpc': '2.0',
'method': 'Settings.GetSettingValue',
'params': {'setting': setting},
From ef9ca693c6f8f4b712ae1f5d60f794bbd55ad5cf Mon Sep 17 00:00:00 2001
From: MoojMidge <56883549+MoojMidge@users.noreply.github.com>
Date: Mon, 11 Dec 2023 16:05:37 +1100
Subject: [PATCH 081/141] Workaround for missing datetime.timestamp method in
Python < v3.3
- Also update default fmt parameter in strptime wrapper
---
.../youtube_plugin/kodion/sql_store/storage.py | 7 ++++---
.../kodion/utils/datetime_parser.py | 12 ++++++------
.../youtube_plugin/youtube/client/youtube.py | 17 +++++++++--------
3 files changed, 19 insertions(+), 17 deletions(-)
diff --git a/resources/lib/youtube_plugin/kodion/sql_store/storage.py b/resources/lib/youtube_plugin/kodion/sql_store/storage.py
index 8983a00b7..0487ea00e 100644
--- a/resources/lib/youtube_plugin/kodion/sql_store/storage.py
+++ b/resources/lib/youtube_plugin/kodion/sql_store/storage.py
@@ -17,6 +17,7 @@
from traceback import print_exc
from ..logger import log_error
+from ..utils.datetime_parser import since_epoch
class Storage(object):
@@ -160,7 +161,7 @@ def _sync(self):
def _set(self, item_id, item):
# add 1 microsecond, required for dbapi2
- now = datetime.now().timestamp() + 0.000001
+ now = since_epoch(datetime.now()) + 0.000001
self._open()
self._execute(True, self._set_query, values=[item_id,
now,
@@ -170,7 +171,7 @@ def _set(self, item_id, item):
def _set_all(self, items):
# add 1 microsecond, required for dbapi2
- now = datetime.now().timestamp() + 0.000001
+ now = since_epoch(datetime.now()) + 0.000001
self._open()
self._execute(True, self._set_query,
values=[(key, now, self._encode(json.dumps(item)))
@@ -322,7 +323,7 @@ def get_seconds_diff(self, current_stamp):
return time_delta.total_seconds()
if isinstance(current_stamp, (float, int)):
- return current_datetime.timestamp() - current_stamp
+ return since_epoch(current_datetime) - current_stamp
stamp_datetime = self._parse_datetime_string(current_stamp)
if not stamp_datetime:
diff --git a/resources/lib/youtube_plugin/kodion/utils/datetime_parser.py b/resources/lib/youtube_plugin/kodion/utils/datetime_parser.py
index 16d4cefbb..9890f15b9 100644
--- a/resources/lib/youtube_plugin/kodion/utils/datetime_parser.py
+++ b/resources/lib/youtube_plugin/kodion/utils/datetime_parser.py
@@ -173,14 +173,14 @@ def datetime_to_since(context, dt):
return ' '.join((context.format_date_short(dt), context.format_time(dt)))
-def strptime(s, fmt='%Y-%m-%dT%H:%M:%S.%fZ'):
+def strptime(s, fmt='%Y-%m-%dT%H:%M:%S'):
# noinspection PyUnresolvedReferences
- ms_precision = '.' in s[-5:-1]
- if fmt == '%Y-%m-%dT%H:%M:%S.%fZ' and not ms_precision:
- fmt = '%Y-%m-%dT%H:%M:%SZ'
- elif fmt == '%Y-%m-%dT%H:%M:%SZ' and ms_precision:
- fmt = '%Y-%m-%dT%H:%M:%S.%fZ'
+ ms_precision_required = '.' in s[-5:-1]
+ if ms_precision_required:
+ fmt.replace('%S', '%S.%f')
+ else:
+ fmt.replace('%S.%f', '%S')
import _strptime
diff --git a/resources/lib/youtube_plugin/youtube/client/youtube.py b/resources/lib/youtube_plugin/youtube/client/youtube.py
index cb86ad423..88294de7c 100644
--- a/resources/lib/youtube_plugin/youtube/client/youtube.py
+++ b/resources/lib/youtube_plugin/youtube/client/youtube.py
@@ -417,12 +417,13 @@ def helper(video_id, responses):
items.append(item)
# Finally sort items per page by date for a better distribution
- sorted_items.sort(
- key=lambda a: (
- a['page_number'],
- -datetime_parser.parse(a['snippet']['publishedAt']).timestamp()
- ),
- )
+ def _sort_by_date_time(item):
+ return (item['page_number'],
+ -datetime_parser.since_epoch(datetime_parser.parse(
+ item['snippet']['publishedAt']
+ )))
+
+ sorted_items.sort(key=_sort_by_date_time)
# Finalize result
payload['items'] = sorted_items
@@ -873,9 +874,9 @@ def fetch_xml(_url, _responses):
_result['items'].append(entry_data)
# sorting by publish date
- def _sort_by_date_time(e):
+ def _sort_by_date_time(item):
return datetime_parser.since_epoch(
- datetime_parser.strptime(e["published"][0:19], "%Y-%m-%dT%H:%M:%S")
+ datetime_parser.strptime(item['published'][0:19])
)
_result['items'].sort(reverse=True, key=_sort_by_date_time)
From f4d41dba2a082f11347f3705d88c84bbfb21a99a Mon Sep 17 00:00:00 2001
From: MoojMidge <56883549+MoojMidge@users.noreply.github.com>
Date: Mon, 11 Dec 2023 16:12:46 +1100
Subject: [PATCH 082/141] Add Python2 and Kodi 18 compatibility tweaks
- Ensure new style classes are used
- Use correct inputstream property for listitems
- Use correct infolabel date formatting
- Use correct setting type getter/setter for str list
- Ignore labelmask parameter in sort methods
- Use traditional packages rather than namespace packages
---
.../youtube_plugin/kodion/context/xbmc/__init__.py | 0
.../kodion/context/xbmc/xbmc_context.py | 13 +++++++------
.../youtube_plugin/kodion/network/http_server.py | 9 +++++----
.../youtube_plugin/kodion/player/xbmc/__init__.py | 0
.../youtube_plugin/kodion/plugin/xbmc/__init__.py | 0
.../youtube_plugin/kodion/settings/xbmc/__init__.py | 0
.../kodion/settings/xbmc/xbmc_plugin_settings.py | 8 ++++----
.../lib/youtube_plugin/kodion/ui/xbmc/__init__.py | 0
.../youtube_plugin/kodion/ui/xbmc/info_labels.py | 8 +++++---
.../lib/youtube_plugin/kodion/ui/xbmc/xbmc_items.py | 11 ++++++-----
.../youtube_plugin/kodion/utils/system_version.py | 3 +++
11 files changed, 30 insertions(+), 22 deletions(-)
create mode 100644 resources/lib/youtube_plugin/kodion/context/xbmc/__init__.py
create mode 100644 resources/lib/youtube_plugin/kodion/player/xbmc/__init__.py
create mode 100644 resources/lib/youtube_plugin/kodion/plugin/xbmc/__init__.py
create mode 100644 resources/lib/youtube_plugin/kodion/settings/xbmc/__init__.py
create mode 100644 resources/lib/youtube_plugin/kodion/ui/xbmc/__init__.py
diff --git a/resources/lib/youtube_plugin/kodion/context/xbmc/__init__.py b/resources/lib/youtube_plugin/kodion/context/xbmc/__init__.py
new file mode 100644
index 000000000..e69de29bb
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 ecfbb28e9..bfe2cc640 100644
--- a/resources/lib/youtube_plugin/kodion/context/xbmc/xbmc_context.py
+++ b/resources/lib/youtube_plugin/kodion/context/xbmc/xbmc_context.py
@@ -20,11 +20,11 @@
import xbmcvfs
from ..abstract_context import AbstractContext
-from ... import utils
from ...player.xbmc.xbmc_player import XbmcPlayer
from ...player.xbmc.xbmc_playlist import XbmcPlaylist
from ...settings.xbmc.xbmc_plugin_settings import XbmcPluginSettings
from ...ui.xbmc.xbmc_context_ui import XbmcContextUI
+from ...utils import current_system_version, loose_version, to_unicode
class XbmcContext(AbstractContext):
@@ -403,7 +403,7 @@ def localize(self, text_id, default_text=''):
"""
source = self._addon if 30000 <= text_id < 31000 else xbmc
result = source.getLocalizedString(text_id)
- result = utils.to_unicode(result) if result else default_text
+ result = to_unicode(result) if result else default_text
return result
def set_content_type(self, content_type):
@@ -411,8 +411,9 @@ def set_content_type(self, content_type):
xbmcplugin.setContent(self._plugin_handle, content_type)
def add_sort_method(self, *sort_methods):
+ args = slice(None if current_system_version.compatible(19, 0) else 2)
for sort_method in sort_methods:
- xbmcplugin.addSortMethod(self._plugin_handle, *sort_method)
+ xbmcplugin.addSortMethod(self._plugin_handle, *sort_method[args])
def clone(self, new_path=None, new_params=None):
if not new_path:
@@ -524,16 +525,16 @@ def inputstream_adaptive_capabilities(self, capability=None):
if not self.use_inputstream_adaptive() or not inputstream_version:
return frozenset() if capability is None else None
- isa_loose_version = utils.loose_version(inputstream_version)
+ isa_loose_version = loose_version(inputstream_version)
if capability is None:
capabilities = frozenset(
capability for capability, version in self._ISA_CAPABILITIES.items()
if version is True
- or version and isa_loose_version >= utils.loose_version(version)
+ or version and isa_loose_version >= loose_version(version)
)
return capabilities
version = self._ISA_CAPABILITIES.get(capability)
- return version is True or version and isa_loose_version >= utils.loose_version(version)
+ return version is True or version and isa_loose_version >= loose_version(version)
@staticmethod
def inputstream_adaptive_auto_stream_selection():
diff --git a/resources/lib/youtube_plugin/kodion/network/http_server.py b/resources/lib/youtube_plugin/kodion/network/http_server.py
index e5fd5422e..56c5353a9 100644
--- a/resources/lib/youtube_plugin/kodion/network/http_server.py
+++ b/resources/lib/youtube_plugin/kodion/network/http_server.py
@@ -10,15 +10,16 @@
import json
import os
import re
-from socket import error as socket_error
from http import server as BaseHTTPServer
+from io import open
+from socket import error as socket_error
from textwrap import dedent
from urllib.parse import parse_qs, urlparse
-from xbmc import getCondVisibility, executebuiltin
+from xbmc import executebuiltin, getCondVisibility
+from xbmcaddon import Addon
from xbmcgui import Dialog, Window
from xbmcvfs import translatePath
-from xbmcaddon import Addon
from .requests import BaseRequestsClass
from ..logger import log_debug
@@ -32,7 +33,7 @@
_server_requests = BaseRequestsClass()
-class YouTubeProxyRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
+class YouTubeProxyRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler, object):
base_path = translatePath('special://temp/{0}'.format(_addon_id))
chunk_size = 1024 * 64
local_ranges = (
diff --git a/resources/lib/youtube_plugin/kodion/player/xbmc/__init__.py b/resources/lib/youtube_plugin/kodion/player/xbmc/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/resources/lib/youtube_plugin/kodion/plugin/xbmc/__init__.py b/resources/lib/youtube_plugin/kodion/plugin/xbmc/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/resources/lib/youtube_plugin/kodion/settings/xbmc/__init__.py b/resources/lib/youtube_plugin/kodion/settings/xbmc/__init__.py
new file mode 100644
index 000000000..e69de29bb
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 be5b888e7..1cf235bc4 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
@@ -24,7 +24,7 @@ def __init__(self, xbmc_addon):
if self._funcs:
return
- if current_system_version.get_version() >= (20, 0):
+ if current_system_version.compatible(20, 0):
_class = xbmcaddon.Settings
self._funcs.update({
@@ -41,11 +41,11 @@ def __init__(self, xbmc_addon):
_class = xbmcaddon.Addon
def _get_string_list(store, setting):
- return _class.getSettingString(store, setting).split(',')
+ return _class.getSetting(store, setting).split(',')
def _set_string_list(store, setting, value):
value = ','.join(value)
- return _class.setSettingString(store, setting, value)
+ return _class.setSetting(store, setting, value)
self._funcs.update({
'get_bool': _class.getSettingBool,
@@ -62,7 +62,7 @@ def _set_string_list(store, setting, value):
def flush(cls, xbmc_addon):
cls._echo = get_kodi_setting('debug.showloginfo')
cls._cache = {}
- if current_system_version.get_version() >= (20, 0):
+ if current_system_version.compatible(20, 0):
cls._store = xbmc_addon.getSettings()
else:
cls._store = xbmc_addon
diff --git a/resources/lib/youtube_plugin/kodion/ui/xbmc/__init__.py b/resources/lib/youtube_plugin/kodion/ui/xbmc/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/resources/lib/youtube_plugin/kodion/ui/xbmc/info_labels.py b/resources/lib/youtube_plugin/kodion/ui/xbmc/info_labels.py
index 35d74db9c..b3d13fc9d 100644
--- a/resources/lib/youtube_plugin/kodion/ui/xbmc/info_labels.py
+++ b/resources/lib/youtube_plugin/kodion/ui/xbmc/info_labels.py
@@ -8,8 +8,8 @@
See LICENSES/GPL-2.0-only for more information.
"""
-from ... import utils
from ...items import AudioItem, DirectoryItem, ImageItem, VideoItem
+from ...utils import current_system_version, datetime_parser
def _process_date_value(info_labels, name, param):
@@ -19,7 +19,9 @@ def _process_date_value(info_labels, name, param):
def _process_datetime_value(info_labels, name, param):
if param:
- info_labels[name] = param.isoformat('T')
+ info_labels[name] = (param.isoformat('T')
+ if current_system_version.compatible(19, 0) else
+ param.strftime('%d.%d.%Y'))
def _process_int_value(info_labels, name, param):
@@ -64,7 +66,7 @@ def _process_video_rating(info_labels, param):
def _process_date_string(info_labels, name, param):
if param:
- date = utils.datetime_parser.parse(param)
+ date = datetime_parser.parse(param)
info_labels[name] = date.isoformat()
diff --git a/resources/lib/youtube_plugin/kodion/ui/xbmc/xbmc_items.py b/resources/lib/youtube_plugin/kodion/ui/xbmc/xbmc_items.py
index e16416ee6..380c7868b 100644
--- a/resources/lib/youtube_plugin/kodion/ui/xbmc/xbmc_items.py
+++ b/resources/lib/youtube_plugin/kodion/ui/xbmc/xbmc_items.py
@@ -12,7 +12,7 @@
from . import info_labels
from ...items import AudioItem, UriItem, VideoItem
-from ...utils import datetime_parser
+from ...utils import current_system_version, datetime_parser
try:
@@ -34,10 +34,8 @@ def add_stream_info(self, *args, **kwargs):
def set_resume_point(self,
infoproperties,
- *_args,
resume_key='ResumeTime',
- total_key='TotalTime',
- **_kwargs):
+ total_key='TotalTime',):
if resume_key in infoproperties:
infoproperties[resume_key] = str(infoproperties[resume_key])
if total_key in infoproperties:
@@ -95,7 +93,10 @@ def video_playback_item(context, video_item):
manifest_type = 'hls'
mime_type = 'application/x-mpegURL'
- props['inputstream'] = 'inputstream.adaptive'
+ inputstream_property = ('inputstream'
+ if current_system_version.compatible(19, 0) else
+ 'inputstreamaddon')
+ props[inputstream_property] = 'inputstream.adaptive'
props['inputstream.adaptive.manifest_type'] = manifest_type
if headers:
diff --git a/resources/lib/youtube_plugin/kodion/utils/system_version.py b/resources/lib/youtube_plugin/kodion/utils/system_version.py
index d51db45fa..51e19a760 100644
--- a/resources/lib/youtube_plugin/kodion/utils/system_version.py
+++ b/resources/lib/youtube_plugin/kodion/utils/system_version.py
@@ -90,5 +90,8 @@ def get_version(self):
def get_app_name(self):
return self._appname
+ def compatible(self, *version):
+ return self._version >= version
+
current_system_version = SystemVersion()
From 91e38eb6ca45e97132fbf00993981436059d9aa7 Mon Sep 17 00:00:00 2001
From: MoojMidge <56883549+MoojMidge@users.noreply.github.com>
Date: Mon, 11 Dec 2023 16:14:07 +1100
Subject: [PATCH 083/141] Ensure current_user value is loaded as int
---
.../youtube_plugin/kodion/json_store/access_manager.py | 10 ++++++++++
1 file changed, 10 insertions(+)
diff --git a/resources/lib/youtube_plugin/kodion/json_store/access_manager.py b/resources/lib/youtube_plugin/kodion/json_store/access_manager.py
index e54b0dc62..f4d152802 100644
--- a/resources/lib/youtube_plugin/kodion/json_store/access_manager.py
+++ b/resources/lib/youtube_plugin/kodion/json_store/access_manager.py
@@ -111,6 +111,11 @@ def _process_data(data):
int(key): value
for key, value in users.items()
}
+ current_user = data['access_manager']['current_user']
+ try:
+ data['access_manager']['current_user'] = int(current_user)
+ except (TypeError, ValueError):
+ pass
return data
def get_data(self, process=_process_data.__func__):
@@ -216,6 +221,11 @@ def set_user(self, user, switch_to=False):
:param switch_to: boolean, change current user
:return:
"""
+ try:
+ user = int(user)
+ except (TypeError, ValueError):
+ pass
+
self._user = user
if switch_to:
data = {
From b16e5ac5b11e61359bb9a2edc2b9c6fa2904c3cd Mon Sep 17 00:00:00 2001
From: MoojMidge <56883549+MoojMidge@users.noreply.github.com>
Date: Sat, 9 Dec 2023 16:45:53 +1100
Subject: [PATCH 084/141] Kodi Leia compatibility
---
resources/lib/__init__.py | 3 +
resources/lib/default.py | 2 +
resources/lib/startup.py | 2 +
resources/lib/youtube_authentication.py | 2 +
resources/lib/youtube_plugin/__init__.py | 3 +
.../kodion/abstract_provider.py | 4 +-
.../kodion/compatibility/__init__.py | 116 ++++++++++++++++++
.../kodion/constants/__init__.py | 2 +
.../kodion/constants/const_content_types.py | 2 +
.../kodion/constants/const_paths.py | 2 +
.../kodion/constants/const_settings.py | 2 +
.../kodion/constants/const_sort_methods.py | 5 +-
.../youtube_plugin/kodion/context/__init__.py | 2 +
.../kodion/context/abstract_context.py | 4 +-
.../kodion/context/xbmc/xbmc_context.py | 18 ++-
resources/lib/youtube_plugin/kodion/debug.py | 2 +
.../youtube_plugin/kodion/items/__init__.py | 2 +
.../youtube_plugin/kodion/items/audio_item.py | 5 +-
.../youtube_plugin/kodion/items/base_item.py | 4 +-
.../kodion/items/directory_item.py | 2 +
.../kodion/items/favorites_item.py | 2 +
.../youtube_plugin/kodion/items/image_item.py | 2 +
.../kodion/items/new_search_item.py | 2 +
.../kodion/items/next_page_item.py | 2 +
.../kodion/items/search_history_item.py | 2 +
.../kodion/items/search_item.py | 2 +
.../youtube_plugin/kodion/items/uri_item.py | 2 +
.../lib/youtube_plugin/kodion/items/utils.py | 2 +
.../youtube_plugin/kodion/items/video_item.py | 4 +-
.../kodion/items/watch_later_item.py | 2 +
.../kodion/json_store/__init__.py | 2 +
.../kodion/json_store/api_keys.py | 2 +
.../kodion/json_store/json_store.py | 6 +-
resources/lib/youtube_plugin/kodion/logger.py | 6 +-
.../youtube_plugin/kodion/network/__init__.py | 2 +
.../kodion/network/http_server.py | 38 +++---
.../youtube_plugin/kodion/network/ip_api.py | 2 +
.../youtube_plugin/kodion/network/requests.py | 8 +-
.../youtube_plugin/kodion/player/__init__.py | 2 +
.../kodion/player/xbmc/xbmc_player.py | 4 +-
.../kodion/player/xbmc/xbmc_playlist.py | 4 +-
.../youtube_plugin/kodion/plugin/__init__.py | 2 +
.../kodion/plugin/xbmc/xbmc_runner.py | 4 +-
resources/lib/youtube_plugin/kodion/runner.py | 2 +
.../lib/youtube_plugin/kodion/service.py | 2 +
.../kodion/settings/__init__.py | 2 +
.../kodion/settings/abstract_settings.py | 2 +
.../settings/xbmc/xbmc_plugin_settings.py | 3 +-
.../kodion/sql_store/data_cache.py | 2 +
.../kodion/sql_store/favorite_list.py | 2 +
.../kodion/sql_store/function_cache.py | 2 +
.../kodion/sql_store/playback_history.py | 2 +
.../kodion/sql_store/search_history.py | 2 +
.../kodion/sql_store/storage.py | 2 +
.../kodion/sql_store/watch_later_list.py | 2 +
.../lib/youtube_plugin/kodion/ui/__init__.py | 2 +
.../kodion/ui/xbmc/info_labels.py | 2 +
.../kodion/ui/xbmc/xbmc_context_ui.py | 4 +-
.../kodion/ui/xbmc/xbmc_items.py | 13 +-
.../kodion/ui/xbmc/xbmc_progress_dialog.py | 4 +-
.../youtube_plugin/kodion/utils/__init__.py | 2 +
.../kodion/utils/datetime_parser.py | 2 +
.../youtube_plugin/kodion/utils/methods.py | 6 +-
.../youtube_plugin/kodion/utils/monitor.py | 12 +-
.../lib/youtube_plugin/kodion/utils/player.py | 4 +-
.../kodion/utils/system_version.py | 4 +-
resources/lib/youtube_plugin/refresh.py | 3 +
.../lib/youtube_plugin/youtube/__init__.py | 2 +
.../youtube/client/__config__.py | 2 +
.../youtube_plugin/youtube/client/__init__.py | 2 +
.../youtube/client/login_client.py | 4 +-
.../youtube/client/request_client.py | 2 +
.../youtube_plugin/youtube/client/youtube.py | 6 +-
.../youtube_plugin/youtube/helper/__init__.py | 2 +
.../youtube/helper/ratebypass/__init__.py | 2 +
.../youtube/helper/ratebypass/ratebypass.py | 2 +
.../youtube/helper/resource_manager.py | 2 +
.../youtube/helper/signature/__init__.py | 2 +
.../youtube/helper/signature/cipher.py | 2 +
.../youtube/helper/subtitles.py | 16 ++-
.../lib/youtube_plugin/youtube/helper/tv.py | 2 +
.../youtube/helper/url_resolver.py | 5 +-
.../youtube/helper/url_to_item_converter.py | 4 +-
.../youtube_plugin/youtube/helper/utils.py | 2 +
.../lib/youtube_plugin/youtube/helper/v3.py | 2 +
.../youtube/helper/video_info.py | 22 ++--
.../youtube/helper/yt_context_menu.py | 2 +
.../youtube_plugin/youtube/helper/yt_login.py | 2 +
.../youtube/helper/yt_old_actions.py | 2 +
.../youtube_plugin/youtube/helper/yt_play.py | 2 +
.../youtube/helper/yt_playlist.py | 2 +
.../youtube/helper/yt_setup_wizard.py | 2 +
.../youtube/helper/yt_specials.py | 2 +
.../youtube/helper/yt_subscriptions.py | 2 +
.../youtube_plugin/youtube/helper/yt_video.py | 2 +
.../lib/youtube_plugin/youtube/provider.py | 6 +-
.../youtube/youtube_exceptions.py | 2 +
resources/lib/youtube_registration.py | 2 +
resources/lib/youtube_requests.py | 2 +
resources/lib/youtube_resolver.py | 2 +
100 files changed, 396 insertions(+), 90 deletions(-)
create mode 100644 resources/lib/youtube_plugin/kodion/compatibility/__init__.py
diff --git a/resources/lib/__init__.py b/resources/lib/__init__.py
index d2d4112fb..2c7daf8bc 100644
--- a/resources/lib/__init__.py
+++ b/resources/lib/__init__.py
@@ -8,4 +8,7 @@
See LICENSES/GPL-2.0-only for more information.
"""
+from __future__ import absolute_import, division, unicode_literals
+
+
__all__ = ['youtube_plugin']
diff --git a/resources/lib/default.py b/resources/lib/default.py
index 10385f513..ecb6f2d3a 100644
--- a/resources/lib/default.py
+++ b/resources/lib/default.py
@@ -8,6 +8,8 @@
See LICENSES/GPL-2.0-only for more information.
"""
+from __future__ import absolute_import, division, unicode_literals
+
from youtube_plugin.kodion import runner
from youtube_plugin import youtube
diff --git a/resources/lib/startup.py b/resources/lib/startup.py
index 36f71ca9c..31b84ed00 100644
--- a/resources/lib/startup.py
+++ b/resources/lib/startup.py
@@ -8,6 +8,8 @@
See LICENSES/GPL-2.0-only for more information.
"""
+from __future__ import absolute_import, division, unicode_literals
+
from youtube_plugin.kodion import service
service.run()
diff --git a/resources/lib/youtube_authentication.py b/resources/lib/youtube_authentication.py
index 08abf56c9..e29822074 100644
--- a/resources/lib/youtube_authentication.py
+++ b/resources/lib/youtube_authentication.py
@@ -7,6 +7,8 @@
See LICENSES/GPL-2.0-only for more information.
"""
+from __future__ import absolute_import, division, unicode_literals
+
from youtube_plugin.youtube.provider import Provider
from youtube_plugin.kodion.context import Context
from youtube_plugin.youtube.helper import yt_login
diff --git a/resources/lib/youtube_plugin/__init__.py b/resources/lib/youtube_plugin/__init__.py
index 02f46fa36..84fc92d21 100644
--- a/resources/lib/youtube_plugin/__init__.py
+++ b/resources/lib/youtube_plugin/__init__.py
@@ -8,6 +8,9 @@
See LICENSES/GPL-2.0-only for more information.
"""
+from __future__ import absolute_import, division, unicode_literals
+
+
key_sets = {
'youtube-tv': {
'id': 'ODYxNTU2NzA4NDU0LWQ2ZGxtM2xoMDVpZGQ4bnBlazE4azZiZThiYTNvYzY4',
diff --git a/resources/lib/youtube_plugin/kodion/abstract_provider.py b/resources/lib/youtube_plugin/kodion/abstract_provider.py
index cf49f8732..b346a196e 100644
--- a/resources/lib/youtube_plugin/kodion/abstract_provider.py
+++ b/resources/lib/youtube_plugin/kodion/abstract_provider.py
@@ -8,11 +8,13 @@
See LICENSES/GPL-2.0-only for more information.
"""
+from __future__ import absolute_import, division, unicode_literals
+
import json
import re
-from urllib.parse import quote, unquote
from . import constants
+from .compatibility import quote, unquote
from .exceptions import KodionException
from .items import (
DirectoryItem,
diff --git a/resources/lib/youtube_plugin/kodion/compatibility/__init__.py b/resources/lib/youtube_plugin/kodion/compatibility/__init__.py
new file mode 100644
index 000000000..904da9afc
--- /dev/null
+++ b/resources/lib/youtube_plugin/kodion/compatibility/__init__.py
@@ -0,0 +1,116 @@
+# -*- coding: utf-8 -*-
+"""
+
+ Copyright (C) 2023-present plugin.video.youtube
+
+ SPDX-License-Identifier: GPL-2.0-only
+ See LICENSES/GPL-2.0-only for more information.
+"""
+
+try:
+ from html import unescape
+ from http import server as BaseHTTPServer
+ from urllib.parse import (
+ parse_qs,
+ parse_qsl,
+ quote,
+ unquote,
+ urlencode,
+ urljoin,
+ urlparse,
+ urlsplit,
+ urlunsplit,
+ )
+
+ import xbmc
+ import xbmcaddon
+ import xbmcgui
+ import xbmcplugin
+ import xbmcvfs
+
+except ImportError:
+ import BaseHTTPServer
+ from contextlib import contextmanager as _contextmanager
+ from urllib import (
+ quote as _quote,
+ unquote as _unquote,
+ urlencode as _urlencode,
+ )
+ from urlparse import (
+ parse_qs,
+ parse_qsl,
+ urljoin,
+ urlparse,
+ urlsplit,
+ urlunsplit,
+ )
+ from xml.sax.saxutils import unescape
+
+ from kodi_six import (
+ xbmc,
+ xbmcaddon,
+ xbmcgui,
+ xbmcplugin,
+ xbmcvfs,
+ )
+
+
+ def quote(data, *args, **kwargs):
+ return _quote(data.encode('utf-8'), *args, **kwargs)
+
+
+ def unquote(data):
+ return _unquote(data.encode('utf-8'))
+
+
+ def urlencode(data, *args, **kwargs):
+ if isinstance(data, dict):
+ data = data.items()
+ return _urlencode({
+ key.encode('utf-8'): (
+ [part.encode('utf-8') if isinstance(part, unicode)
+ else str(part)
+ for part in value] if isinstance(value, (list, tuple))
+ else value.encode('utf-8') if isinstance(value, unicode)
+ else str(value)
+ )
+ for key, value in data
+ }, *args, **kwargs)
+
+
+ _File = xbmcvfs.File
+
+
+ @_contextmanager
+ def _file_closer(*args, **kwargs):
+ file = None
+ try:
+ file = _File(*args, **kwargs)
+ yield file
+ finally:
+ if file:
+ file.close()
+
+
+ xbmcvfs.File = _file_closer
+ xbmcvfs.translatePath = xbmc.translatePath
+
+
+__all__ = (
+ 'BaseHTTPServer',
+ 'parse_qs',
+ 'parse_qsl',
+ 'quote',
+ 'unescape',
+ 'unquote',
+ 'urlencode',
+ 'urljoin',
+ 'urlparse',
+ 'urlsplit',
+ 'urlunsplit',
+ 'xbmc',
+ 'xbmcaddon',
+ 'xbmcgui',
+ 'xbmcplugin',
+ 'xbmcvfs',
+)
diff --git a/resources/lib/youtube_plugin/kodion/constants/__init__.py b/resources/lib/youtube_plugin/kodion/constants/__init__.py
index 655985e9b..91e90d688 100644
--- a/resources/lib/youtube_plugin/kodion/constants/__init__.py
+++ b/resources/lib/youtube_plugin/kodion/constants/__init__.py
@@ -8,6 +8,8 @@
See LICENSES/GPL-2.0-only for more information.
"""
+from __future__ import absolute_import, division, unicode_literals
+
from . import const_settings as setting
from . import const_sort_methods as sort_method
from . import const_content_types as content_type
diff --git a/resources/lib/youtube_plugin/kodion/constants/const_content_types.py b/resources/lib/youtube_plugin/kodion/constants/const_content_types.py
index 0c2998b59..5990ee1f6 100644
--- a/resources/lib/youtube_plugin/kodion/constants/const_content_types.py
+++ b/resources/lib/youtube_plugin/kodion/constants/const_content_types.py
@@ -8,6 +8,8 @@
See LICENSES/GPL-2.0-only for more information.
"""
+from __future__ import absolute_import, division, unicode_literals
+
FILES = 'files'
SONGS = 'songs'
ARTISTS = 'artists'
diff --git a/resources/lib/youtube_plugin/kodion/constants/const_paths.py b/resources/lib/youtube_plugin/kodion/constants/const_paths.py
index 59e254ac4..8903eb34d 100644
--- a/resources/lib/youtube_plugin/kodion/constants/const_paths.py
+++ b/resources/lib/youtube_plugin/kodion/constants/const_paths.py
@@ -8,6 +8,8 @@
See LICENSES/GPL-2.0-only for more information.
"""
+from __future__ import absolute_import, division, unicode_literals
+
SEARCH = 'kodion/search'
FAVORITES = 'kodion/favorites'
WATCH_LATER = 'kodion/watch_later'
diff --git a/resources/lib/youtube_plugin/kodion/constants/const_settings.py b/resources/lib/youtube_plugin/kodion/constants/const_settings.py
index 70501103b..16ddff484 100644
--- a/resources/lib/youtube_plugin/kodion/constants/const_settings.py
+++ b/resources/lib/youtube_plugin/kodion/constants/const_settings.py
@@ -8,6 +8,8 @@
See LICENSES/GPL-2.0-only for more information.
"""
+from __future__ import absolute_import, division, unicode_literals
+
THUMB_SIZE = 'kodion.thumbnail.size' # (int)
SHOW_FANART = 'kodion.fanart.show' # (bool)
SAFE_SEARCH = 'kodion.safe.search' # (int)
diff --git a/resources/lib/youtube_plugin/kodion/constants/const_sort_methods.py b/resources/lib/youtube_plugin/kodion/constants/const_sort_methods.py
index c8d2cdb8e..8b2f6437d 100644
--- a/resources/lib/youtube_plugin/kodion/constants/const_sort_methods.py
+++ b/resources/lib/youtube_plugin/kodion/constants/const_sort_methods.py
@@ -8,11 +8,14 @@
See LICENSES/GPL-2.0-only for more information.
"""
+from __future__ import absolute_import, division, unicode_literals
+
import sys
-from xbmcplugin import __dict__ as xbmcplugin
+from ..compatibility import xbmcplugin
+xbmcplugin = xbmcplugin.__dict__
namespace = sys.modules[__name__]
names = [
'NONE', # 0
diff --git a/resources/lib/youtube_plugin/kodion/context/__init__.py b/resources/lib/youtube_plugin/kodion/context/__init__.py
index 89d69a6ec..1d90e85c3 100644
--- a/resources/lib/youtube_plugin/kodion/context/__init__.py
+++ b/resources/lib/youtube_plugin/kodion/context/__init__.py
@@ -7,6 +7,8 @@
See LICENSES/GPL-2.0-only for more information.
"""
+from __future__ import absolute_import, division, unicode_literals
+
from .xbmc.xbmc_context import XbmcContext as Context
diff --git a/resources/lib/youtube_plugin/kodion/context/abstract_context.py b/resources/lib/youtube_plugin/kodion/context/abstract_context.py
index aff45b4d1..765cc61e4 100644
--- a/resources/lib/youtube_plugin/kodion/context/abstract_context.py
+++ b/resources/lib/youtube_plugin/kodion/context/abstract_context.py
@@ -8,10 +8,12 @@
See LICENSES/GPL-2.0-only for more information.
"""
+from __future__ import absolute_import, division, unicode_literals
+
import os
-from urllib.parse import urlencode
from .. import constants, logger
+from ..compatibility import urlencode
from ..json_store import AccessManager
from ..sql_store import (
DataCache,
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 bfe2cc640..65e652b4f 100644
--- a/resources/lib/youtube_plugin/kodion/context/xbmc/xbmc_context.py
+++ b/resources/lib/youtube_plugin/kodion/context/xbmc/xbmc_context.py
@@ -8,18 +8,24 @@
See LICENSES/GPL-2.0-only for more information.
"""
+from __future__ import absolute_import, division, unicode_literals
+
import json
import os
import sys
import weakref
-from urllib.parse import parse_qsl, quote, unquote, urlparse
-
-import xbmc
-import xbmcaddon
-import xbmcplugin
-import xbmcvfs
from ..abstract_context import AbstractContext
+from ...compatibility import (
+ parse_qsl,
+ quote,
+ unquote,
+ urlparse,
+ xbmc,
+ xbmcaddon,
+ xbmcplugin,
+ xbmcvfs,
+)
from ...player.xbmc.xbmc_player import XbmcPlayer
from ...player.xbmc.xbmc_playlist import XbmcPlaylist
from ...settings.xbmc.xbmc_plugin_settings import XbmcPluginSettings
diff --git a/resources/lib/youtube_plugin/kodion/debug.py b/resources/lib/youtube_plugin/kodion/debug.py
index b1f22a02c..3160c47ec 100644
--- a/resources/lib/youtube_plugin/kodion/debug.py
+++ b/resources/lib/youtube_plugin/kodion/debug.py
@@ -8,6 +8,8 @@
See LICENSES/GPL-2.0-only for more information.
"""
+from __future__ import absolute_import, division, unicode_literals
+
import json
import os
from io import open
diff --git a/resources/lib/youtube_plugin/kodion/items/__init__.py b/resources/lib/youtube_plugin/kodion/items/__init__.py
index be32dbd74..fff783f35 100644
--- a/resources/lib/youtube_plugin/kodion/items/__init__.py
+++ b/resources/lib/youtube_plugin/kodion/items/__init__.py
@@ -8,6 +8,8 @@
See LICENSES/GPL-2.0-only for more information.
"""
+from __future__ import absolute_import, division, unicode_literals
+
from .utils import to_json, from_json, to_jsons
from .uri_item import UriItem
diff --git a/resources/lib/youtube_plugin/kodion/items/audio_item.py b/resources/lib/youtube_plugin/kodion/items/audio_item.py
index a7170fc5a..226113940 100644
--- a/resources/lib/youtube_plugin/kodion/items/audio_item.py
+++ b/resources/lib/youtube_plugin/kodion/items/audio_item.py
@@ -8,9 +8,10 @@
See LICENSES/GPL-2.0-only for more information.
"""
-from .base_item import BaseItem
+from __future__ import absolute_import, division, unicode_literals
-from html import unescape
+from .base_item import BaseItem
+from ..compatibility import unescape
class AudioItem(BaseItem):
diff --git a/resources/lib/youtube_plugin/kodion/items/base_item.py b/resources/lib/youtube_plugin/kodion/items/base_item.py
index 5054f3a29..44d8a89d3 100644
--- a/resources/lib/youtube_plugin/kodion/items/base_item.py
+++ b/resources/lib/youtube_plugin/kodion/items/base_item.py
@@ -8,10 +8,12 @@
See LICENSES/GPL-2.0-only for more information.
"""
+from __future__ import absolute_import, division, unicode_literals
+
import hashlib
import datetime
-from html import unescape
+from ..compatibility import unescape
class BaseItem(object):
diff --git a/resources/lib/youtube_plugin/kodion/items/directory_item.py b/resources/lib/youtube_plugin/kodion/items/directory_item.py
index 14b1702f8..2738c0b03 100644
--- a/resources/lib/youtube_plugin/kodion/items/directory_item.py
+++ b/resources/lib/youtube_plugin/kodion/items/directory_item.py
@@ -8,6 +8,8 @@
See LICENSES/GPL-2.0-only for more information.
"""
+from __future__ import absolute_import, division, unicode_literals
+
from .base_item import BaseItem
diff --git a/resources/lib/youtube_plugin/kodion/items/favorites_item.py b/resources/lib/youtube_plugin/kodion/items/favorites_item.py
index a32c5b756..8e25552bb 100644
--- a/resources/lib/youtube_plugin/kodion/items/favorites_item.py
+++ b/resources/lib/youtube_plugin/kodion/items/favorites_item.py
@@ -8,6 +8,8 @@
See LICENSES/GPL-2.0-only for more information.
"""
+from __future__ import absolute_import, division, unicode_literals
+
from .directory_item import DirectoryItem
from .. import constants
diff --git a/resources/lib/youtube_plugin/kodion/items/image_item.py b/resources/lib/youtube_plugin/kodion/items/image_item.py
index 2a1217135..1edb640e6 100644
--- a/resources/lib/youtube_plugin/kodion/items/image_item.py
+++ b/resources/lib/youtube_plugin/kodion/items/image_item.py
@@ -8,6 +8,8 @@
See LICENSES/GPL-2.0-only for more information.
"""
+from __future__ import absolute_import, division, unicode_literals
+
from .base_item import BaseItem
diff --git a/resources/lib/youtube_plugin/kodion/items/new_search_item.py b/resources/lib/youtube_plugin/kodion/items/new_search_item.py
index f0985d96d..18f79c97e 100644
--- a/resources/lib/youtube_plugin/kodion/items/new_search_item.py
+++ b/resources/lib/youtube_plugin/kodion/items/new_search_item.py
@@ -8,6 +8,8 @@
See LICENSES/GPL-2.0-only for more information.
"""
+from __future__ import absolute_import, division, unicode_literals
+
from .directory_item import DirectoryItem
from .. import constants
diff --git a/resources/lib/youtube_plugin/kodion/items/next_page_item.py b/resources/lib/youtube_plugin/kodion/items/next_page_item.py
index fc4b83576..ee266e52b 100644
--- a/resources/lib/youtube_plugin/kodion/items/next_page_item.py
+++ b/resources/lib/youtube_plugin/kodion/items/next_page_item.py
@@ -8,6 +8,8 @@
See LICENSES/GPL-2.0-only for more information.
"""
+from __future__ import absolute_import, division, unicode_literals
+
from .directory_item import DirectoryItem
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 42e9f7c1f..e53d020aa 100644
--- a/resources/lib/youtube_plugin/kodion/items/search_history_item.py
+++ b/resources/lib/youtube_plugin/kodion/items/search_history_item.py
@@ -8,6 +8,8 @@
See LICENSES/GPL-2.0-only for more information.
"""
+from __future__ import absolute_import, division, unicode_literals
+
from .directory_item import DirectoryItem
from ..constants.const_paths import SEARCH
diff --git a/resources/lib/youtube_plugin/kodion/items/search_item.py b/resources/lib/youtube_plugin/kodion/items/search_item.py
index 39869c2c0..2ff9bbd1f 100644
--- a/resources/lib/youtube_plugin/kodion/items/search_item.py
+++ b/resources/lib/youtube_plugin/kodion/items/search_item.py
@@ -8,6 +8,8 @@
See LICENSES/GPL-2.0-only for more information.
"""
+from __future__ import absolute_import, division, unicode_literals
+
from .directory_item import DirectoryItem
from ..constants.const_paths import SEARCH
diff --git a/resources/lib/youtube_plugin/kodion/items/uri_item.py b/resources/lib/youtube_plugin/kodion/items/uri_item.py
index e30df6e7e..c5926afb2 100644
--- a/resources/lib/youtube_plugin/kodion/items/uri_item.py
+++ b/resources/lib/youtube_plugin/kodion/items/uri_item.py
@@ -8,6 +8,8 @@
See LICENSES/GPL-2.0-only for more information.
"""
+from __future__ import absolute_import, division, unicode_literals
+
from .base_item import BaseItem
diff --git a/resources/lib/youtube_plugin/kodion/items/utils.py b/resources/lib/youtube_plugin/kodion/items/utils.py
index cabfc1885..64e0966ba 100644
--- a/resources/lib/youtube_plugin/kodion/items/utils.py
+++ b/resources/lib/youtube_plugin/kodion/items/utils.py
@@ -8,6 +8,8 @@
See LICENSES/GPL-2.0-only for more information.
"""
+from __future__ import absolute_import, division, unicode_literals
+
import json
from .audio_item import AudioItem
diff --git a/resources/lib/youtube_plugin/kodion/items/video_item.py b/resources/lib/youtube_plugin/kodion/items/video_item.py
index 234b5c160..2e40a3f6a 100644
--- a/resources/lib/youtube_plugin/kodion/items/video_item.py
+++ b/resources/lib/youtube_plugin/kodion/items/video_item.py
@@ -8,11 +8,13 @@
See LICENSES/GPL-2.0-only for more information.
"""
+from __future__ import absolute_import, division, unicode_literals
+
import datetime
import re
-from html import unescape
from .base_item import BaseItem
+from ..compatibility import unescape
from ..utils import duration_to_seconds, seconds_to_duration
diff --git a/resources/lib/youtube_plugin/kodion/items/watch_later_item.py b/resources/lib/youtube_plugin/kodion/items/watch_later_item.py
index 0a10c27f0..01cfc0455 100644
--- a/resources/lib/youtube_plugin/kodion/items/watch_later_item.py
+++ b/resources/lib/youtube_plugin/kodion/items/watch_later_item.py
@@ -8,6 +8,8 @@
See LICENSES/GPL-2.0-only for more information.
"""
+from __future__ import absolute_import, division, unicode_literals
+
from .directory_item import DirectoryItem
from .. import constants
diff --git a/resources/lib/youtube_plugin/kodion/json_store/__init__.py b/resources/lib/youtube_plugin/kodion/json_store/__init__.py
index 96383f1c0..0e05ebe41 100644
--- a/resources/lib/youtube_plugin/kodion/json_store/__init__.py
+++ b/resources/lib/youtube_plugin/kodion/json_store/__init__.py
@@ -7,6 +7,8 @@
See LICENSES/GPL-2.0-only for more information.
"""
+from __future__ import absolute_import, division, unicode_literals
+
from .access_manager import AccessManager
from .api_keys import APIKeyStore
diff --git a/resources/lib/youtube_plugin/kodion/json_store/api_keys.py b/resources/lib/youtube_plugin/kodion/json_store/api_keys.py
index 3c20a22c4..488ea8ab4 100644
--- a/resources/lib/youtube_plugin/kodion/json_store/api_keys.py
+++ b/resources/lib/youtube_plugin/kodion/json_store/api_keys.py
@@ -7,6 +7,8 @@
See LICENSES/GPL-2.0-only for more information.
"""
+from __future__ import absolute_import, division, unicode_literals
+
from .json_store import JSONStore
diff --git a/resources/lib/youtube_plugin/kodion/json_store/json_store.py b/resources/lib/youtube_plugin/kodion/json_store/json_store.py
index 87e1d6180..b6dea6368 100644
--- a/resources/lib/youtube_plugin/kodion/json_store/json_store.py
+++ b/resources/lib/youtube_plugin/kodion/json_store/json_store.py
@@ -7,13 +7,13 @@
See LICENSES/GPL-2.0-only for more information.
"""
+from __future__ import absolute_import, division, unicode_literals
+
import json
import os
from io import open
-import xbmcaddon
-import xbmcvfs
-
+from ..compatibility import xbmcaddon, xbmcvfs
from ..logger import log_debug, log_error
from ..utils import make_dirs, merge_dicts, to_unicode
diff --git a/resources/lib/youtube_plugin/kodion/logger.py b/resources/lib/youtube_plugin/kodion/logger.py
index df709b3bb..0940faf42 100644
--- a/resources/lib/youtube_plugin/kodion/logger.py
+++ b/resources/lib/youtube_plugin/kodion/logger.py
@@ -8,8 +8,10 @@
See LICENSES/GPL-2.0-only for more information.
"""
-import xbmc
-import xbmcaddon
+from __future__ import absolute_import, division, unicode_literals
+
+from .compatibility import xbmc, xbmcaddon
+
DEBUG = xbmc.LOGDEBUG
INFO = xbmc.LOGINFO
diff --git a/resources/lib/youtube_plugin/kodion/network/__init__.py b/resources/lib/youtube_plugin/kodion/network/__init__.py
index 8e1c3dccf..960dd0e91 100644
--- a/resources/lib/youtube_plugin/kodion/network/__init__.py
+++ b/resources/lib/youtube_plugin/kodion/network/__init__.py
@@ -7,6 +7,8 @@
See LICENSES/GPL-2.0-only for more information.
"""
+from __future__ import absolute_import, division, unicode_literals
+
from .http_server import get_client_ip_address, get_http_server, is_httpd_live
from .ip_api import Locator
from .requests import BaseRequestsClass
diff --git a/resources/lib/youtube_plugin/kodion/network/http_server.py b/resources/lib/youtube_plugin/kodion/network/http_server.py
index 56c5353a9..ea06ddff7 100644
--- a/resources/lib/youtube_plugin/kodion/network/http_server.py
+++ b/resources/lib/youtube_plugin/kodion/network/http_server.py
@@ -7,34 +7,38 @@
See LICENSES/GPL-2.0-only for more information.
"""
+from __future__ import absolute_import, division, unicode_literals
+
import json
import os
import re
-from http import server as BaseHTTPServer
from io import open
from socket import error as socket_error
from textwrap import dedent
-from urllib.parse import parse_qs, urlparse
-
-from xbmc import executebuiltin, getCondVisibility
-from xbmcaddon import Addon
-from xbmcgui import Dialog, Window
-from xbmcvfs import translatePath
from .requests import BaseRequestsClass
+from ..compatibility import (
+ BaseHTTPServer,
+ parse_qs,
+ urlparse,
+ xbmc,
+ xbmcaddon,
+ xbmcgui,
+ xbmcvfs,
+)
from ..logger import log_debug
from ..settings import Settings
_addon_id = 'plugin.video.youtube'
-_addon = Addon(_addon_id)
+_addon = xbmcaddon.Addon(_addon_id)
_settings = Settings(_addon)
_i18n = _addon.getLocalizedString
_server_requests = BaseRequestsClass()
class YouTubeProxyRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler, object):
- base_path = translatePath('special://temp/{0}'.format(_addon_id))
+ base_path = xbmcvfs.translatePath('special://temp/{0}'.format(_addon_id))
chunk_size = 1024 * 64
local_ranges = (
'10.',
@@ -124,7 +128,7 @@ def do_GET(self):
self.wfile.write(chunk)
elif api_config_enabled and stripped_path.startswith('/api_submit'):
- executebuiltin('Dialog.Close(addonsettings, true)')
+ xbmc.executebuiltin('Dialog.Close(addonsettings, true)')
query = urlparse(self.path).query
params = parse_qs(query)
@@ -215,7 +219,7 @@ def do_POST(self):
if not self.connection_allowed():
self.send_error(403)
elif self.path.startswith('/widevine'):
- home = Window(10000)
+ home = xbmcgui.Window(10000)
lic_url = home.getProperty('plugin.video.youtube-license_url')
if not lic_url:
@@ -267,7 +271,7 @@ def do_POST(self):
if 'HD' in authorized_types:
size_limit = fmt_to_px['HD']
elif 'HD720' in authorized_types:
- if getCondVisibility('system.platform.android') == 1:
+ if xbmc.getCondVisibility('system.platform.android') == 1:
size_limit = fmt_to_px['HD720']
else:
size_limit = fmt_to_px['SD']
@@ -528,11 +532,11 @@ def get_http_server(address=None, port=None):
except socket_error as e:
log_debug('HTTPServer: Failed to start |{address}:{port}| |{response}|'
.format(address=address, port=port, response=str(e)))
- Dialog().notification(_addon.getAddonInfo('name'),
- str(e),
- _addon.getAddonInfo('icon'),
- time=5000,
- sound=False)
+ xbmcgui.Dialog().notification(_addon.getAddonInfo('name'),
+ str(e),
+ _addon.getAddonInfo('icon'),
+ time=5000,
+ sound=False)
return None
diff --git a/resources/lib/youtube_plugin/kodion/network/ip_api.py b/resources/lib/youtube_plugin/kodion/network/ip_api.py
index 79892c6fe..ed009455e 100644
--- a/resources/lib/youtube_plugin/kodion/network/ip_api.py
+++ b/resources/lib/youtube_plugin/kodion/network/ip_api.py
@@ -7,6 +7,8 @@
See LICENSES/GPL-2.0-only for more information.
"""
+from __future__ import absolute_import, division, unicode_literals
+
from .requests import BaseRequestsClass
from .. import logger
diff --git a/resources/lib/youtube_plugin/kodion/network/requests.py b/resources/lib/youtube_plugin/kodion/network/requests.py
index ad83e1f44..19e8b570c 100644
--- a/resources/lib/youtube_plugin/kodion/network/requests.py
+++ b/resources/lib/youtube_plugin/kodion/network/requests.py
@@ -7,21 +7,21 @@
See LICENSES/GPL-2.0-only for more information.
"""
-import atexit
+from __future__ import absolute_import, division, unicode_literals
+import atexit
from traceback import format_exc, format_stack
from requests import Session
from requests.adapters import HTTPAdapter, Retry
from requests.exceptions import RequestException
+from ..compatibility import xbmcaddon
from ..logger import log_error
from ..settings import Settings
-from xbmcaddon import Addon
-
-_settings = Settings(Addon(id='plugin.video.youtube'))
+_settings = Settings(xbmcaddon.Addon(id='plugin.video.youtube'))
class BaseRequestsClass(object):
diff --git a/resources/lib/youtube_plugin/kodion/player/__init__.py b/resources/lib/youtube_plugin/kodion/player/__init__.py
index e30f5d82f..4c8c94b61 100644
--- a/resources/lib/youtube_plugin/kodion/player/__init__.py
+++ b/resources/lib/youtube_plugin/kodion/player/__init__.py
@@ -7,6 +7,8 @@
See LICENSES/GPL-2.0-only for more information.
"""
+from __future__ import absolute_import, division, unicode_literals
+
from .xbmc.xbmc_player import XbmcPlayer as Player
from .xbmc.xbmc_playlist import XbmcPlaylist as Playlist
diff --git a/resources/lib/youtube_plugin/kodion/player/xbmc/xbmc_player.py b/resources/lib/youtube_plugin/kodion/player/xbmc/xbmc_player.py
index 4b44cfaa7..498acb85b 100644
--- a/resources/lib/youtube_plugin/kodion/player/xbmc/xbmc_player.py
+++ b/resources/lib/youtube_plugin/kodion/player/xbmc/xbmc_player.py
@@ -8,8 +8,10 @@
See LICENSES/GPL-2.0-only for more information.
"""
-import xbmc
+from __future__ import absolute_import, division, unicode_literals
+
from ..abstract_player import AbstractPlayer
+from ...compatibility import xbmc
class XbmcPlayer(AbstractPlayer):
diff --git a/resources/lib/youtube_plugin/kodion/player/xbmc/xbmc_playlist.py b/resources/lib/youtube_plugin/kodion/player/xbmc/xbmc_playlist.py
index bc171be58..29e03de19 100644
--- a/resources/lib/youtube_plugin/kodion/player/xbmc/xbmc_playlist.py
+++ b/resources/lib/youtube_plugin/kodion/player/xbmc/xbmc_playlist.py
@@ -8,10 +8,12 @@
See LICENSES/GPL-2.0-only for more information.
"""
+from __future__ import absolute_import, division, unicode_literals
+
import json
-import xbmc
from ..abstract_playlist import AbstractPlaylist
+from ...compatibility import xbmc
from ...items import VideoItem
from ...ui.xbmc import xbmc_items
diff --git a/resources/lib/youtube_plugin/kodion/plugin/__init__.py b/resources/lib/youtube_plugin/kodion/plugin/__init__.py
index 19ec2ef97..19fca2ffd 100644
--- a/resources/lib/youtube_plugin/kodion/plugin/__init__.py
+++ b/resources/lib/youtube_plugin/kodion/plugin/__init__.py
@@ -7,6 +7,8 @@
See LICENSES/GPL-2.0-only for more information.
"""
+from __future__ import absolute_import, division, unicode_literals
+
from .xbmc.xbmc_runner import XbmcRunner as Runner
diff --git a/resources/lib/youtube_plugin/kodion/plugin/xbmc/xbmc_runner.py b/resources/lib/youtube_plugin/kodion/plugin/xbmc/xbmc_runner.py
index c4b178b6c..131451578 100644
--- a/resources/lib/youtube_plugin/kodion/plugin/xbmc/xbmc_runner.py
+++ b/resources/lib/youtube_plugin/kodion/plugin/xbmc/xbmc_runner.py
@@ -8,10 +8,10 @@
See LICENSES/GPL-2.0-only for more information.
"""
-import xbmcgui
-import xbmcplugin
+from __future__ import absolute_import, division, unicode_literals
from ..abstract_provider_runner import AbstractProviderRunner
+from ...compatibility import xbmcgui, xbmcplugin
from ...exceptions import KodionException
from ...items import AudioItem, DirectoryItem, ImageItem, UriItem, VideoItem
from ...player import Playlist
diff --git a/resources/lib/youtube_plugin/kodion/runner.py b/resources/lib/youtube_plugin/kodion/runner.py
index 63ae7b367..d03d44d69 100644
--- a/resources/lib/youtube_plugin/kodion/runner.py
+++ b/resources/lib/youtube_plugin/kodion/runner.py
@@ -8,6 +8,8 @@
See LICENSES/GPL-2.0-only for more information.
"""
+from __future__ import absolute_import, division, unicode_literals
+
import copy
import timeit
diff --git a/resources/lib/youtube_plugin/kodion/service.py b/resources/lib/youtube_plugin/kodion/service.py
index 29d2a90e3..e50d59d71 100644
--- a/resources/lib/youtube_plugin/kodion/service.py
+++ b/resources/lib/youtube_plugin/kodion/service.py
@@ -8,6 +8,8 @@
See LICENSES/GPL-2.0-only for more information.
"""
+from __future__ import absolute_import, division, unicode_literals
+
from datetime import datetime
import time
diff --git a/resources/lib/youtube_plugin/kodion/settings/__init__.py b/resources/lib/youtube_plugin/kodion/settings/__init__.py
index 06ed1a6ba..d5432f4b3 100644
--- a/resources/lib/youtube_plugin/kodion/settings/__init__.py
+++ b/resources/lib/youtube_plugin/kodion/settings/__init__.py
@@ -7,6 +7,8 @@
See LICENSES/GPL-2.0-only for more information.
"""
+from __future__ import absolute_import, division, unicode_literals
+
from .xbmc.xbmc_plugin_settings import XbmcPluginSettings as Settings
diff --git a/resources/lib/youtube_plugin/kodion/settings/abstract_settings.py b/resources/lib/youtube_plugin/kodion/settings/abstract_settings.py
index 15a7d9b28..a0009b0ba 100644
--- a/resources/lib/youtube_plugin/kodion/settings/abstract_settings.py
+++ b/resources/lib/youtube_plugin/kodion/settings/abstract_settings.py
@@ -8,6 +8,8 @@
See LICENSES/GPL-2.0-only for more information.
"""
+from __future__ import absolute_import, division, unicode_literals
+
import sys
from ..constants import setting as SETTINGS
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 1cf235bc4..d19342c02 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
@@ -8,9 +8,10 @@
See LICENSES/GPL-2.0-only for more information.
"""
-import xbmcaddon
+from __future__ import absolute_import, division, unicode_literals
from ..abstract_settings import AbstractSettings
+from ...compatibility import xbmcaddon
from ...logger import log_debug
from ...utils.methods import get_kodi_setting
from ...utils.system_version import current_system_version
diff --git a/resources/lib/youtube_plugin/kodion/sql_store/data_cache.py b/resources/lib/youtube_plugin/kodion/sql_store/data_cache.py
index fb8ca4134..8eb571ce0 100644
--- a/resources/lib/youtube_plugin/kodion/sql_store/data_cache.py
+++ b/resources/lib/youtube_plugin/kodion/sql_store/data_cache.py
@@ -8,6 +8,8 @@
See LICENSES/GPL-2.0-only for more information.
"""
+from __future__ import absolute_import, division, unicode_literals
+
import json
from datetime import datetime
diff --git a/resources/lib/youtube_plugin/kodion/sql_store/favorite_list.py b/resources/lib/youtube_plugin/kodion/sql_store/favorite_list.py
index 11504727b..788ba1ff4 100644
--- a/resources/lib/youtube_plugin/kodion/sql_store/favorite_list.py
+++ b/resources/lib/youtube_plugin/kodion/sql_store/favorite_list.py
@@ -8,6 +8,8 @@
See LICENSES/GPL-2.0-only for more information.
"""
+from __future__ import absolute_import, division, unicode_literals
+
from .storage import Storage
from ..items import from_json, to_json
diff --git a/resources/lib/youtube_plugin/kodion/sql_store/function_cache.py b/resources/lib/youtube_plugin/kodion/sql_store/function_cache.py
index 7b71ad10a..91839a64b 100644
--- a/resources/lib/youtube_plugin/kodion/sql_store/function_cache.py
+++ b/resources/lib/youtube_plugin/kodion/sql_store/function_cache.py
@@ -8,6 +8,8 @@
See LICENSES/GPL-2.0-only for more information.
"""
+from __future__ import absolute_import, division, unicode_literals
+
from functools import partial
from hashlib import md5
diff --git a/resources/lib/youtube_plugin/kodion/sql_store/playback_history.py b/resources/lib/youtube_plugin/kodion/sql_store/playback_history.py
index 3acdb6400..0f9a263ad 100644
--- a/resources/lib/youtube_plugin/kodion/sql_store/playback_history.py
+++ b/resources/lib/youtube_plugin/kodion/sql_store/playback_history.py
@@ -7,6 +7,8 @@
See LICENSES/GPL-2.0-only for more information.
"""
+from __future__ import absolute_import, division, unicode_literals
+
from .storage import Storage
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 90c8d1cb9..bf83574ac 100644
--- a/resources/lib/youtube_plugin/kodion/sql_store/search_history.py
+++ b/resources/lib/youtube_plugin/kodion/sql_store/search_history.py
@@ -8,6 +8,8 @@
See LICENSES/GPL-2.0-only for more information.
"""
+from __future__ import absolute_import, division, unicode_literals
+
from hashlib import md5
from .storage import Storage
diff --git a/resources/lib/youtube_plugin/kodion/sql_store/storage.py b/resources/lib/youtube_plugin/kodion/sql_store/storage.py
index 0487ea00e..d8a41b7d1 100644
--- a/resources/lib/youtube_plugin/kodion/sql_store/storage.py
+++ b/resources/lib/youtube_plugin/kodion/sql_store/storage.py
@@ -8,6 +8,8 @@
See LICENSES/GPL-2.0-only for more information.
"""
+from __future__ import absolute_import, division, unicode_literals
+
import json
import os
import pickle
diff --git a/resources/lib/youtube_plugin/kodion/sql_store/watch_later_list.py b/resources/lib/youtube_plugin/kodion/sql_store/watch_later_list.py
index 5da49a5d3..cb8e39743 100644
--- a/resources/lib/youtube_plugin/kodion/sql_store/watch_later_list.py
+++ b/resources/lib/youtube_plugin/kodion/sql_store/watch_later_list.py
@@ -8,6 +8,8 @@
See LICENSES/GPL-2.0-only for more information.
"""
+from __future__ import absolute_import, division, unicode_literals
+
from datetime import datetime
from .storage import Storage
diff --git a/resources/lib/youtube_plugin/kodion/ui/__init__.py b/resources/lib/youtube_plugin/kodion/ui/__init__.py
index b173ebd14..8f09da745 100644
--- a/resources/lib/youtube_plugin/kodion/ui/__init__.py
+++ b/resources/lib/youtube_plugin/kodion/ui/__init__.py
@@ -7,6 +7,8 @@
See LICENSES/GPL-2.0-only for more information.
"""
+from __future__ import absolute_import, division, unicode_literals
+
from .xbmc.xbmc_context_ui import XbmcContextUI as ContextUI
diff --git a/resources/lib/youtube_plugin/kodion/ui/xbmc/info_labels.py b/resources/lib/youtube_plugin/kodion/ui/xbmc/info_labels.py
index b3d13fc9d..674d2f2bc 100644
--- a/resources/lib/youtube_plugin/kodion/ui/xbmc/info_labels.py
+++ b/resources/lib/youtube_plugin/kodion/ui/xbmc/info_labels.py
@@ -8,6 +8,8 @@
See LICENSES/GPL-2.0-only for more information.
"""
+from __future__ import absolute_import, division, unicode_literals
+
from ...items import AudioItem, DirectoryItem, ImageItem, VideoItem
from ...utils import current_system_version, datetime_parser
diff --git a/resources/lib/youtube_plugin/kodion/ui/xbmc/xbmc_context_ui.py b/resources/lib/youtube_plugin/kodion/ui/xbmc/xbmc_context_ui.py
index 954b3af18..b3ffb53a0 100644
--- a/resources/lib/youtube_plugin/kodion/ui/xbmc/xbmc_context_ui.py
+++ b/resources/lib/youtube_plugin/kodion/ui/xbmc/xbmc_context_ui.py
@@ -8,12 +8,12 @@
See LICENSES/GPL-2.0-only for more information.
"""
-import xbmc
-import xbmcgui
+from __future__ import absolute_import, division, unicode_literals
from .xbmc_progress_dialog import XbmcProgressDialog, XbmcProgressDialogBG
from ..abstract_context_ui import AbstractContextUI
from ... import utils
+from ...compatibility import xbmc, xbmcgui
class XbmcContextUI(AbstractContextUI):
diff --git a/resources/lib/youtube_plugin/kodion/ui/xbmc/xbmc_items.py b/resources/lib/youtube_plugin/kodion/ui/xbmc/xbmc_items.py
index 380c7868b..0d3a0e615 100644
--- a/resources/lib/youtube_plugin/kodion/ui/xbmc/xbmc_items.py
+++ b/resources/lib/youtube_plugin/kodion/ui/xbmc/xbmc_items.py
@@ -8,9 +8,10 @@
See LICENSES/GPL-2.0-only for more information.
"""
-from xbmcgui import ListItem
+from __future__ import absolute_import, division, unicode_literals
from . import info_labels
+from ...compatibility import xbmcgui
from ...items import AudioItem, UriItem, VideoItem
from ...utils import current_system_version, datetime_parser
@@ -35,7 +36,7 @@ def add_stream_info(self, *args, **kwargs):
def set_resume_point(self,
infoproperties,
resume_key='ResumeTime',
- total_key='TotalTime',):
+ total_key='TotalTime'):
if resume_key in infoproperties:
infoproperties[resume_key] = str(infoproperties[resume_key])
if total_key in infoproperties:
@@ -115,7 +116,7 @@ def video_playback_item(context, video_item):
if not alternative_player and headers and uri.startswith('http'):
video_item.set_uri('|'.join([uri, headers]))
- list_item = ListItem(**kwargs)
+ list_item = xbmcgui.ListItem(**kwargs)
if mime_type:
list_item.setContentLookup(False)
list_item.setMimeType(mime_type)
@@ -170,7 +171,7 @@ def audio_listitem(context, audio_item):
'ForceResolvePlugin': 'true',
}
- list_item = ListItem(**kwargs)
+ list_item = xbmcgui.ListItem(**kwargs)
fanart = (context.get_settings().show_fanart()
and audio_item.get_fanart()
@@ -206,7 +207,7 @@ def uri_listitem(context, uri_item):
'ForceResolvePlugin': 'true',
}
- list_item = ListItem(**kwargs)
+ list_item = xbmcgui.ListItem(**kwargs)
list_item.setProperties(props)
return list_item
@@ -226,7 +227,7 @@ def video_listitem(context, video_item):
'ForceResolvePlugin': 'true',
}
- list_item = ListItem(**kwargs)
+ list_item = xbmcgui.ListItem(**kwargs)
published_at = video_item.get_added_utc()
scheduled_start = video_item.get_scheduled_start_utc()
diff --git a/resources/lib/youtube_plugin/kodion/ui/xbmc/xbmc_progress_dialog.py b/resources/lib/youtube_plugin/kodion/ui/xbmc/xbmc_progress_dialog.py
index fda3cde27..596ada365 100644
--- a/resources/lib/youtube_plugin/kodion/ui/xbmc/xbmc_progress_dialog.py
+++ b/resources/lib/youtube_plugin/kodion/ui/xbmc/xbmc_progress_dialog.py
@@ -8,8 +8,10 @@
See LICENSES/GPL-2.0-only for more information.
"""
-import xbmcgui
+from __future__ import absolute_import, division, unicode_literals
+
from ..abstract_progress_dialog import AbstractProgressDialog
+from ...compatibility import xbmcgui
class XbmcProgressDialog(AbstractProgressDialog):
diff --git a/resources/lib/youtube_plugin/kodion/utils/__init__.py b/resources/lib/youtube_plugin/kodion/utils/__init__.py
index e586f8a59..19b0e80e7 100644
--- a/resources/lib/youtube_plugin/kodion/utils/__init__.py
+++ b/resources/lib/youtube_plugin/kodion/utils/__init__.py
@@ -8,6 +8,8 @@
See LICENSES/GPL-2.0-only for more information.
"""
+from __future__ import absolute_import, division, unicode_literals
+
from . import datetime_parser
from .methods import (
create_path,
diff --git a/resources/lib/youtube_plugin/kodion/utils/datetime_parser.py b/resources/lib/youtube_plugin/kodion/utils/datetime_parser.py
index 9890f15b9..83c0cf6f2 100644
--- a/resources/lib/youtube_plugin/kodion/utils/datetime_parser.py
+++ b/resources/lib/youtube_plugin/kodion/utils/datetime_parser.py
@@ -8,6 +8,8 @@
See LICENSES/GPL-2.0-only for more information.
"""
+from __future__ import absolute_import, division, unicode_literals
+
import re
import time
from datetime import date, datetime, time as dt_time, timedelta
diff --git a/resources/lib/youtube_plugin/kodion/utils/methods.py b/resources/lib/youtube_plugin/kodion/utils/methods.py
index adb6455e2..de4d03f9f 100644
--- a/resources/lib/youtube_plugin/kodion/utils/methods.py
+++ b/resources/lib/youtube_plugin/kodion/utils/methods.py
@@ -8,16 +8,16 @@
See LICENSES/GPL-2.0-only for more information.
"""
+from __future__ import absolute_import, division, unicode_literals
+
import copy
import json
import os
import re
from datetime import timedelta
from math import floor, log
-from urllib.parse import quote
-import xbmc
-import xbmcvfs
+from ..compatibility import quote, xbmc, xbmcvfs
__all__ = (
diff --git a/resources/lib/youtube_plugin/kodion/utils/monitor.py b/resources/lib/youtube_plugin/kodion/utils/monitor.py
index e25e22bdc..ecf05bb61 100644
--- a/resources/lib/youtube_plugin/kodion/utils/monitor.py
+++ b/resources/lib/youtube_plugin/kodion/utils/monitor.py
@@ -7,16 +7,14 @@
See LICENSES/GPL-2.0-only for more information.
"""
+from __future__ import absolute_import, division, unicode_literals
+
import json
import os
import shutil
import threading
-from urllib.parse import unquote
-
-import xbmc
-import xbmcvfs
-from xbmcaddon import Addon
+from ..compatibility import unquote, xbmc, xbmcaddon, xbmcvfs
from ..logger import log_debug
from ..network import get_http_server, is_httpd_live
from ..settings import Settings
@@ -24,7 +22,7 @@
class YouTubeMonitor(xbmc.Monitor):
_addon_id = 'plugin.video.youtube'
- _settings = Settings(Addon(_addon_id))
+ _settings = Settings(xbmcaddon.Addon(_addon_id))
# noinspection PyUnusedLocal,PyMissingConstructor
def __init__(self, *args, **kwargs):
@@ -88,7 +86,7 @@ def onNotification(self, sender, method, data):
.format(method=method))
def onSettingsChanged(self):
- self._settings.flush(Addon(self._addon_id))
+ self._settings.flush(xbmcaddon.Addon(self._addon_id))
data = {
'use_httpd': (self._settings.use_mpd_videos()
diff --git a/resources/lib/youtube_plugin/kodion/utils/player.py b/resources/lib/youtube_plugin/kodion/utils/player.py
index b2dcce605..830f299c6 100644
--- a/resources/lib/youtube_plugin/kodion/utils/player.py
+++ b/resources/lib/youtube_plugin/kodion/utils/player.py
@@ -7,11 +7,13 @@
See LICENSES/GPL-2.0-only for more information.
"""
+from __future__ import absolute_import, division, unicode_literals
+
import json
import re
import threading
-import xbmc
+from ..compatibility import xbmc
class PlaybackMonitorThread(threading.Thread):
diff --git a/resources/lib/youtube_plugin/kodion/utils/system_version.py b/resources/lib/youtube_plugin/kodion/utils/system_version.py
index 51e19a760..b758dacb5 100644
--- a/resources/lib/youtube_plugin/kodion/utils/system_version.py
+++ b/resources/lib/youtube_plugin/kodion/utils/system_version.py
@@ -8,9 +8,11 @@
See LICENSES/GPL-2.0-only for more information.
"""
+from __future__ import absolute_import, division, unicode_literals
+
import json
-import xbmc
+from ..compatibility import xbmc
class SystemVersion(object):
diff --git a/resources/lib/youtube_plugin/refresh.py b/resources/lib/youtube_plugin/refresh.py
index 15789ab5f..981231c3a 100644
--- a/resources/lib/youtube_plugin/refresh.py
+++ b/resources/lib/youtube_plugin/refresh.py
@@ -7,7 +7,10 @@
See LICENSES/GPL-2.0-only for more information.
"""
+from __future__ import absolute_import, division, unicode_literals
+
import xbmc
+
if __name__ == '__main__':
xbmc.executebuiltin("Container.Refresh")
diff --git a/resources/lib/youtube_plugin/youtube/__init__.py b/resources/lib/youtube_plugin/youtube/__init__.py
index 597a18f32..f1edbe0f9 100644
--- a/resources/lib/youtube_plugin/youtube/__init__.py
+++ b/resources/lib/youtube_plugin/youtube/__init__.py
@@ -8,6 +8,8 @@
See LICENSES/GPL-2.0-only for more information.
"""
+from __future__ import absolute_import, division, unicode_literals
+
from .provider import Provider
diff --git a/resources/lib/youtube_plugin/youtube/client/__config__.py b/resources/lib/youtube_plugin/youtube/client/__config__.py
index 239475da3..e6279c3f1 100644
--- a/resources/lib/youtube_plugin/youtube/client/__config__.py
+++ b/resources/lib/youtube_plugin/youtube/client/__config__.py
@@ -7,6 +7,8 @@
See LICENSES/GPL-2.0-only for more information.
"""
+from __future__ import absolute_import, division, unicode_literals
+
from base64 import b64decode
from hashlib import md5
diff --git a/resources/lib/youtube_plugin/youtube/client/__init__.py b/resources/lib/youtube_plugin/youtube/client/__init__.py
index a97c2a226..8a0c95b2d 100644
--- a/resources/lib/youtube_plugin/youtube/client/__init__.py
+++ b/resources/lib/youtube_plugin/youtube/client/__init__.py
@@ -8,6 +8,8 @@
See LICENSES/GPL-2.0-only for more information.
"""
+from __future__ import absolute_import, division, unicode_literals
+
from .youtube import YouTube
diff --git a/resources/lib/youtube_plugin/youtube/client/login_client.py b/resources/lib/youtube_plugin/youtube/client/login_client.py
index 70edd8319..cf37fcfac 100644
--- a/resources/lib/youtube_plugin/youtube/client/login_client.py
+++ b/resources/lib/youtube_plugin/youtube/client/login_client.py
@@ -8,8 +8,9 @@
See LICENSES/GPL-2.0-only for more information.
"""
+from __future__ import absolute_import, division, unicode_literals
+
import time
-from urllib.parse import parse_qsl
from requests.exceptions import InvalidJSONError
@@ -20,6 +21,7 @@
youtube_tv,
)
from .request_client import YouTubeRequestClient
+from ...kodion.compatibility import parse_qsl
from ...kodion.logger import log_debug
from ...youtube.youtube_exceptions import (
InvalidGrant,
diff --git a/resources/lib/youtube_plugin/youtube/client/request_client.py b/resources/lib/youtube_plugin/youtube/client/request_client.py
index 603bc3233..4bace16e9 100644
--- a/resources/lib/youtube_plugin/youtube/client/request_client.py
+++ b/resources/lib/youtube_plugin/youtube/client/request_client.py
@@ -7,6 +7,8 @@
See LICENSES/GPL-2.0-only for more information.
"""
+from __future__ import absolute_import, division, unicode_literals
+
from ...kodion.utils import merge_dicts
from ...kodion.network import BaseRequestsClass
from ...youtube.youtube_exceptions import YouTubeException
diff --git a/resources/lib/youtube_plugin/youtube/client/youtube.py b/resources/lib/youtube_plugin/youtube/client/youtube.py
index 88294de7c..b6874c97b 100644
--- a/resources/lib/youtube_plugin/youtube/client/youtube.py
+++ b/resources/lib/youtube_plugin/youtube/client/youtube.py
@@ -8,6 +8,8 @@
See LICENSES/GPL-2.0-only for more information.
"""
+from __future__ import absolute_import, division, unicode_literals
+
import copy
import json
import re
@@ -17,8 +19,8 @@
from .login_client import LoginClient
from ..helper.video_info import VideoInfo
from ...kodion import Context
-from ...kodion.utils import datetime_parser
-from ...kodion.utils import to_unicode
+from ...kodion.utils import datetime_parser, to_unicode
+
_context = Context(plugin_id='plugin.video.youtube')
diff --git a/resources/lib/youtube_plugin/youtube/helper/__init__.py b/resources/lib/youtube_plugin/youtube/helper/__init__.py
index 6e400683d..12b93382c 100644
--- a/resources/lib/youtube_plugin/youtube/helper/__init__.py
+++ b/resources/lib/youtube_plugin/youtube/helper/__init__.py
@@ -8,6 +8,8 @@
See LICENSES/GPL-2.0-only for more information.
"""
+from __future__ import absolute_import, division, unicode_literals
+
from .resource_manager import ResourceManager
from .url_resolver import UrlResolver
from .url_to_item_converter import UrlToItemConverter
diff --git a/resources/lib/youtube_plugin/youtube/helper/ratebypass/__init__.py b/resources/lib/youtube_plugin/youtube/helper/ratebypass/__init__.py
index 8afb79d19..2dcf59c7d 100644
--- a/resources/lib/youtube_plugin/youtube/helper/ratebypass/__init__.py
+++ b/resources/lib/youtube_plugin/youtube/helper/ratebypass/__init__.py
@@ -7,6 +7,8 @@
See LICENSES/GPL-2.0-only for more information.
"""
+from __future__ import absolute_import, division, unicode_literals
+
from ....youtube.helper.ratebypass import ratebypass
diff --git a/resources/lib/youtube_plugin/youtube/helper/ratebypass/ratebypass.py b/resources/lib/youtube_plugin/youtube/helper/ratebypass/ratebypass.py
index b3632a734..bd924f1a9 100644
--- a/resources/lib/youtube_plugin/youtube/helper/ratebypass/ratebypass.py
+++ b/resources/lib/youtube_plugin/youtube/helper/ratebypass/ratebypass.py
@@ -9,6 +9,8 @@
See LICENSES/GPL-2.0-only for more information.
"""
+from __future__ import absolute_import, division, unicode_literals
+
import re
try:
diff --git a/resources/lib/youtube_plugin/youtube/helper/resource_manager.py b/resources/lib/youtube_plugin/youtube/helper/resource_manager.py
index 0ec5e7e3e..fab7c216e 100644
--- a/resources/lib/youtube_plugin/youtube/helper/resource_manager.py
+++ b/resources/lib/youtube_plugin/youtube/helper/resource_manager.py
@@ -8,6 +8,8 @@
See LICENSES/GPL-2.0-only for more information.
"""
+from __future__ import absolute_import, division, unicode_literals
+
from ..youtube_exceptions import YouTubeException
from ...kodion.utils import strip_html_from_text
diff --git a/resources/lib/youtube_plugin/youtube/helper/signature/__init__.py b/resources/lib/youtube_plugin/youtube/helper/signature/__init__.py
index 886aaaa2e..4370d5ee9 100644
--- a/resources/lib/youtube_plugin/youtube/helper/signature/__init__.py
+++ b/resources/lib/youtube_plugin/youtube/helper/signature/__init__.py
@@ -8,6 +8,8 @@
See LICENSES/GPL-2.0-only for more information.
"""
+from __future__ import absolute_import, division, unicode_literals
+
from ....youtube.helper.signature.cipher import Cipher
diff --git a/resources/lib/youtube_plugin/youtube/helper/signature/cipher.py b/resources/lib/youtube_plugin/youtube/helper/signature/cipher.py
index fa52385c6..b031daf16 100644
--- a/resources/lib/youtube_plugin/youtube/helper/signature/cipher.py
+++ b/resources/lib/youtube_plugin/youtube/helper/signature/cipher.py
@@ -8,6 +8,8 @@
See LICENSES/GPL-2.0-only for more information.
"""
+from __future__ import absolute_import, division, unicode_literals
+
import re
from .json_script_engine import JsonScriptEngine
diff --git a/resources/lib/youtube_plugin/youtube/helper/subtitles.py b/resources/lib/youtube_plugin/youtube/helper/subtitles.py
index 48019112c..70ed8a8bb 100644
--- a/resources/lib/youtube_plugin/youtube/helper/subtitles.py
+++ b/resources/lib/youtube_plugin/youtube/helper/subtitles.py
@@ -6,11 +6,17 @@
See LICENSES/GPL-2.0-only for more information.
"""
-from html import unescape
-from urllib.parse import parse_qs, urlsplit, urlunsplit, urlencode, urljoin
-
-import xbmcvfs
-
+from __future__ import absolute_import, division, unicode_literals
+
+from ...kodion.compatibility import (
+ parse_qs,
+ unescape,
+ urlencode,
+ urljoin,
+ urlsplit,
+ urlunsplit,
+ xbmcvfs,
+)
from ...kodion.network import BaseRequestsClass
from ...kodion.utils import make_dirs
diff --git a/resources/lib/youtube_plugin/youtube/helper/tv.py b/resources/lib/youtube_plugin/youtube/helper/tv.py
index c280beb54..0d5541ff7 100644
--- a/resources/lib/youtube_plugin/youtube/helper/tv.py
+++ b/resources/lib/youtube_plugin/youtube/helper/tv.py
@@ -8,6 +8,8 @@
See LICENSES/GPL-2.0-only for more information.
"""
+from __future__ import absolute_import, division, unicode_literals
+
from ...kodion.items import DirectoryItem, NextPageItem, VideoItem
from ...youtube.helper import utils
diff --git a/resources/lib/youtube_plugin/youtube/helper/url_resolver.py b/resources/lib/youtube_plugin/youtube/helper/url_resolver.py
index 0a28d1f93..08e8aceeb 100644
--- a/resources/lib/youtube_plugin/youtube/helper/url_resolver.py
+++ b/resources/lib/youtube_plugin/youtube/helper/url_resolver.py
@@ -8,10 +8,11 @@
See LICENSES/GPL-2.0-only for more information.
"""
+from __future__ import absolute_import, division, unicode_literals
+
import re
-from html import unescape
-from urllib.parse import parse_qsl, urlencode, urlparse
+from ...kodion.compatibility import parse_qsl, unescape, urlencode, urlparse
from ...kodion.network import BaseRequestsClass
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 dbe3f50be..c03177a64 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
@@ -8,10 +8,12 @@
See LICENSES/GPL-2.0-only for more information.
"""
+from __future__ import absolute_import, division, unicode_literals
+
import re
-from urllib.parse import parse_qsl, urlparse
from . import utils
+from ...kodion.compatibility import parse_qsl, urlparse
from ...kodion.items import DirectoryItem, UriItem, VideoItem
from ...kodion.utils import duration_to_seconds
diff --git a/resources/lib/youtube_plugin/youtube/helper/utils.py b/resources/lib/youtube_plugin/youtube/helper/utils.py
index c7a3d3acf..24e503383 100644
--- a/resources/lib/youtube_plugin/youtube/helper/utils.py
+++ b/resources/lib/youtube_plugin/youtube/helper/utils.py
@@ -8,6 +8,8 @@
See LICENSES/GPL-2.0-only for more information.
"""
+from __future__ import absolute_import, division, unicode_literals
+
import re
import time
from math import log10
diff --git a/resources/lib/youtube_plugin/youtube/helper/v3.py b/resources/lib/youtube_plugin/youtube/helper/v3.py
index 6fe14423b..1fa2c409b 100644
--- a/resources/lib/youtube_plugin/youtube/helper/v3.py
+++ b/resources/lib/youtube_plugin/youtube/helper/v3.py
@@ -8,6 +8,8 @@
See LICENSES/GPL-2.0-only for more information.
"""
+from __future__ import absolute_import, division, unicode_literals
+
from ...youtube.helper import yt_context_menu
from ... import kodion
from ...kodion import items
diff --git a/resources/lib/youtube_plugin/youtube/helper/video_info.py b/resources/lib/youtube_plugin/youtube/helper/video_info.py
index 12c9d50bb..80b91a6d6 100644
--- a/resources/lib/youtube_plugin/youtube/helper/video_info.py
+++ b/resources/lib/youtube_plugin/youtube/helper/video_info.py
@@ -8,29 +8,29 @@
See LICENSES/GPL-2.0-only for more information.
"""
+from __future__ import absolute_import, division, unicode_literals
+
import random
import re
import traceback
-
-from html import unescape
from json import dumps as json_dumps, loads as json_loads
-from urllib.parse import (
+
+from .ratebypass import ratebypass
+from .signature.cipher import Cipher
+from .subtitles import Subtitles
+from ..client.request_client import YouTubeRequestClient
+from ..youtube_exceptions import YouTubeException
+from ...kodion.compatibility import (
parse_qs,
quote,
+ unescape,
unquote,
urlencode,
urljoin,
urlsplit,
urlunsplit,
+ xbmcvfs,
)
-
-import xbmcvfs
-
-from .ratebypass import ratebypass
-from .signature.cipher import Cipher
-from .subtitles import Subtitles
-from ..client.request_client import YouTubeRequestClient
-from ..youtube_exceptions import YouTubeException
from ...kodion.network import is_httpd_live
from ...kodion.utils import make_dirs
diff --git a/resources/lib/youtube_plugin/youtube/helper/yt_context_menu.py b/resources/lib/youtube_plugin/youtube/helper/yt_context_menu.py
index d35080dd6..3159ebc49 100644
--- a/resources/lib/youtube_plugin/youtube/helper/yt_context_menu.py
+++ b/resources/lib/youtube_plugin/youtube/helper/yt_context_menu.py
@@ -8,6 +8,8 @@
See LICENSES/GPL-2.0-only for more information.
"""
+from __future__ import absolute_import, division, unicode_literals
+
from ... import kodion
diff --git a/resources/lib/youtube_plugin/youtube/helper/yt_login.py b/resources/lib/youtube_plugin/youtube/helper/yt_login.py
index fc96c5662..81bcece3c 100644
--- a/resources/lib/youtube_plugin/youtube/helper/yt_login.py
+++ b/resources/lib/youtube_plugin/youtube/helper/yt_login.py
@@ -8,6 +8,8 @@
See LICENSES/GPL-2.0-only for more information.
"""
+from __future__ import absolute_import, division, unicode_literals
+
import copy
import json
import time
diff --git a/resources/lib/youtube_plugin/youtube/helper/yt_old_actions.py b/resources/lib/youtube_plugin/youtube/helper/yt_old_actions.py
index a380e982c..0ed81368e 100644
--- a/resources/lib/youtube_plugin/youtube/helper/yt_old_actions.py
+++ b/resources/lib/youtube_plugin/youtube/helper/yt_old_actions.py
@@ -8,6 +8,8 @@
See LICENSES/GPL-2.0-only for more information.
"""
+from __future__ import absolute_import, division, unicode_literals
+
from ... import kodion
diff --git a/resources/lib/youtube_plugin/youtube/helper/yt_play.py b/resources/lib/youtube_plugin/youtube/helper/yt_play.py
index 07d22c2c4..88a327afb 100644
--- a/resources/lib/youtube_plugin/youtube/helper/yt_play.py
+++ b/resources/lib/youtube_plugin/youtube/helper/yt_play.py
@@ -8,6 +8,8 @@
See LICENSES/GPL-2.0-only for more information.
"""
+from __future__ import absolute_import, division, unicode_literals
+
import json
import random
import traceback
diff --git a/resources/lib/youtube_plugin/youtube/helper/yt_playlist.py b/resources/lib/youtube_plugin/youtube/helper/yt_playlist.py
index 4d639a6d4..c0dfa676d 100644
--- a/resources/lib/youtube_plugin/youtube/helper/yt_playlist.py
+++ b/resources/lib/youtube_plugin/youtube/helper/yt_playlist.py
@@ -8,6 +8,8 @@
See LICENSES/GPL-2.0-only for more information.
"""
+from __future__ import absolute_import, division, unicode_literals
+
from ... import kodion
from ...youtube.helper import v3
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 372097040..e6bdda954 100644
--- a/resources/lib/youtube_plugin/youtube/helper/yt_setup_wizard.py
+++ b/resources/lib/youtube_plugin/youtube/helper/yt_setup_wizard.py
@@ -8,6 +8,8 @@
See LICENSES/GPL-2.0-only for more information.
"""
+from __future__ import absolute_import, division, unicode_literals
+
from ...kodion.network import Locator
diff --git a/resources/lib/youtube_plugin/youtube/helper/yt_specials.py b/resources/lib/youtube_plugin/youtube/helper/yt_specials.py
index 77f9bfe72..6b143dd51 100644
--- a/resources/lib/youtube_plugin/youtube/helper/yt_specials.py
+++ b/resources/lib/youtube_plugin/youtube/helper/yt_specials.py
@@ -8,6 +8,8 @@
See LICENSES/GPL-2.0-only for more information.
"""
+from __future__ import absolute_import, division, unicode_literals
+
from . import utils
from ...kodion import KodionException, constants
from ...kodion.items import DirectoryItem, UriItem
diff --git a/resources/lib/youtube_plugin/youtube/helper/yt_subscriptions.py b/resources/lib/youtube_plugin/youtube/helper/yt_subscriptions.py
index fd01629bd..24da6925c 100644
--- a/resources/lib/youtube_plugin/youtube/helper/yt_subscriptions.py
+++ b/resources/lib/youtube_plugin/youtube/helper/yt_subscriptions.py
@@ -8,6 +8,8 @@
See LICENSES/GPL-2.0-only for more information.
"""
+from __future__ import absolute_import, division, unicode_literals
+
from ...kodion.items import UriItem
from ... import kodion
from ...youtube.helper import v3
diff --git a/resources/lib/youtube_plugin/youtube/helper/yt_video.py b/resources/lib/youtube_plugin/youtube/helper/yt_video.py
index ecc4a32d5..699263a9e 100644
--- a/resources/lib/youtube_plugin/youtube/helper/yt_video.py
+++ b/resources/lib/youtube_plugin/youtube/helper/yt_video.py
@@ -8,6 +8,8 @@
See LICENSES/GPL-2.0-only for more information.
"""
+from __future__ import absolute_import, division, unicode_literals
+
from ... import kodion
from ...youtube.helper import v3
diff --git a/resources/lib/youtube_plugin/youtube/provider.py b/resources/lib/youtube_plugin/youtube/provider.py
index 777fd3812..d4fbb8039 100644
--- a/resources/lib/youtube_plugin/youtube/provider.py
+++ b/resources/lib/youtube_plugin/youtube/provider.py
@@ -8,6 +8,8 @@
See LICENSES/GPL-2.0-only for more information.
"""
+from __future__ import absolute_import, division, unicode_literals
+
import json
import os
import re
@@ -15,9 +17,6 @@
import socket
from base64 import b64decode
-import xbmcaddon
-import xbmcvfs
-
from .helper import (
ResourceManager,
UrlResolver,
@@ -34,6 +33,7 @@
)
from .youtube_exceptions import InvalidGrant, LoginException
from ..kodion import (AbstractProvider, RegisterProviderPath, constants)
+from ..kodion.compatibility import xbmcaddon, xbmcvfs
from ..kodion.items import DirectoryItem, NewSearchItem, SearchItem
from ..kodion.network import get_client_ip_address, is_httpd_live
from ..kodion.utils import find_video_id, strip_html_from_text
diff --git a/resources/lib/youtube_plugin/youtube/youtube_exceptions.py b/resources/lib/youtube_plugin/youtube/youtube_exceptions.py
index 1e7cda824..fe4e24f91 100644
--- a/resources/lib/youtube_plugin/youtube/youtube_exceptions.py
+++ b/resources/lib/youtube_plugin/youtube/youtube_exceptions.py
@@ -8,6 +8,8 @@
See LICENSES/GPL-2.0-only for more information.
"""
+from __future__ import absolute_import, division, unicode_literals
+
from ..kodion import KodionException
diff --git a/resources/lib/youtube_registration.py b/resources/lib/youtube_registration.py
index 429420ba0..548be86bb 100644
--- a/resources/lib/youtube_registration.py
+++ b/resources/lib/youtube_registration.py
@@ -7,6 +7,8 @@
See LICENSES/GPL-2.0-only for more information.
"""
+from __future__ import absolute_import, division, unicode_literals
+
from base64 import b64encode
from youtube_plugin.kodion.json_store import APIKeyStore
from youtube_plugin.kodion.context import Context
diff --git a/resources/lib/youtube_requests.py b/resources/lib/youtube_requests.py
index 7f427348e..b25204c74 100644
--- a/resources/lib/youtube_requests.py
+++ b/resources/lib/youtube_requests.py
@@ -7,6 +7,8 @@
See LICENSES/GPL-2.0-only for more information.
"""
+from __future__ import absolute_import, division, unicode_literals
+
import re
from youtube_plugin.youtube.provider import Provider
diff --git a/resources/lib/youtube_resolver.py b/resources/lib/youtube_resolver.py
index 69d5d411c..53c771a3e 100644
--- a/resources/lib/youtube_resolver.py
+++ b/resources/lib/youtube_resolver.py
@@ -7,6 +7,8 @@
See LICENSES/GPL-2.0-only for more information.
"""
+from __future__ import absolute_import, division, unicode_literals
+
import re
from youtube_plugin.youtube.provider import Provider
From 64a942c8f4d55529f2bbce66108f6e27d1c21d1b Mon Sep 17 00:00:00 2001
From: MoojMidge <56883549+MoojMidge@users.noreply.github.com>
Date: Mon, 11 Dec 2023 23:48:13 +1100
Subject: [PATCH 085/141] Prevent possible memory leak and/or freeze on exit
---
.../kodion/json_store/json_store.py | 4 +-
.../kodion/network/http_server.py | 8 ++-
.../kodion/settings/abstract_settings.py | 3 +-
.../settings/xbmc/xbmc_plugin_settings.py | 58 +++++++++----------
4 files changed, 38 insertions(+), 35 deletions(-)
diff --git a/resources/lib/youtube_plugin/kodion/json_store/json_store.py b/resources/lib/youtube_plugin/kodion/json_store/json_store.py
index b6dea6368..cf39c9e78 100644
--- a/resources/lib/youtube_plugin/kodion/json_store/json_store.py
+++ b/resources/lib/youtube_plugin/kodion/json_store/json_store.py
@@ -20,11 +20,13 @@
_addon_id = 'plugin.video.youtube'
_addon = xbmcaddon.Addon(_addon_id)
+_addon_data_path = _addon.getAddonInfo('profile')
+del _addon
class JSONStore(object):
def __init__(self, filename):
- self.base_path = xbmcvfs.translatePath(_addon.getAddonInfo('profile'))
+ self.base_path = xbmcvfs.translatePath(_addon_data_path)
if not xbmcvfs.exists(self.base_path) and not make_dirs(self.base_path):
log_error('JSONStore.__init__ |{path}| invalid path'.format(
diff --git a/resources/lib/youtube_plugin/kodion/network/http_server.py b/resources/lib/youtube_plugin/kodion/network/http_server.py
index ea06ddff7..cd48e76d9 100644
--- a/resources/lib/youtube_plugin/kodion/network/http_server.py
+++ b/resources/lib/youtube_plugin/kodion/network/http_server.py
@@ -34,6 +34,10 @@
_addon = xbmcaddon.Addon(_addon_id)
_settings = Settings(_addon)
_i18n = _addon.getLocalizedString
+_addon_name = _addon.getAddonInfo('name')
+_addon_icon = _addon.getAddonInfo('icon')
+del _addon
+
_server_requests = BaseRequestsClass()
@@ -532,9 +536,9 @@ def get_http_server(address=None, port=None):
except socket_error as e:
log_debug('HTTPServer: Failed to start |{address}:{port}| |{response}|'
.format(address=address, port=port, response=str(e)))
- xbmcgui.Dialog().notification(_addon.getAddonInfo('name'),
+ xbmcgui.Dialog().notification(_addon_name,
str(e),
- _addon.getAddonInfo('icon'),
+ _addon_icon,
time=5000,
sound=False)
return None
diff --git a/resources/lib/youtube_plugin/kodion/settings/abstract_settings.py b/resources/lib/youtube_plugin/kodion/settings/abstract_settings.py
index a0009b0ba..527ab40ff 100644
--- a/resources/lib/youtube_plugin/kodion/settings/abstract_settings.py
+++ b/resources/lib/youtube_plugin/kodion/settings/abstract_settings.py
@@ -23,8 +23,7 @@ class AbstractSettings(object):
_echo = False
_cache = {}
- _funcs = {}
- _store = None
+ _type = None
@classmethod
def flush(cls, xbmc_addon):
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 d19342c02..577d72bb2 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
@@ -23,20 +23,18 @@ def __init__(self, xbmc_addon):
self.flush(xbmc_addon)
- if self._funcs:
- return
if current_system_version.compatible(20, 0):
_class = xbmcaddon.Settings
- self._funcs.update({
- 'get_bool': _class.getBool,
- 'set_bool': _class.setBool,
- 'get_int': _class.getInt,
- 'set_int': _class.setInt,
- 'get_str': _class.getString,
- 'set_str': _class.setString,
- 'get_str_list': _class.getStringList,
- 'set_str_list': _class.setStringList,
+ self.__dict__.update({
+ '_get_bool': _class.getBool,
+ '_set_bool': _class.setBool,
+ '_get_int': _class.getInt,
+ '_set_int': _class.setInt,
+ '_get_str': _class.getString,
+ '_set_str': _class.setString,
+ '_get_str_list': _class.getStringList,
+ '_set_str_list': _class.setStringList,
})
else:
_class = xbmcaddon.Addon
@@ -48,15 +46,15 @@ def _set_string_list(store, setting, value):
value = ','.join(value)
return _class.setSetting(store, setting, value)
- self._funcs.update({
- 'get_bool': _class.getSettingBool,
- 'set_bool': _class.setSettingBool,
- 'get_int': _class.getSettingInt,
- 'set_int': _class.setSettingInt,
- 'get_str': _class.getSettingString,
- 'set_str': _class.setSettingString,
- 'get_str_list': _get_string_list,
- 'set_str_list': _set_string_list,
+ self.__dict__.update({
+ '_get_bool': _class.getSettingBool,
+ '_set_bool': _class.setSettingBool,
+ '_get_int': _class.getSettingInt,
+ '_set_int': _class.setSettingInt,
+ '_get_str': _class.getSettingString,
+ '_set_str': _class.setSettingString,
+ '_get_str_list': _get_string_list,
+ '_set_str_list': _set_string_list,
})
@classmethod
@@ -64,9 +62,9 @@ def flush(cls, xbmc_addon):
cls._echo = get_kodi_setting('debug.showloginfo')
cls._cache = {}
if current_system_version.compatible(20, 0):
- cls._store = xbmc_addon.getSettings()
+ cls._type = xbmc_addon.getSettings
else:
- cls._store = xbmc_addon
+ cls._type = xbmcaddon.Addon
def get_bool(self, setting, default=None, echo=None):
if setting in self._cache:
@@ -74,7 +72,7 @@ def get_bool(self, setting, default=None, echo=None):
error = False
try:
- value = bool(self._funcs['get_bool'](self._store, setting))
+ value = bool(self._get_bool(self._type(), setting))
except (AttributeError, TypeError) as ex:
error = ex
value = self.get_string(setting, echo=False)
@@ -94,7 +92,7 @@ def get_bool(self, setting, default=None, echo=None):
def set_bool(self, setting, value, echo=None):
try:
- error = not self._funcs['set_bool'](self._store, setting, value)
+ error = not self._set_bool(self._type(), setting, value)
if not error:
self._cache[setting] = value
except RuntimeError as ex:
@@ -114,7 +112,7 @@ def get_int(self, setting, default=-1, process=None, echo=None):
error = False
try:
- value = int(self._funcs['get_int'](self._store, setting))
+ value = int(self._get_int(self._type(), setting))
if process:
value = process(value)
except (AttributeError, TypeError, ValueError) as ex:
@@ -140,7 +138,7 @@ def get_int(self, setting, default=-1, process=None, echo=None):
def set_int(self, setting, value, echo=None):
try:
- error = not self._funcs['set_int'](self._store, setting, value)
+ error = not self._set_int(self._type(), setting, value)
if not error:
self._cache[setting] = value
except RuntimeError as ex:
@@ -160,7 +158,7 @@ def get_string(self, setting, default='', echo=None):
error = False
try:
- value = self._funcs['get_str'](self._store, setting) or default
+ value = self._get_str(self._type(), setting) or default
except RuntimeError as ex:
error = ex
value = default
@@ -176,7 +174,7 @@ def get_string(self, setting, default='', echo=None):
def set_string(self, setting, value, echo=None):
try:
- error = not self._funcs['set_str'](self._store, setting, value)
+ error = not self._set_str(self._type(), setting, value)
if not error:
self._cache[setting] = value
except RuntimeError as ex:
@@ -196,7 +194,7 @@ def get_string_list(self, setting, default=None, echo=None):
error = False
try:
- value = self._funcs['get_str_list'](self._store, setting)
+ value = self._get_str_list(self._type(), setting)
if not value:
value = [] if default is None else default
except RuntimeError as ex:
@@ -214,7 +212,7 @@ def get_string_list(self, setting, default=None, echo=None):
def set_string_list(self, setting, value, echo=None):
try:
- error = not self._funcs['set_str_list'](self._store, setting, value)
+ error = not self._set_str_list(self._type(), setting, value)
if not error:
self._cache[setting] = value
except RuntimeError as ex:
From f9113905c81a1dca0a4a1916fcbdf69b62cfb696 Mon Sep 17 00:00:00 2001
From: MoojMidge <56883549+MoojMidge@users.noreply.github.com>
Date: Mon, 11 Dec 2023 23:49:01 +1100
Subject: [PATCH 086/141] Fix incorrect string id
---
resources/lib/youtube_plugin/youtube/helper/yt_setup_wizard.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
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 e6bdda954..39f0695f9 100644
--- a/resources/lib/youtube_plugin/youtube/helper/yt_setup_wizard.py
+++ b/resources/lib/youtube_plugin/youtube/helper/yt_setup_wizard.py
@@ -108,7 +108,7 @@ def _process_language(provider, context):
def _process_geo_location(context):
- if not context.get_ui().on_yes_no_input(context.get_name(), context.localize('perform.geolocation')):
+ if not context.get_ui().on_yes_no_input(context.get_name(), context.localize('perform_geolocation')):
return
locator = Locator()
From b7ae94c07b1785a9a81137fc5a12466f42899474 Mon Sep 17 00:00:00 2001
From: MoojMidge <56883549+MoojMidge@users.noreply.github.com>
Date: Mon, 11 Dec 2023 23:52:30 +1100
Subject: [PATCH 087/141] Remove need for urlparse and urlunsplit usage
- Replace urlparse usage with urlsplit instead
- Replace urlunsplit with _replace().geturl()
---
.../youtube_plugin/kodion/compatibility/__init__.py | 6 ------
.../kodion/context/xbmc/xbmc_context.py | 4 ++--
.../youtube_plugin/kodion/network/http_server.py | 4 ++--
.../lib/youtube_plugin/youtube/helper/subtitles.py | 13 +++++--------
.../youtube_plugin/youtube/helper/url_resolver.py | 10 +++++-----
.../youtube/helper/url_to_item_converter.py | 4 ++--
.../lib/youtube_plugin/youtube/helper/video_info.py | 7 +------
7 files changed, 17 insertions(+), 31 deletions(-)
diff --git a/resources/lib/youtube_plugin/kodion/compatibility/__init__.py b/resources/lib/youtube_plugin/kodion/compatibility/__init__.py
index 904da9afc..b8cc23a3f 100644
--- a/resources/lib/youtube_plugin/kodion/compatibility/__init__.py
+++ b/resources/lib/youtube_plugin/kodion/compatibility/__init__.py
@@ -17,9 +17,7 @@
unquote,
urlencode,
urljoin,
- urlparse,
urlsplit,
- urlunsplit,
)
import xbmc
@@ -40,9 +38,7 @@
parse_qs,
parse_qsl,
urljoin,
- urlparse,
urlsplit,
- urlunsplit,
)
from xml.sax.saxutils import unescape
@@ -105,9 +101,7 @@ def _file_closer(*args, **kwargs):
'unquote',
'urlencode',
'urljoin',
- 'urlparse',
'urlsplit',
- 'urlunsplit',
'xbmc',
'xbmcaddon',
'xbmcgui',
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 65e652b4f..6ba3d588d 100644
--- a/resources/lib/youtube_plugin/kodion/context/xbmc/xbmc_context.py
+++ b/resources/lib/youtube_plugin/kodion/context/xbmc/xbmc_context.py
@@ -20,7 +20,7 @@
parse_qsl,
quote,
unquote,
- urlparse,
+ urlsplit,
xbmc,
xbmcaddon,
xbmcplugin,
@@ -264,7 +264,7 @@ def __init__(self, path='/', params=None, plugin_name='', plugin_id='', override
# first the path of the uri
if override:
self._uri = sys.argv[0]
- parsed_url = urlparse(self._uri)
+ parsed_url = urlsplit(self._uri)
self._path = unquote(parsed_url.path)
# after that try to get the params
diff --git a/resources/lib/youtube_plugin/kodion/network/http_server.py b/resources/lib/youtube_plugin/kodion/network/http_server.py
index cd48e76d9..48836c372 100644
--- a/resources/lib/youtube_plugin/kodion/network/http_server.py
+++ b/resources/lib/youtube_plugin/kodion/network/http_server.py
@@ -20,7 +20,7 @@
from ..compatibility import (
BaseHTTPServer,
parse_qs,
- urlparse,
+ urlsplit,
xbmc,
xbmcaddon,
xbmcgui,
@@ -134,7 +134,7 @@ def do_GET(self):
elif api_config_enabled and stripped_path.startswith('/api_submit'):
xbmc.executebuiltin('Dialog.Close(addonsettings, true)')
- query = urlparse(self.path).query
+ query = urlsplit(self.path).query
params = parse_qs(query)
updated = []
diff --git a/resources/lib/youtube_plugin/youtube/helper/subtitles.py b/resources/lib/youtube_plugin/youtube/helper/subtitles.py
index 70ed8a8bb..485d538e5 100644
--- a/resources/lib/youtube_plugin/youtube/helper/subtitles.py
+++ b/resources/lib/youtube_plugin/youtube/helper/subtitles.py
@@ -14,7 +14,6 @@
urlencode,
urljoin,
urlsplit,
- urlunsplit,
xbmcvfs,
)
from ...kodion.network import BaseRequestsClass
@@ -306,18 +305,16 @@ def _set_query_param(url, *pairs):
else:
return url
- scheme, netloc, path, query_string, fragment = urlsplit(url)
- query_params = parse_qs(query_string)
+ components = urlsplit(url)
+ query_params = parse_qs(components.query)
for name, value in pairs:
if name:
query_params[name] = [value]
- new_query_string = urlencode(query_params, doseq=True)
- if isinstance(scheme, bytes):
- new_query_string = new_query_string.encode('utf-8')
-
- return urlunsplit((scheme, netloc, path, new_query_string, fragment))
+ return components._replace(
+ query=urlencode(query_params, doseq=True)
+ ).geturl()
@staticmethod
def _normalize_url(url):
diff --git a/resources/lib/youtube_plugin/youtube/helper/url_resolver.py b/resources/lib/youtube_plugin/youtube/helper/url_resolver.py
index 08e8aceeb..a6d94cfa8 100644
--- a/resources/lib/youtube_plugin/youtube/helper/url_resolver.py
+++ b/resources/lib/youtube_plugin/youtube/helper/url_resolver.py
@@ -12,7 +12,7 @@
import re
-from ...kodion.compatibility import parse_qsl, unescape, urlencode, urlparse
+from ...kodion.compatibility import parse_qsl, unescape, urlencode, urlsplit
from ...kodion.network import BaseRequestsClass
@@ -111,7 +111,7 @@ def resolve(self, url, url_components, method='HEAD'):
# top-level query string
params = dict(parse_qsl(url_components.query))
# components of next_url
- next_components = urlparse(params.pop('next_url', ''))
+ next_components = urlsplit(params.pop('next_url', ''))
if not next_components.scheme or not next_components.netloc:
return url
# query string encoded inside next_url
@@ -141,7 +141,7 @@ def resolve(self, url, url_components, method='HEAD'):
url = matches['video_url']
if url:
num_matched += 1
- url_components = urlparse(unescape(url))
+ url_components = urlsplit(unescape(url))
params = dict(parse_qsl(url_components.query))
if not num_matched & 2:
@@ -174,7 +174,7 @@ def resolve(self, url, url_components, method='HEAD'):
if match:
url = match.group('channel_url')
if path.endswith(('/live', '/streams')):
- url_components = urlparse(unescape(url))
+ url_components = urlsplit(unescape(url))
params = dict(parse_qsl(url_components.query))
params['live'] = 1
return url_components._replace(
@@ -223,7 +223,7 @@ def _resolve(self, url):
resolved_url = url
for resolver_name in self._resolvers:
resolver = self._resolver_map[resolver_name]
- url_components = urlparse(resolved_url)
+ url_components = urlsplit(resolved_url)
method = resolver.supports_url(resolved_url, url_components)
if not method:
continue
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 c03177a64..c81aef0eb 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
@@ -13,7 +13,7 @@
import re
from . import utils
-from ...kodion.compatibility import parse_qsl, urlparse
+from ...kodion.compatibility import parse_qsl, urlsplit
from ...kodion.items import DirectoryItem, UriItem, VideoItem
from ...kodion.utils import duration_to_seconds
@@ -41,7 +41,7 @@ def __init__(self, flatten=True):
self._channel_ids = []
def add_url(self, url, provider, context):
- parsed_url = urlparse(url)
+ parsed_url = urlsplit(url)
if parsed_url.hostname.lower() not in self.VALID_HOSTNAMES:
context.log_debug('Unknown hostname "{0}" in url "{1}"'.format(
parsed_url.hostname, url
diff --git a/resources/lib/youtube_plugin/youtube/helper/video_info.py b/resources/lib/youtube_plugin/youtube/helper/video_info.py
index 80b91a6d6..2326c5f1b 100644
--- a/resources/lib/youtube_plugin/youtube/helper/video_info.py
+++ b/resources/lib/youtube_plugin/youtube/helper/video_info.py
@@ -28,7 +28,6 @@
urlencode,
urljoin,
urlsplit,
- urlunsplit,
xbmcvfs,
)
from ...kodion.network import is_httpd_live
@@ -980,11 +979,7 @@ def _process_url_params(self, url):
elif not update_url:
return url
- return urlunsplit((parts.scheme,
- parts.netloc,
- parts.path,
- urlencode(query, doseq=True),
- parts.fragment))
+ return parts._replace(query=urlencode(query, doseq=True)).geturl()
def _get_error_details(self, playability_status, details=None):
if not playability_status:
From 54bdc195cf46284905c62b4ec7e36262a17264b6 Mon Sep 17 00:00:00 2001
From: MoojMidge <56883549+MoojMidge@users.noreply.github.com>
Date: Mon, 11 Dec 2023 23:54:50 +1100
Subject: [PATCH 088/141] Import tidy up
---
.../kodion/context/abstract_context.py | 2 +-
.../youtube_plugin/youtube/helper/utils.py | 38 +++++++++++--------
.../lib/youtube_plugin/youtube/helper/v3.py | 4 +-
3 files changed, 25 insertions(+), 19 deletions(-)
diff --git a/resources/lib/youtube_plugin/kodion/context/abstract_context.py b/resources/lib/youtube_plugin/kodion/context/abstract_context.py
index 765cc61e4..576384437 100644
--- a/resources/lib/youtube_plugin/kodion/context/abstract_context.py
+++ b/resources/lib/youtube_plugin/kodion/context/abstract_context.py
@@ -23,7 +23,7 @@
SearchHistory,
WatchLaterList,
)
-from ..utils import (create_path, create_uri_path, current_system_version)
+from ..utils import create_path, create_uri_path, current_system_version
class AbstractContext(object):
diff --git a/resources/lib/youtube_plugin/youtube/helper/utils.py b/resources/lib/youtube_plugin/youtube/helper/utils.py
index 24e503383..c3104520a 100644
--- a/resources/lib/youtube_plugin/youtube/helper/utils.py
+++ b/resources/lib/youtube_plugin/youtube/helper/utils.py
@@ -14,10 +14,16 @@
import time
from math import log10
-from ...kodion import utils
from ...kodion.items import DirectoryItem
+from ...kodion.utils import (
+ create_path,
+ datetime_parser,
+ friendly_number,
+ strip_html_from_text,
+)
from ...youtube.helper import yt_context_menu
+
try:
from inputstreamhelper import Helper as ISHelper
except ImportError:
@@ -71,7 +77,7 @@ def make_comment_item(context, snippet, uri, total_replies=0):
like_count = snippet['likeCount']
if like_count:
- like_count, _ = utils.friendly_number(like_count)
+ like_count, _ = friendly_number(like_count)
color = __COLOR_MAP['likeCount']
label_likes = ui.color(color, ui.bold(like_count))
plot_likes = ui.color(color, ui.bold(' '.join((
@@ -81,7 +87,7 @@ def make_comment_item(context, snippet, uri, total_replies=0):
plot_props.append(plot_likes)
if total_replies:
- total_replies, _ = utils.friendly_number(total_replies)
+ total_replies, _ = friendly_number(total_replies)
color = __COLOR_MAP['commentCount']
label_replies = ui.color(color, ui.bold(total_replies))
plot_replies = ui.color(color, ui.bold(' '.join((
@@ -124,13 +130,13 @@ def make_comment_item(context, snippet, uri, total_replies=0):
comment_item = DirectoryItem(label, uri)
comment_item.set_plot(plot)
- datetime = utils.datetime_parser.parse(published_at, as_utc=True)
+ datetime = datetime_parser.parse(published_at, as_utc=True)
comment_item.set_added_utc(datetime)
- local_datetime = utils.datetime_parser.utc_to_local(datetime)
+ local_datetime = datetime_parser.utc_to_local(datetime)
comment_item.set_dateadded_from_datetime(local_datetime)
if edited:
- datetime = utils.datetime_parser.parse(updated_at, as_utc=True)
- local_datetime = utils.datetime_parser.utc_to_local(datetime)
+ datetime = datetime_parser.parse(updated_at, as_utc=True)
+ local_datetime = datetime_parser.utc_to_local(datetime)
comment_item.set_date_from_datetime(local_datetime)
if not uri:
@@ -375,7 +381,7 @@ def update_video_infos(provider, context, video_id_dict,
else:
duration = yt_item.get('contentDetails', {}).get('duration')
if duration:
- duration = utils.datetime_parser.parse(duration)
+ duration = datetime_parser.parse(duration)
# subtract 1s because YouTube duration is +1s too long
duration = (duration.seconds - 1) if duration.seconds else None
if duration:
@@ -402,9 +408,9 @@ def update_video_infos(provider, context, video_id_dict,
else:
start_at = None
if start_at:
- datetime = utils.datetime_parser.parse(start_at, as_utc=True)
+ datetime = datetime_parser.parse(start_at, as_utc=True)
video_item.set_scheduled_start_utc(datetime)
- local_datetime = utils.datetime_parser.utc_to_local(datetime)
+ local_datetime = datetime_parser.utc_to_local(datetime)
video_item.set_year_from_datetime(local_datetime)
video_item.set_aired_from_datetime(local_datetime)
video_item.set_premiered_from_datetime(local_datetime)
@@ -413,7 +419,7 @@ def update_video_infos(provider, context, video_id_dict,
else 'upcoming')
start_at = '{type_label} {start_at}'.format(
type_label=type_label,
- start_at=utils.datetime_parser.get_scheduled_start(
+ start_at=datetime_parser.get_scheduled_start(
context, local_datetime
)
)
@@ -427,7 +433,7 @@ def update_video_infos(provider, context, video_id_dict,
if not label:
continue
- str_value, value = utils.friendly_number(value)
+ str_value, value = friendly_number(value)
if not value:
continue
@@ -489,7 +495,7 @@ def update_video_infos(provider, context, video_id_dict,
# plot
channel_name = snippet.get('channelTitle', '')
- description = utils.strip_html_from_text(snippet['description'])
+ description = strip_html_from_text(snippet['description'])
if show_details:
description = ''.join((
ui.bold(channel_name, cr_after=2) if channel_name else '',
@@ -507,9 +513,9 @@ def update_video_infos(provider, context, video_id_dict,
# date time
published_at = snippet.get('publishedAt')
if published_at:
- datetime = utils.datetime_parser.parse(published_at, as_utc=True)
+ datetime = datetime_parser.parse(published_at, as_utc=True)
video_item.set_added_utc(datetime)
- local_datetime = utils.datetime_parser.utc_to_local(datetime)
+ local_datetime = datetime_parser.utc_to_local(datetime)
video_item.set_dateadded_from_datetime(local_datetime)
if not start_at:
video_item.set_year_from_datetime(local_datetime)
@@ -601,7 +607,7 @@ def update_video_infos(provider, context, video_id_dict,
# got to [CHANNEL], only if we are not directly in the channel provide a jump to the channel
if (channel_id and channel_name and
- utils.create_path('channel', channel_id) != path):
+ create_path('channel', channel_id) != path):
video_item.set_channel_id(channel_id)
yt_context_menu.append_go_to_channel(
context_menu, context, channel_id, channel_name
diff --git a/resources/lib/youtube_plugin/youtube/helper/v3.py b/resources/lib/youtube_plugin/youtube/helper/v3.py
index 1fa2c409b..04f8ae424 100644
--- a/resources/lib/youtube_plugin/youtube/helper/v3.py
+++ b/resources/lib/youtube_plugin/youtube/helper/v3.py
@@ -10,10 +10,10 @@
from __future__ import absolute_import, division, unicode_literals
-from ...youtube.helper import yt_context_menu
+from . import utils
from ... import kodion
from ...kodion import items
-from . import utils
+from ...youtube.helper import yt_context_menu
def _process_list_response(provider, context, json_data):
From b6407f726836b23712b036b7cf81fbbfd7392feb Mon Sep 17 00:00:00 2001
From: MoojMidge <56883549+MoojMidge@users.noreply.github.com>
Date: Tue, 12 Dec 2023 12:52:04 +1100
Subject: [PATCH 089/141] Update filter_short_videos
- Caller must check whether Settings.hide_short_videos() is True
- update_video_infos will short circuit if short videos are hidden
---
.../lib/youtube_plugin/youtube/helper/tv.py | 6 ++++--
.../lib/youtube_plugin/youtube/helper/utils.py | 17 +++++++++--------
.../lib/youtube_plugin/youtube/helper/v3.py | 3 ++-
3 files changed, 15 insertions(+), 11 deletions(-)
diff --git a/resources/lib/youtube_plugin/youtube/helper/tv.py b/resources/lib/youtube_plugin/youtube/helper/tv.py
index 0d5541ff7..56a0ca9ea 100644
--- a/resources/lib/youtube_plugin/youtube/helper/tv.py
+++ b/resources/lib/youtube_plugin/youtube/helper/tv.py
@@ -53,7 +53,8 @@ def my_subscriptions_to_items(provider, context, json_data, do_filter=False):
utils.update_video_infos(provider, context, video_id_dict, channel_items_dict=channel_item_dict, use_play_data=use_play_data)
utils.update_fanarts(provider, context, channel_item_dict)
- result = utils.filter_short_videos(context, result)
+ if context.get_settings().hide_short_videos():
+ result = utils.filter_short_videos(result)
# next page
next_page_token = json_data.get('next_page_token', '')
@@ -99,7 +100,8 @@ def tv_videos_to_items(provider, context, json_data):
utils.update_video_infos(provider, context, video_id_dict, channel_items_dict=channel_item_dict, use_play_data=use_play_data)
utils.update_fanarts(provider, context, channel_item_dict)
- result = utils.filter_short_videos(context, result)
+ if context.get_settings().hide_short_videos():
+ result = utils.filter_short_videos(result)
# next page
next_page_token = json_data.get('next_page_token', '')
diff --git a/resources/lib/youtube_plugin/youtube/helper/utils.py b/resources/lib/youtube_plugin/youtube/helper/utils.py
index c3104520a..5239475c1 100644
--- a/resources/lib/youtube_plugin/youtube/helper/utils.py
+++ b/resources/lib/youtube_plugin/youtube/helper/utils.py
@@ -353,6 +353,7 @@ def update_video_infos(provider, context, video_id_dict,
alternate_player = settings.is_support_alternative_player_enabled()
logged_in = provider.is_logged_in()
path = context.get_path()
+ hide_shorts = settings.hide_short_videos()
show_details = settings.show_detailed_description()
thumb_size = settings.use_thumbnail_size()
thumb_stamp = get_thumb_timestamp()
@@ -386,6 +387,8 @@ def update_video_infos(provider, context, video_id_dict,
duration = (duration.seconds - 1) if duration.seconds else None
if duration:
video_item.set_duration_from_seconds(duration)
+ if hide_shorts and duration <= 60:
+ continue
if not video_item.live and play_data:
if 'play_count' in play_data:
@@ -808,11 +811,9 @@ def add_related_video_to_playlist(provider, context, client, v3, video_id):
break
-def filter_short_videos(context, items):
- if context.get_settings().hide_short_videos():
- items = [
- item
- for item in items
- if item.playable and not 0 <= item.get_duration() <= 60
- ]
- return items
+def filter_short_videos(items):
+ return [
+ item
+ for item in items
+ if item.playable and not 0 <= item.get_duration() <= 60
+ ]
diff --git a/resources/lib/youtube_plugin/youtube/helper/v3.py b/resources/lib/youtube_plugin/youtube/helper/v3.py
index 04f8ae424..4420b5560 100644
--- a/resources/lib/youtube_plugin/youtube/helper/v3.py
+++ b/resources/lib/youtube_plugin/youtube/helper/v3.py
@@ -297,7 +297,8 @@ def response_to_items(provider, context, json_data, sort=None, reverse_sort=Fals
if sort is not None:
result = sorted(result, key=sort, reverse=reverse_sort)
- result = utils.filter_short_videos(context, result)
+ if context.get_settings().hide_short_videos():
+ result = utils.filter_short_videos(result)
# no processing of next page item
if not process_next_page:
From a3af85f01e8fc515158a3fba2882d65d5fbf5908 Mon Sep 17 00:00:00 2001
From: MoojMidge <56883549+MoojMidge@users.noreply.github.com>
Date: Tue, 12 Dec 2023 13:07:11 +1100
Subject: [PATCH 090/141] Improve default sorting of live videos
- aired infolabel does not include time, date does
- sort by date/time ascending
---
resources/lib/youtube_plugin/youtube/helper/v3.py | 2 +-
resources/lib/youtube_plugin/youtube/helper/yt_specials.py | 4 ++--
resources/lib/youtube_plugin/youtube/provider.py | 2 +-
3 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/resources/lib/youtube_plugin/youtube/helper/v3.py b/resources/lib/youtube_plugin/youtube/helper/v3.py
index 4420b5560..ca82bb24b 100644
--- a/resources/lib/youtube_plugin/youtube/helper/v3.py
+++ b/resources/lib/youtube_plugin/youtube/helper/v3.py
@@ -278,7 +278,7 @@ def _process_list_response(provider, context, json_data):
return result
-def response_to_items(provider, context, json_data, sort=None, reverse_sort=False, process_next_page=True):
+def response_to_items(provider, context, json_data, sort=None, reverse=False, process_next_page=True):
result = []
is_youtube, kind = _parse_kind(json_data)
diff --git a/resources/lib/youtube_plugin/youtube/helper/yt_specials.py b/resources/lib/youtube_plugin/youtube/helper/yt_specials.py
index 6b143dd51..de8873dc4 100644
--- a/resources/lib/youtube_plugin/youtube/helper/yt_specials.py
+++ b/resources/lib/youtube_plugin/youtube/helper/yt_specials.py
@@ -131,7 +131,7 @@ def _process_disliked_videos(provider, context):
def _process_live_events(provider, context, event_type='live'):
def _sort(x):
- return x.get_aired()
+ return x.get_date()
provider.set_content_type(context, constants.content_type.VIDEOS)
result = []
@@ -143,7 +143,7 @@ def _sort(x):
json_data = provider.get_client(context).get_live_events(event_type=event_type, page_token=page_token, location=location)
if not v3.handle_error(context, json_data):
return False
- result.extend(v3.response_to_items(provider, context, json_data, sort=_sort, reverse_sort=True))
+ result.extend(v3.response_to_items(provider, context, json_data, sort=_sort))
return result
diff --git a/resources/lib/youtube_plugin/youtube/provider.py b/resources/lib/youtube_plugin/youtube/provider.py
index d4fbb8039..8c70cb240 100644
--- a/resources/lib/youtube_plugin/youtube/provider.py
+++ b/resources/lib/youtube_plugin/youtube/provider.py
@@ -494,7 +494,7 @@ def _on_channel(self, context, re_match):
return False
result.extend(
- v3.response_to_items(self, context, json_data, sort=lambda x: x.get_aired(), reverse_sort=True))
+ v3.response_to_items(self, context, json_data, sort=lambda x: x.get_date()))
return result
From 0889e7bd3a3e5a3d8b8d0b8883ceef7ad06efede Mon Sep 17 00:00:00 2001
From: MoojMidge <56883549+MoojMidge@users.noreply.github.com>
Date: Tue, 12 Dec 2023 13:14:17 +1100
Subject: [PATCH 091/141] Minor speed improvements to creation of listings
- TODO: Proper thread management
- Reduce requests backoff factor
---
.../youtube_plugin/kodion/network/requests.py | 2 +-
.../youtube_plugin/youtube/helper/utils.py | 18 +-
.../lib/youtube_plugin/youtube/helper/v3.py | 199 +++++++---
.../youtube/helper/yt_specials.py | 340 ++++++++----------
4 files changed, 317 insertions(+), 242 deletions(-)
diff --git a/resources/lib/youtube_plugin/kodion/network/requests.py b/resources/lib/youtube_plugin/kodion/network/requests.py
index 19e8b570c..c41473b6a 100644
--- a/resources/lib/youtube_plugin/kodion/network/requests.py
+++ b/resources/lib/youtube_plugin/kodion/network/requests.py
@@ -30,7 +30,7 @@ class BaseRequestsClass(object):
pool_block=True,
max_retries=Retry(
total=3,
- backoff_factor=1,
+ backoff_factor=0.1,
status_forcelist={500, 502, 503, 504},
allowed_methods=None,
)
diff --git a/resources/lib/youtube_plugin/youtube/helper/utils.py b/resources/lib/youtube_plugin/youtube/helper/utils.py
index 5239475c1..2262b6f9c 100644
--- a/resources/lib/youtube_plugin/youtube/helper/utils.py
+++ b/resources/lib/youtube_plugin/youtube/helper/utils.py
@@ -349,16 +349,21 @@ def update_video_infos(provider, context, video_id_dict,
if not playlist_item_id_dict:
playlist_item_id_dict = {}
- settings = context.get_settings()
- alternate_player = settings.is_support_alternative_player_enabled()
logged_in = provider.is_logged_in()
- path = context.get_path()
+ if logged_in:
+ wl_playlist_id = context.get_access_manager().get_watch_later_id()
+ else:
+ wl_playlist_id = None
+
+ settings = context.get_settings()
hide_shorts = settings.hide_short_videos()
+ alternate_player = settings.is_support_alternative_player_enabled()
show_details = settings.show_detailed_description()
thumb_size = settings.use_thumbnail_size()
thumb_stamp = get_thumb_timestamp()
+
+ path = context.get_path()
ui = context.get_ui()
- watch_later_playlist_id = context.get_access_manager().get_watch_later_id()
for video_id, yt_item in data.items():
video_item = video_id_dict[video_id]
@@ -579,10 +584,9 @@ def update_video_infos(provider, context, video_id_dict,
if logged_in:
# add 'Watch Later' only if we are not in my 'Watch Later' list
- if (watch_later_playlist_id
- and watch_later_playlist_id != playlist_id):
+ if wl_playlist_id and wl_playlist_id != playlist_id:
yt_context_menu.append_watch_later(
- context_menu, context, watch_later_playlist_id, video_id
+ context_menu, context, wl_playlist_id, video_id
)
# provide 'remove' for videos in my playlists
diff --git a/resources/lib/youtube_plugin/youtube/helper/v3.py b/resources/lib/youtube_plugin/youtube/helper/v3.py
index ca82bb24b..713435b16 100644
--- a/resources/lib/youtube_plugin/youtube/helper/v3.py
+++ b/resources/lib/youtube_plugin/youtube/helper/v3.py
@@ -10,13 +10,29 @@
from __future__ import absolute_import, division, unicode_literals
-from . import utils
-from ... import kodion
-from ...kodion import items
+from threading import Thread
+
+from .utils import (
+ filter_short_videos,
+ get_thumbnail,
+ make_comment_item,
+ update_channel_infos,
+ update_fanarts,
+ update_playlist_infos,
+ update_video_infos,
+)
+from ...kodion import KodionException
+from ...kodion.items import DirectoryItem, NextPageItem, VideoItem
+from ...kodion.utils import strip_html_from_text
from ...youtube.helper import yt_context_menu
def _process_list_response(provider, context, json_data):
+ yt_items = json_data.get('items', [])
+ if not yt_items:
+ context.log_warning('List of search result is empty')
+ return []
+
video_id_dict = {}
channel_id_dict = {}
playlist_id_dict = {}
@@ -25,15 +41,13 @@ def _process_list_response(provider, context, json_data):
result = []
- thumb_size = context.get_settings().use_thumbnail_size()
- yt_items = json_data.get('items', [])
- if not yt_items:
- context.log_warning('List of search result is empty')
- return result
-
incognito = context.get_param('incognito', False)
addon_id = context.get_param('addon_id', '')
+ settings = context.get_settings()
+ thumb_size = settings.use_thumbnail_size()
+ use_play_data = not incognito and settings.use_local_history()
+
for yt_item in yt_items:
is_youtube, kind = _parse_kind(yt_item)
@@ -45,14 +59,14 @@ def _process_list_response(provider, context, json_data):
video_id = yt_item['id']
snippet = yt_item['snippet']
title = snippet.get('title', context.localize('untitled'))
- image = utils.get_thumbnail(thumb_size, snippet.get('thumbnails', {}))
+ image = get_thumbnail(thumb_size, snippet.get('thumbnails', {}))
item_params = {'video_id': video_id}
if incognito:
item_params['incognito'] = incognito
if addon_id:
item_params['addon_id'] = addon_id
item_uri = context.create_uri(['play'], item_params)
- video_item = items.VideoItem(title, item_uri, image=image)
+ video_item = VideoItem(title, item_uri, image=image)
video_item.video_id = video_id
if incognito:
video_item.set_play_count(0)
@@ -63,14 +77,14 @@ def _process_list_response(provider, context, json_data):
channel_id = yt_item['id']
snippet = yt_item['snippet']
title = snippet.get('title', context.localize('untitled'))
- image = utils.get_thumbnail(thumb_size, snippet.get('thumbnails', {}))
+ image = get_thumbnail(thumb_size, snippet.get('thumbnails', {}))
item_params = {}
if incognito:
item_params['incognito'] = incognito
if addon_id:
item_params['addon_id'] = addon_id
item_uri = context.create_uri(['channel', channel_id], item_params)
- channel_item = items.DirectoryItem(title, item_uri, image=image)
+ channel_item = DirectoryItem(title, item_uri, image=image)
channel_item.set_fanart(provider.get_fanart(context))
# if logged in => provide subscribing to the channel
@@ -90,13 +104,13 @@ def _process_list_response(provider, context, json_data):
if addon_id:
item_params['addon_id'] = addon_id
item_uri = context.create_uri(['special', 'browse_channels'], item_params)
- guide_item = items.DirectoryItem(title, item_uri)
+ guide_item = DirectoryItem(title, item_uri)
guide_item.set_fanart(provider.get_fanart(context))
result.append(guide_item)
elif kind == 'subscription':
snippet = yt_item['snippet']
title = snippet.get('title', context.localize('untitled'))
- image = utils.get_thumbnail(thumb_size, snippet.get('thumbnails', {}))
+ image = get_thumbnail(thumb_size, snippet.get('thumbnails', {}))
channel_id = snippet['resourceId']['channelId']
item_params = {}
if incognito:
@@ -104,7 +118,7 @@ def _process_list_response(provider, context, json_data):
if addon_id:
item_params['addon_id'] = addon_id
item_uri = context.create_uri(['channel', channel_id], item_params)
- channel_item = items.DirectoryItem(title, item_uri, image=image)
+ channel_item = DirectoryItem(title, item_uri, image=image)
channel_item.set_fanart(provider.get_fanart(context))
channel_item.set_channel_id(channel_id)
# map channel id with subscription id - we need it for the unsubscription
@@ -116,7 +130,7 @@ def _process_list_response(provider, context, json_data):
playlist_id = yt_item['id']
snippet = yt_item['snippet']
title = snippet.get('title', context.localize('untitled'))
- image = utils.get_thumbnail(thumb_size, snippet.get('thumbnails', {}))
+ image = get_thumbnail(thumb_size, snippet.get('thumbnails', {}))
channel_id = snippet['channelId']
@@ -129,7 +143,7 @@ def _process_list_response(provider, context, json_data):
if addon_id:
item_params['addon_id'] = addon_id
item_uri = context.create_uri(['channel', channel_id, 'playlist', playlist_id], item_params)
- playlist_item = items.DirectoryItem(title, item_uri, image=image)
+ playlist_item = DirectoryItem(title, item_uri, image=image)
playlist_item.set_fanart(provider.get_fanart(context))
result.append(playlist_item)
playlist_id_dict[playlist_id] = playlist_item
@@ -141,14 +155,14 @@ def _process_list_response(provider, context, json_data):
playlist_item_id_dict[video_id] = yt_item['id']
title = snippet.get('title', context.localize('untitled'))
- image = utils.get_thumbnail(thumb_size, snippet.get('thumbnails', {}))
+ image = get_thumbnail(thumb_size, snippet.get('thumbnails', {}))
item_params = {'video_id': video_id}
if incognito:
item_params['incognito'] = incognito
if addon_id:
item_params['addon_id'] = addon_id
item_uri = context.create_uri(['play'], item_params)
- video_item = items.VideoItem(title, item_uri, image=image)
+ video_item = VideoItem(title, item_uri, image=image)
video_item.video_id = video_id
if incognito:
video_item.set_play_count(0)
@@ -172,14 +186,14 @@ def _process_list_response(provider, context, json_data):
continue
title = snippet.get('title', context.localize('untitled'))
- image = utils.get_thumbnail(thumb_size, snippet.get('thumbnails', {}))
+ image = get_thumbnail(thumb_size, snippet.get('thumbnails', {}))
item_params = {'video_id': video_id}
if incognito:
item_params['incognito'] = incognito
if addon_id:
item_params['addon_id'] = addon_id
item_uri = context.create_uri(['play'], item_params)
- video_item = items.VideoItem(title, item_uri, image=image)
+ video_item = VideoItem(title, item_uri, image=image)
video_item.video_id = video_id
if incognito:
video_item.set_play_count(0)
@@ -196,10 +210,10 @@ def _process_list_response(provider, context, json_data):
item_uri = context.create_uri(['special', 'child_comments'], item_params)
else:
item_uri = ''
- result.append(utils.make_comment_item(context, snippet, item_uri, total_replies))
+ result.append(make_comment_item(context, snippet, item_uri, total_replies))
elif kind == 'comment':
- result.append(utils.make_comment_item(context, yt_item['snippet'], uri=''))
+ result.append(make_comment_item(context, yt_item['snippet'], uri=''))
elif kind == 'searchresult':
_, kind = _parse_kind(yt_item.get('id', {}))
@@ -209,14 +223,14 @@ def _process_list_response(provider, context, json_data):
video_id = yt_item['id']['videoId']
snippet = yt_item.get('snippet', {})
title = snippet.get('title', context.localize('untitled'))
- image = utils.get_thumbnail(thumb_size, snippet.get('thumbnails', {}))
+ image = get_thumbnail(thumb_size, snippet.get('thumbnails', {}))
item_params = {'video_id': video_id}
if incognito:
item_params['incognito'] = incognito
if addon_id:
item_params['addon_id'] = addon_id
item_uri = context.create_uri(['play'], item_params)
- video_item = items.VideoItem(title, item_uri, image=image)
+ video_item = VideoItem(title, item_uri, image=image)
video_item.video_id = video_id
if incognito:
video_item.set_play_count(0)
@@ -228,7 +242,7 @@ def _process_list_response(provider, context, json_data):
playlist_id = yt_item['id']['playlistId']
snippet = yt_item['snippet']
title = snippet.get('title', context.localize('untitled'))
- image = utils.get_thumbnail(thumb_size, snippet.get('thumbnails', {}))
+ image = get_thumbnail(thumb_size, snippet.get('thumbnails', {}))
channel_id = snippet['channelId']
# if the path directs to a playlist of our own, we correct the channel id to 'mine'
@@ -241,7 +255,7 @@ def _process_list_response(provider, context, json_data):
if addon_id:
item_params['addon_id'] = addon_id
item_uri = context.create_uri(['channel', channel_id, 'playlist', playlist_id], item_params)
- playlist_item = items.DirectoryItem(title, item_uri, image=image)
+ playlist_item = DirectoryItem(title, item_uri, image=image)
playlist_item.set_fanart(provider.get_fanart(context))
result.append(playlist_item)
playlist_id_dict[playlist_id] = playlist_item
@@ -249,56 +263,141 @@ def _process_list_response(provider, context, json_data):
channel_id = yt_item['id']['channelId']
snippet = yt_item['snippet']
title = snippet.get('title', context.localize('untitled'))
- image = utils.get_thumbnail(thumb_size, snippet.get('thumbnails', {}))
+ image = get_thumbnail(thumb_size, snippet.get('thumbnails', {}))
item_params = {}
if incognito:
item_params['incognito'] = incognito
if addon_id:
item_params['addon_id'] = addon_id
item_uri = context.create_uri(['channel', channel_id], item_params)
- channel_item = items.DirectoryItem(title, item_uri, image=image)
+ channel_item = DirectoryItem(title, item_uri, image=image)
channel_item.set_fanart(provider.get_fanart(context))
result.append(channel_item)
channel_id_dict[channel_id] = channel_item
else:
- raise kodion.KodionException("Unknown kind '%s'" % kind)
+ raise KodionException("Unknown kind '%s'" % kind)
else:
- raise kodion.KodionException("Unknown kind '%s'" % kind)
-
- use_play_data = not incognito and context.get_settings().use_local_history()
+ raise KodionException("Unknown kind '%s'" % kind)
# this will also update the channel_id_dict with the correct channel id for each video.
channel_items_dict = {}
- utils.update_video_infos(provider, context, video_id_dict, playlist_item_id_dict, channel_items_dict,
- live_details=True, use_play_data=use_play_data)
- utils.update_playlist_infos(provider, context, playlist_id_dict, channel_items_dict)
- utils.update_channel_infos(provider, context, channel_id_dict, subscription_id_dict, channel_items_dict)
- if video_id_dict or playlist_id_dict:
- utils.update_fanarts(provider, context, channel_items_dict)
+
+ running = 0
+ resource_manager = provider.get_resource_manager(context)
+ resources = [
+ {
+ 'fetcher': resource_manager.get_videos,
+ 'args': (video_id_dict.keys(), ),
+ 'kwargs': {'live_details': True, 'suppress_errors': True},
+ 'thread': None,
+ 'updater': update_video_infos,
+ 'upd_args': (provider, context, video_id_dict, playlist_item_id_dict, channel_items_dict),
+ 'upd_kwargs': {'data': None, 'live_details': True, 'use_play_data': use_play_data},
+ 'complete': False,
+ 'defer': False,
+ },
+ {
+ 'fetcher': resource_manager.get_playlists,
+ 'args': (playlist_id_dict.keys(), ),
+ 'kwargs': {},
+ 'thread': None,
+ 'updater': update_playlist_infos,
+ 'upd_args': (provider, context, playlist_id_dict, channel_items_dict),
+ 'upd_kwargs': {'data': None},
+ 'complete': False,
+ 'defer': False,
+ },
+ {
+ 'fetcher': resource_manager.get_channels,
+ 'args': (channel_id_dict.keys(), ),
+ 'kwargs': {},
+ 'thread': None,
+ 'updater': update_channel_infos,
+ 'upd_args': (provider, context, channel_id_dict, subscription_id_dict, channel_items_dict),
+ 'upd_kwargs': {'data': None},
+ 'complete': False,
+ 'defer': False,
+ },
+ {
+ 'fetcher': resource_manager.get_fanarts,
+ 'args': (channel_items_dict.keys(), ),
+ 'kwargs': {},
+ 'thread': None,
+ 'updater': update_fanarts,
+ 'upd_args': (provider, context, channel_items_dict),
+ 'upd_kwargs': {'data': None},
+ 'complete': False,
+ 'defer': True,
+ },
+ ]
+
+ def _fetch(resource):
+ data = resource['fetcher'](
+ *resource['args'], **resource['kwargs']
+ )
+ if not data:
+ return
+ resource['upd_kwargs']['data'] = data
+ resource['updater'](*resource['upd_args'], **resource['upd_kwargs'])
+
+ for resource in resources:
+ if not resource['args'][0]:
+ resource['complete'] = True
+ continue
+
+ running += 1
+ if not resource['defer']:
+ # _fetch(resource)
+ thread = Thread(target=_fetch, args=(resource, ))
+ thread.daemon = True
+ thread.start()
+ resource['thread'] = thread
+
+ while running > 0:
+ for resource in resources:
+ if resource['complete']:
+ continue
+
+ thread = resource['thread']
+ if thread:
+ thread.join(30)
+ if not thread.is_alive():
+ resource['thread'] = None
+ resource['complete'] = True
+ running -= 1
+ elif resource['defer']:
+ resource['defer'] = False
+ # _fetch(resource)
+ thread = Thread(target=_fetch, args=(resource, ))
+ thread.daemon = True
+ thread.start()
+ resource['thread'] = thread
+ else:
+ running -= 1
+ resource['complete'] = True
+
return result
def response_to_items(provider, context, json_data, sort=None, reverse=False, process_next_page=True):
- result = []
-
is_youtube, kind = _parse_kind(json_data)
if not is_youtube:
context.log_debug('v3 response: Response discarded, is_youtube=False')
- return result
+ return []
if kind in ['searchlistresponse', 'playlistitemlistresponse', 'playlistlistresponse',
'subscriptionlistresponse', 'guidecategorylistresponse', 'channellistresponse',
'videolistresponse', 'activitylistresponse', 'commentthreadlistresponse',
'commentlistresponse']:
- result.extend(_process_list_response(provider, context, json_data))
+ result = _process_list_response(provider, context, json_data)
else:
- raise kodion.KodionException("Unknown kind '%s'" % kind)
+ raise KodionException("Unknown kind '%s'" % kind)
if sort is not None:
- result = sorted(result, key=sort, reverse=reverse_sort)
+ result.sort(key=sort, reverse=reverse)
if context.get_settings().hide_short_videos():
- result = utils.filter_short_videos(result)
+ result = filter_short_videos(result)
# no processing of next page item
if not process_next_page:
@@ -326,7 +425,7 @@ def response_to_items(provider, context, json_data, sort=None, reverse=False, pr
new_context = context.clone(new_params=new_params)
current_page = new_context.get_param('page', 1)
- next_page_item = items.NextPageItem(new_context, current_page, fanart=provider.get_fanart(new_context))
+ next_page_item = NextPageItem(new_context, current_page, fanart=provider.get_fanart(new_context))
result.append(next_page_item)
return result
@@ -337,8 +436,8 @@ def handle_error(context, json_data):
ok_dialog = False
message_timeout = 5000
- message = kodion.utils.strip_html_from_text(json_data['error'].get('message', ''))
- log_message = kodion.utils.strip_html_from_text(json_data['error'].get('message', ''))
+ message = strip_html_from_text(json_data['error'].get('message', ''))
+ log_message = strip_html_from_text(json_data['error'].get('message', ''))
reason = json_data['error']['errors'][0].get('reason', '')
title = '%s: %s' % (context.get_name(), reason)
diff --git a/resources/lib/youtube_plugin/youtube/helper/yt_specials.py b/resources/lib/youtube_plugin/youtube/helper/yt_specials.py
index de8873dc4..f7744173d 100644
--- a/resources/lib/youtube_plugin/youtube/helper/yt_specials.py
+++ b/resources/lib/youtube_plugin/youtube/helper/yt_specials.py
@@ -11,7 +11,8 @@
from __future__ import absolute_import, division, unicode_literals
from . import utils
-from ...kodion import KodionException, constants
+from ...kodion import KodionException
+from ...kodion.constants import content_type
from ...kodion.items import DirectoryItem, UriItem
from ...kodion.utils import strip_html_from_text
from ...youtube.helper import (
@@ -24,148 +25,131 @@
def _process_related_videos(provider, context):
- provider.set_content_type(context, constants.content_type.VIDEOS)
- result = []
-
- page_token = context.get_param('page_token', '')
+ provider.set_content_type(context, content_type.VIDEOS)
video_id = context.get_param('video_id', '')
- if video_id:
- json_data = provider.get_client(context).get_related_videos(video_id=video_id, page_token=page_token)
- if not v3.handle_error(context, json_data):
- return False
- result.extend(v3.response_to_items(provider, context, json_data, process_next_page=False))
+ if not video_id:
+ return []
- return result
+ json_data = provider.get_client(context).get_related_videos(
+ video_id=video_id, page_token=context.get_param('page_token', '')
+ )
+ if not v3.handle_error(context, json_data):
+ return False
+ return v3.response_to_items(provider,
+ context,
+ json_data,
+ process_next_page=False)
def _process_parent_comments(provider, context):
- provider.set_content_type(context, constants.content_type.FILES)
- result = []
-
- page_token = context.get_param('page_token', '')
+ provider.set_content_type(context, content_type.FILES)
video_id = context.get_param('video_id', '')
- if video_id:
- json_data = provider.get_client(context).get_parent_comments(video_id=video_id, page_token=page_token)
- if not v3.handle_error(context, json_data):
- return False
- result.extend(v3.response_to_items(provider, context, json_data))
+ if not video_id:
+ return []
- return result
+ json_data = provider.get_client(context).get_parent_comments(
+ video_id=video_id, page_token=context.get_param('page_token', '')
+ )
+ if not v3.handle_error(context, json_data):
+ return False
+ return v3.response_to_items(provider, context, json_data)
def _process_child_comments(provider, context):
- provider.set_content_type(context, constants.content_type.FILES)
- result = []
-
- page_token = context.get_param('page_token', '')
+ provider.set_content_type(context, content_type.FILES)
parent_id = context.get_param('parent_id', '')
- if parent_id:
- json_data = provider.get_client(context).get_child_comments(parent_id=parent_id, page_token=page_token)
- if not v3.handle_error(context, json_data):
- return False
- result.extend(v3.response_to_items(provider, context, json_data))
+ if not parent_id:
+ return []
- return result
+ json_data = provider.get_client(context).get_child_comments(
+ parent_id=parent_id, page_token=context.get_param('page_token', '')
+ )
+ if not v3.handle_error(context, json_data):
+ return False
+ return v3.response_to_items(provider, context, json_data)
def _process_recommendations(provider, context):
- provider.set_content_type(context, constants.content_type.VIDEOS)
- result = []
-
- page_token = context.get_param('page_token', '')
- json_data = provider.get_client(context).get_activities('home', page_token=page_token)
+ provider.set_content_type(context, content_type.VIDEOS)
+ json_data = provider.get_client(context).get_activities(
+ channel_id='home', page_token=context.get_param('page_token', '')
+ )
if not v3.handle_error(context, json_data):
return False
- result.extend(v3.response_to_items(provider, context, json_data))
- return result
+ return v3.response_to_items(provider, context, json_data)
def _process_popular_right_now(provider, context):
- provider.set_content_type(context, constants.content_type.VIDEOS)
- result = []
-
- page_token = context.get_param('page_token', '')
- json_data = provider.get_client(context).get_popular_videos(page_token=page_token)
+ provider.set_content_type(context, content_type.VIDEOS)
+ json_data = provider.get_client(context).get_popular_videos(
+ page_token=context.get_param('page_token', '')
+ )
if not v3.handle_error(context, json_data):
return False
- result.extend(v3.response_to_items(provider, context, json_data))
-
- return result
+ return v3.response_to_items(provider, context, json_data)
def _process_browse_channels(provider, context):
- provider.set_content_type(context, constants.content_type.FILES)
- result = []
-
- # page_token = context.get_param('page_token', '')
- guide_id = context.get_param('guide_id', '')
+ provider.set_content_type(context, content_type.FILES)
client = provider.get_client(context)
-
+ guide_id = context.get_param('guide_id', '')
if guide_id:
json_data = client.get_guide_category(guide_id)
if not v3.handle_error(context, json_data):
return False
- result.extend(v3.response_to_items(provider, context, json_data))
- else:
- function_cache = context.get_function_cache()
- json_data = function_cache.get(client.get_guide_categories,
- function_cache.ONE_MONTH)
- if not v3.handle_error(context, json_data):
- return False
- result.extend(v3.response_to_items(provider, context, json_data))
+ return v3.response_to_items(provider, context, json_data)
- return result
+ function_cache = context.get_function_cache()
+ json_data = function_cache.get(client.get_guide_categories,
+ function_cache.ONE_MONTH)
+ if not v3.handle_error(context, json_data):
+ return False
+ return v3.response_to_items(provider, context, json_data)
def _process_disliked_videos(provider, context):
- provider.set_content_type(context, constants.content_type.VIDEOS)
- result = []
-
- page_token = context.get_param('page_token', '')
- json_data = provider.get_client(context).get_disliked_videos(page_token=page_token)
+ provider.set_content_type(context, content_type.VIDEOS)
+ json_data = provider.get_client(context).get_disliked_videos(
+ page_token=context.get_param('page_token', '')
+ )
if not v3.handle_error(context, json_data):
return False
- result.extend(v3.response_to_items(provider, context, json_data))
- return result
+ return v3.response_to_items(provider, context, json_data)
def _process_live_events(provider, context, event_type='live'):
def _sort(x):
return x.get_date()
- provider.set_content_type(context, constants.content_type.VIDEOS)
- result = []
-
+ provider.set_content_type(context, content_type.VIDEOS)
# TODO: cache result
- page_token = context.get_param('page_token', '')
- location = context.get_param('location', False)
-
- json_data = provider.get_client(context).get_live_events(event_type=event_type, page_token=page_token, location=location)
+ json_data = provider.get_client(context).get_live_events(
+ event_type=event_type,
+ page_token=context.get_param('page_token', ''),
+ location=context.get_param('location', False),
+ )
if not v3.handle_error(context, json_data):
return False
- result.extend(v3.response_to_items(provider, context, json_data, sort=_sort))
-
- return result
+ return v3.response_to_items(provider, context, json_data, sort=_sort)
def _process_description_links(provider, context):
- incognito = context.get_param('incognito', False)
- addon_id = context.get_param('addon_id', '')
+ params = context.get_params()
+ incognito = params.get('incognito', False)
+ addon_id = params.get('addon_id', '')
- def _extract_urls(_video_id):
- provider.set_content_type(context, constants.content_type.VIDEOS)
+ def _extract_urls(video_id):
+ provider.set_content_type(context, content_type.VIDEOS)
url_resolver = UrlResolver(context)
- result = []
-
- progress_dialog = \
- context.get_ui().create_progress_dialog(heading=context.localize('please_wait'),
- background=False)
+ progress_dialog = context.get_ui().create_progress_dialog(
+ heading=context.localize('please_wait'), background=False
+ )
resource_manager = provider.get_resource_manager(context)
- video_data = resource_manager.get_videos([_video_id])
- yt_item = video_data[_video_id]
+ video_data = resource_manager.get_videos([video_id])
+ yt_item = video_data[video_id]
snippet = yt_item['snippet'] # crash if not conform
description = strip_html_from_text(snippet['description'])
@@ -190,153 +174,141 @@ def _extract_urls(_video_id):
url_to_item_converter = UrlToItemConverter()
url_to_item_converter.add_urls(res_urls, provider, context)
-
- result.extend(url_to_item_converter.get_items(provider, context))
+ result = url_to_item_converter.get_items(provider, context)
progress_dialog.close()
- if not result:
- progress_dialog.close()
- context.get_ui().on_ok(title=context.localize('video.description.links'),
- text=context.localize('video.description.links.not_found'))
- return False
-
- return result
-
- def _display_channels(_channel_ids):
- _channel_id_dict = {}
+ if result:
+ return result
+ context.get_ui().on_ok(
+ title=context.localize('video.description.links'),
+ text=context.localize('video.description.links.not_found')
+ )
+ return False
+ def _display_channels(channel_ids):
item_params = {}
if incognito:
- item_params.update({'incognito': incognito})
+ item_params['incognito'] = incognito
if addon_id:
- item_params.update({'addon_id': addon_id})
+ item_params['addon_id'] = addon_id
- for channel_id in _channel_ids:
- item_uri = context.create_uri(['channel', channel_id], item_params)
- channel_item = DirectoryItem('', item_uri)
+ channel_id_dict = {}
+ for channel_id in channel_ids:
+ channel_item = DirectoryItem(
+ '', context.create_uri(['channel', channel_id], item_params)
+ )
channel_item.set_fanart(provider.get_fanart(context))
- _channel_id_dict[channel_id] = channel_item
+ channel_id_dict[channel_id] = channel_item
- _channel_item_dict = {}
- utils.update_channel_infos(provider, context, _channel_id_dict, channel_items_dict=_channel_item_dict)
+ channel_item_dict = {}
+ utils.update_channel_infos(provider,
+ context,
+ channel_id_dict,
+ channel_items_dict=channel_item_dict)
# clean up - remove empty entries
- _result = []
- for key in _channel_id_dict:
- _channel_item = _channel_id_dict[key]
- if _channel_item.get_name():
- _result.append(_channel_item)
- return _result
-
- def _display_playlists(_playlist_ids):
- _playlist_id_dict = {}
+ return [channel_item
+ for channel_item in channel_id_dict.values()
+ if channel_item.get_name()]
+ def _display_playlists(playlist_ids):
item_params = {}
if incognito:
- item_params.update({'incognito': incognito})
+ item_params['incognito'] = incognito
if addon_id:
- item_params.update({'addon_id': addon_id})
+ item_params['addon_id'] = addon_id
- for playlist_id in _playlist_ids:
- item_uri = context.create_uri(['playlist', playlist_id], item_params)
- playlist_item = DirectoryItem('', item_uri)
+ playlist_id_dict = {}
+ for playlist_id in playlist_ids:
+ playlist_item = DirectoryItem(
+ '', context.create_uri(['playlist', playlist_id], item_params)
+ )
playlist_item.set_fanart(provider.get_fanart(context))
- _playlist_id_dict[playlist_id] = playlist_item
+ playlist_id_dict[playlist_id] = playlist_item
- _channel_item_dict = {}
- utils.update_playlist_infos(provider, context, _playlist_id_dict, _channel_item_dict)
- utils.update_fanarts(provider, context, _channel_item_dict)
+ channel_item_dict = {}
+ utils.update_playlist_infos(provider,
+ context,
+ playlist_id_dict,
+ channel_items_dict=channel_item_dict)
+ utils.update_fanarts(provider, context, channel_item_dict)
# clean up - remove empty entries
- _result = []
- for key in _playlist_id_dict:
- _playlist_item = _playlist_id_dict[key]
- if _playlist_item.get_name():
- _result.append(_playlist_item)
+ return [playlist_item
+ for playlist_item in playlist_id_dict.values()
+ if playlist_item.get_name()]
- return _result
-
- video_id = context.get_param('video_id', '')
+ video_id = params.get('video_id', '')
if video_id:
return _extract_urls(video_id)
- channel_ids = context.get_param('channel_ids', [])
+ channel_ids = params.get('channel_ids', [])
if channel_ids:
return _display_channels(channel_ids)
- playlist_ids = context.get_param('playlist_ids', [])
+ playlist_ids = params.get('playlist_ids', [])
if playlist_ids:
return _display_playlists(playlist_ids)
context.log_error('Missing video_id or playlist_ids for description links')
-
return False
def _process_saved_playlists_tv(provider, context):
- provider.set_content_type(context, constants.content_type.FILES)
-
- result = []
- next_page_token = context.get_param('next_page_token', '')
- offset = context.get_param('offset', 0)
- json_data = provider.get_client(context).get_saved_playlists(page_token=next_page_token, offset=offset)
- result.extend(tv.saved_playlists_to_items(provider, context, json_data))
-
- return result
+ provider.set_content_type(context, content_type.FILES)
+ json_data = provider.get_client(context).get_saved_playlists(
+ page_token=context.get_param('next_page_token', ''),
+ offset=context.get_param('offset', 0)
+ )
+ return tv.saved_playlists_to_items(provider, context, json_data)
def _process_watch_history_tv(provider, context):
- provider.set_content_type(context, constants.content_type.VIDEOS)
-
- result = []
- next_page_token = context.get_param('next_page_token', '')
- offset = context.get_param('offset', 0)
- json_data = provider.get_client(context).get_watch_history(page_token=next_page_token, offset=offset)
- result.extend(tv.tv_videos_to_items(provider, context, json_data))
-
- return result
+ provider.set_content_type(context, content_type.VIDEOS)
+ json_data = provider.get_client(context).get_watch_history(
+ page_token=context.get_param('next_page_token', ''),
+ offset=context.get_param('offset', 0)
+ )
+ return tv.tv_videos_to_items(provider, context, json_data)
def _process_purchases_tv(provider, context):
- provider.set_content_type(context, constants.content_type.VIDEOS)
-
- result = []
- next_page_token = context.get_param('next_page_token', '')
- offset = context.get_param('offset', 0)
- json_data = provider.get_client(context).get_purchases(page_token=next_page_token, offset=offset)
- result.extend(tv.tv_videos_to_items(provider, context, json_data))
-
- return result
+ provider.set_content_type(context, content_type.VIDEOS)
+ json_data = provider.get_client(context).get_purchases(
+ page_token=context.get_param('next_page_token', ''),
+ offset=context.get_param('offset', 0)
+ )
+ return tv.tv_videos_to_items(provider, context, json_data)
def _process_new_uploaded_videos_tv(provider, context):
- provider.set_content_type(context, constants.content_type.VIDEOS)
-
- result = []
- next_page_token = context.get_param('next_page_token', '')
- offset = context.get_param('offset', 0)
- json_data = provider.get_client(context).get_my_subscriptions(page_token=next_page_token, offset=offset)
- result.extend(tv.my_subscriptions_to_items(provider, context, json_data))
-
- return result
+ provider.set_content_type(context, content_type.VIDEOS)
+ json_data = provider.get_client(context).get_my_subscriptions(
+ page_token=context.get_param('next_page_token', ''),
+ offset=context.get_param('offset', 0)
+ )
+ return tv.my_subscriptions_to_items(provider, context, json_data)
def _process_new_uploaded_videos_tv_filtered(provider, context):
- provider.set_content_type(context, constants.content_type.VIDEOS)
-
- result = []
- next_page_token = context.get_param('next_page_token', '')
- offset = context.get_param('offset', 0)
- json_data = provider.get_client(context).get_my_subscriptions(page_token=next_page_token, offset=offset)
- result.extend(tv.my_subscriptions_to_items(provider, context, json_data, do_filter=True))
-
- return result
+ provider.set_content_type(context, content_type.VIDEOS)
+ json_data = provider.get_client(context).get_my_subscriptions(
+ page_token=context.get_param('next_page_token', ''),
+ offset=context.get_param('offset', 0)
+ )
+ return tv.my_subscriptions_to_items(provider,
+ context,
+ json_data,
+ do_filter=True)
def process(category, provider, context):
_ = provider.get_client(context) # required for provider.is_logged_in()
- if not provider.is_logged_in() and category in ['new_uploaded_videos_tv', 'new_uploaded_videos_tv_filtered', 'disliked_videos']:
+ if (not provider.is_logged_in()
+ and category in ['new_uploaded_videos_tv',
+ 'new_uploaded_videos_tv_filtered',
+ 'disliked_videos']):
return UriItem(context.create_uri(['sign', 'in']))
if category == 'related_videos':
From 697c9de1fdb2c5b4b9caab7023495969d2b689f2 Mon Sep 17 00:00:00 2001
From: MoojMidge <56883549+MoojMidge@users.noreply.github.com>
Date: Tue, 12 Dec 2023 14:10:57 +1100
Subject: [PATCH 092/141] Avoid JSONStore.save if data is unchanged on update
- Also update log messages
---
.../kodion/json_store/json_store.py | 20 +++++++++----------
1 file changed, 10 insertions(+), 10 deletions(-)
diff --git a/resources/lib/youtube_plugin/kodion/json_store/json_store.py b/resources/lib/youtube_plugin/kodion/json_store/json_store.py
index cf39c9e78..1f5a98c29 100644
--- a/resources/lib/youtube_plugin/kodion/json_store/json_store.py
+++ b/resources/lib/youtube_plugin/kodion/json_store/json_store.py
@@ -29,7 +29,7 @@ def __init__(self, filename):
self.base_path = xbmcvfs.translatePath(_addon_data_path)
if not xbmcvfs.exists(self.base_path) and not make_dirs(self.base_path):
- log_error('JSONStore.__init__ |{path}| invalid path'.format(
+ log_error('JSONStore.__init__ - invalid path:\n|{path}|'.format(
path=self.base_path
))
return
@@ -45,12 +45,12 @@ def set_defaults(self, reset=False):
def save(self, data, update=False, process=None):
if update:
data = merge_dicts(self._data, data)
- elif data == self._data:
- log_debug('JSONStore.save |{filename}| data unchanged'.format(
+ if data == self._data:
+ log_debug('JSONStore.save - data unchanged:\n|{filename}|'.format(
filename=self.filename
))
return
- log_debug('JSONStore.save |{filename}|'.format(
+ log_debug('JSONStore.save - saving:\n|{filename}|'.format(
filename=self.filename
))
try:
@@ -64,18 +64,18 @@ def save(self, data, update=False, process=None):
sort_keys=True)))
self._data = process(_data) if process is not None else _data
except (IOError, OSError):
- log_error('JSONStore.save |{filename}| no access to file'.format(
+ log_error('JSONStore.save - access error:\n|{filename}|'.format(
filename=self.filename
))
return
except (TypeError, ValueError):
- log_error('JSONStore.save |{data}| invalid data'.format(
+ log_error('JSONStore.save - invalid data:\n|{data}|'.format(
data=data
))
self.set_defaults(reset=True)
def load(self, process=None):
- log_debug('JSONStore.load |{filename}|'.format(
+ log_debug('JSONStore.load - loading:\n|{filename}|'.format(
filename=self.filename
))
try:
@@ -86,11 +86,11 @@ def load(self, process=None):
_data = json.loads(data)
self._data = process(_data) if process is not None else _data
except (IOError, OSError):
- log_error('JSONStore.load |{filename}| no access to file'.format(
+ log_error('JSONStore.load - access error:\n|{filename}|'.format(
filename=self.filename
))
except (TypeError, ValueError):
- log_error('JSONStore.load |{data}| invalid data'.format(
+ log_error('JSONStore.load - invalid data:\n|{data}|'.format(
data=data
))
@@ -101,7 +101,7 @@ def get_data(self, process=None):
_data = json.loads(json.dumps(self._data))
return process(_data) if process is not None else _data
except (TypeError, ValueError):
- log_error('JSONStore.get_data |{data}| invalid data'.format(
+ log_error('JSONStore.get_data - invalid data:\n|{data}|'.format(
data=self._data
))
self.set_defaults(reset=True)
From 9c19a29174c304a7ad2a29e9e03bfe57bb1b4a36 Mon Sep 17 00:00:00 2001
From: MoojMidge <56883549+MoojMidge@users.noreply.github.com>
Date: Tue, 12 Dec 2023 14:12:11 +1100
Subject: [PATCH 093/141] Add print_stats convenience method to debug.Profiler
---
resources/lib/youtube_plugin/kodion/debug.py | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/resources/lib/youtube_plugin/kodion/debug.py b/resources/lib/youtube_plugin/kodion/debug.py
index 3160c47ec..4c7b9aa9b 100644
--- a/resources/lib/youtube_plugin/kodion/debug.py
+++ b/resources/lib/youtube_plugin/kodion/debug.py
@@ -218,3 +218,8 @@ def get_stats(self, flush=True, reuse=False):
self.enable(flush)
return output
+
+ def print_stats(self):
+ log_debug('Profiling stats: {0}'.format(self.get_stats(
+ reuse=self._reuse
+ )))
From 13d71f01ccedfccd72c0544be34fe460ceb504bc Mon Sep 17 00:00:00 2001
From: MoojMidge <56883549+MoojMidge@users.noreply.github.com>
Date: Tue, 12 Dec 2023 16:17:52 +1100
Subject: [PATCH 094/141] Fix re-requesting blocked video info
- Cache empty video data
- May result in failed requests being cached for a month
- TODO: See whether this will require changes to cache expiration
---
.../lib/youtube_plugin/youtube/helper/resource_manager.py | 8 ++++----
resources/lib/youtube_plugin/youtube/helper/utils.py | 4 ++--
.../lib/youtube_plugin/youtube/helper/yt_specials.py | 8 +++++++-
3 files changed, 13 insertions(+), 7 deletions(-)
diff --git a/resources/lib/youtube_plugin/youtube/helper/resource_manager.py b/resources/lib/youtube_plugin/youtube/helper/resource_manager.py
index fab7c216e..a060b5981 100644
--- a/resources/lib/youtube_plugin/youtube/helper/resource_manager.py
+++ b/resources/lib/youtube_plugin/youtube/helper/resource_manager.py
@@ -110,11 +110,11 @@ def _update_videos(self, video_ids, live_details=False, suppress_errors=False):
if video_ids_to_update:
self._context.log_debug('No data for videos |%s| cached' % ', '.join(video_ids_to_update))
json_data = self._client.get_videos(video_ids_to_update, live_details)
- video_data = {
- yt_item['id']: yt_item
+ video_data = dict.fromkeys(video_ids_to_update, {})
+ video_data.update({
+ yt_item['id']: yt_item or {}
for yt_item in json_data.get('items', [])
- if yt_item
- }
+ })
result.update(video_data)
data_cache.set_items(video_data)
self._context.log_debug('Cached data for videos |%s|' % ', '.join(video_data))
diff --git a/resources/lib/youtube_plugin/youtube/helper/utils.py b/resources/lib/youtube_plugin/youtube/helper/utils.py
index 2262b6f9c..ab487387c 100644
--- a/resources/lib/youtube_plugin/youtube/helper/utils.py
+++ b/resources/lib/youtube_plugin/youtube/helper/utils.py
@@ -371,10 +371,10 @@ def update_video_infos(provider, context, video_id_dict,
# set mediatype
video_item.set_mediatype('video') # using video
- if not yt_item:
+ if not yt_item or 'snippet' not in yt_item:
continue
- snippet = yt_item['snippet'] # crash if not conform
+ snippet = yt_item['snippet']
play_data = use_play_data and yt_item.get('play_data')
broadcast_type = snippet.get('liveBroadcastContent')
video_item.live = broadcast_type == 'live'
diff --git a/resources/lib/youtube_plugin/youtube/helper/yt_specials.py b/resources/lib/youtube_plugin/youtube/helper/yt_specials.py
index f7744173d..5ddc33e9c 100644
--- a/resources/lib/youtube_plugin/youtube/helper/yt_specials.py
+++ b/resources/lib/youtube_plugin/youtube/helper/yt_specials.py
@@ -150,7 +150,13 @@ def _extract_urls(video_id):
video_data = resource_manager.get_videos([video_id])
yt_item = video_data[video_id]
- snippet = yt_item['snippet'] # crash if not conform
+ if not yt_item or 'snippet' not in yt_item:
+ context.get_ui().on_ok(
+ title=context.localize('video.description.links'),
+ text=context.localize('video.description.links.not_found')
+ )
+ return False
+ snippet = yt_item['snippet']
description = strip_html_from_text(snippet['description'])
function_cache = context.get_function_cache()
From 0f4f212ba12a6eab50b2f541e83f8e632af3af5c Mon Sep 17 00:00:00 2001
From: MoojMidge <56883549+MoojMidge@users.noreply.github.com>
Date: Thu, 14 Dec 2023 13:26:20 +1100
Subject: [PATCH 095/141] Misc tidy ups
- time_milliseconds parameter for notifications renamed to time_ms
- Updated imports
---
.../kodion/ui/abstract_context_ui.py | 2 +-
.../kodion/ui/xbmc/xbmc_context_ui.py | 4 +-
.../youtube/client/login_client.py | 6 +--
.../youtube/client/request_client.py | 4 +-
.../youtube/helper/ratebypass/__init__.py | 2 +-
.../youtube/helper/resource_manager.py | 2 +-
.../youtube/helper/signature/__init__.py | 2 +-
.../lib/youtube_plugin/youtube/helper/tv.py | 2 +-
.../youtube_plugin/youtube/helper/utils.py | 4 +-
.../lib/youtube_plugin/youtube/helper/v3.py | 4 +-
.../youtube_plugin/youtube/helper/yt_login.py | 3 +-
.../youtube/helper/yt_old_actions.py | 2 +-
.../youtube_plugin/youtube/helper/yt_play.py | 12 +++---
.../youtube/helper/yt_playlist.py | 41 ++++++++++---------
.../youtube/helper/yt_specials.py | 10 ++---
.../youtube/helper/yt_subscriptions.py | 10 ++---
.../youtube_plugin/youtube/helper/yt_video.py | 15 +++----
.../lib/youtube_plugin/youtube/provider.py | 6 +--
18 files changed, 67 insertions(+), 64 deletions(-)
diff --git a/resources/lib/youtube_plugin/kodion/ui/abstract_context_ui.py b/resources/lib/youtube_plugin/kodion/ui/abstract_context_ui.py
index efca1c1b2..1f5c05769 100644
--- a/resources/lib/youtube_plugin/kodion/ui/abstract_context_ui.py
+++ b/resources/lib/youtube_plugin/kodion/ui/abstract_context_ui.py
@@ -41,7 +41,7 @@ def open_settings(self):
raise NotImplementedError()
def show_notification(self, message, header='', image_uri='',
- time_milliseconds=5000, audible=True):
+ time_ms=5000, audible=True):
raise NotImplementedError()
@staticmethod
diff --git a/resources/lib/youtube_plugin/kodion/ui/xbmc/xbmc_context_ui.py b/resources/lib/youtube_plugin/kodion/ui/xbmc/xbmc_context_ui.py
index b3ffb53a0..da63599a7 100644
--- a/resources/lib/youtube_plugin/kodion/ui/xbmc/xbmc_context_ui.py
+++ b/resources/lib/youtube_plugin/kodion/ui/xbmc/xbmc_context_ui.py
@@ -114,7 +114,7 @@ def show_notification(self,
message,
header='',
image_uri='',
- time_milliseconds=5000,
+ time_ms=5000,
audible=True):
_header = header
if not _header:
@@ -129,7 +129,7 @@ def show_notification(self,
xbmcgui.Dialog().notification(_header,
_message,
_image,
- time_milliseconds,
+ time_ms,
audible)
def open_settings(self):
diff --git a/resources/lib/youtube_plugin/youtube/client/login_client.py b/resources/lib/youtube_plugin/youtube/client/login_client.py
index cf37fcfac..5249f4fc4 100644
--- a/resources/lib/youtube_plugin/youtube/client/login_client.py
+++ b/resources/lib/youtube_plugin/youtube/client/login_client.py
@@ -21,13 +21,13 @@
youtube_tv,
)
from .request_client import YouTubeRequestClient
-from ...kodion.compatibility import parse_qsl
-from ...kodion.logger import log_debug
-from ...youtube.youtube_exceptions import (
+from ..youtube_exceptions import (
InvalidGrant,
LoginException,
YouTubeException,
)
+from ...kodion.compatibility import parse_qsl
+from ...kodion.logger import log_debug
class LoginClient(YouTubeRequestClient):
diff --git a/resources/lib/youtube_plugin/youtube/client/request_client.py b/resources/lib/youtube_plugin/youtube/client/request_client.py
index 4bace16e9..42f438214 100644
--- a/resources/lib/youtube_plugin/youtube/client/request_client.py
+++ b/resources/lib/youtube_plugin/youtube/client/request_client.py
@@ -9,9 +9,9 @@
from __future__ import absolute_import, division, unicode_literals
-from ...kodion.utils import merge_dicts
+from ..youtube_exceptions import YouTubeException
from ...kodion.network import BaseRequestsClass
-from ...youtube.youtube_exceptions import YouTubeException
+from ...kodion.utils import merge_dicts
class YouTubeRequestClient(BaseRequestsClass):
diff --git a/resources/lib/youtube_plugin/youtube/helper/ratebypass/__init__.py b/resources/lib/youtube_plugin/youtube/helper/ratebypass/__init__.py
index 2dcf59c7d..3da983ffd 100644
--- a/resources/lib/youtube_plugin/youtube/helper/ratebypass/__init__.py
+++ b/resources/lib/youtube_plugin/youtube/helper/ratebypass/__init__.py
@@ -9,7 +9,7 @@
from __future__ import absolute_import, division, unicode_literals
-from ....youtube.helper.ratebypass import ratebypass
+from . import ratebypass
__all__ = ('ratebypass',)
diff --git a/resources/lib/youtube_plugin/youtube/helper/resource_manager.py b/resources/lib/youtube_plugin/youtube/helper/resource_manager.py
index a060b5981..60e3aa4b4 100644
--- a/resources/lib/youtube_plugin/youtube/helper/resource_manager.py
+++ b/resources/lib/youtube_plugin/youtube/helper/resource_manager.py
@@ -254,7 +254,7 @@ def handle_error(self, json_data, suppress_errors=False):
context.get_ui().on_ok(title, message)
else:
context.get_ui().show_notification(message, title,
- time_milliseconds=message_timeout)
+ time_ms=message_timeout)
raise YouTubeException(error_message)
diff --git a/resources/lib/youtube_plugin/youtube/helper/signature/__init__.py b/resources/lib/youtube_plugin/youtube/helper/signature/__init__.py
index 4370d5ee9..c3cf86311 100644
--- a/resources/lib/youtube_plugin/youtube/helper/signature/__init__.py
+++ b/resources/lib/youtube_plugin/youtube/helper/signature/__init__.py
@@ -10,7 +10,7 @@
from __future__ import absolute_import, division, unicode_literals
-from ....youtube.helper.signature.cipher import Cipher
+from .cipher import Cipher
__all__ = ('Cipher',)
diff --git a/resources/lib/youtube_plugin/youtube/helper/tv.py b/resources/lib/youtube_plugin/youtube/helper/tv.py
index 56a0ca9ea..716a151b1 100644
--- a/resources/lib/youtube_plugin/youtube/helper/tv.py
+++ b/resources/lib/youtube_plugin/youtube/helper/tv.py
@@ -10,8 +10,8 @@
from __future__ import absolute_import, division, unicode_literals
+from ..helper import utils
from ...kodion.items import DirectoryItem, NextPageItem, VideoItem
-from ...youtube.helper import utils
def my_subscriptions_to_items(provider, context, json_data, do_filter=False):
diff --git a/resources/lib/youtube_plugin/youtube/helper/utils.py b/resources/lib/youtube_plugin/youtube/helper/utils.py
index ab487387c..331846962 100644
--- a/resources/lib/youtube_plugin/youtube/helper/utils.py
+++ b/resources/lib/youtube_plugin/youtube/helper/utils.py
@@ -14,6 +14,7 @@
import time
from math import log10
+from ..helper import yt_context_menu
from ...kodion.items import DirectoryItem
from ...kodion.utils import (
create_path,
@@ -21,7 +22,6 @@
friendly_number,
strip_html_from_text,
)
-from ...youtube.helper import yt_context_menu
try:
@@ -794,7 +794,7 @@ def add_related_video_to_playlist(provider, context, client, v3, video_id):
result_items = v3.response_to_items(provider, context, json_data, process_next_page=False)
page_token = json_data.get('nextPageToken', '')
except:
- context.get_ui().show_notification('Failed to add a suggested video.', time_milliseconds=5000)
+ context.get_ui().show_notification('Failed to add a suggested video.', time_ms=5000)
if result_items:
add_item = next((
diff --git a/resources/lib/youtube_plugin/youtube/helper/v3.py b/resources/lib/youtube_plugin/youtube/helper/v3.py
index 713435b16..22e6ac0dd 100644
--- a/resources/lib/youtube_plugin/youtube/helper/v3.py
+++ b/resources/lib/youtube_plugin/youtube/helper/v3.py
@@ -21,10 +21,10 @@
update_playlist_infos,
update_video_infos,
)
+from ..helper import yt_context_menu
from ...kodion import KodionException
from ...kodion.items import DirectoryItem, NextPageItem, VideoItem
from ...kodion.utils import strip_html_from_text
-from ...youtube.helper import yt_context_menu
def _process_list_response(provider, context, json_data):
@@ -457,7 +457,7 @@ def handle_error(context, json_data):
if ok_dialog:
context.get_ui().on_ok(title, message)
else:
- context.get_ui().show_notification(message, title, time_milliseconds=message_timeout)
+ context.get_ui().show_notification(message, title, time_ms=message_timeout)
return False
diff --git a/resources/lib/youtube_plugin/youtube/helper/yt_login.py b/resources/lib/youtube_plugin/youtube/helper/yt_login.py
index 81bcece3c..4afb9992a 100644
--- a/resources/lib/youtube_plugin/youtube/helper/yt_login.py
+++ b/resources/lib/youtube_plugin/youtube/helper/yt_login.py
@@ -13,7 +13,8 @@
import copy
import json
import time
-from ...youtube.youtube_exceptions import LoginException
+
+from ..youtube_exceptions import LoginException
def process(mode, provider, context, sign_out_refresh=True):
diff --git a/resources/lib/youtube_plugin/youtube/helper/yt_old_actions.py b/resources/lib/youtube_plugin/youtube/helper/yt_old_actions.py
index 0ed81368e..c7501eb77 100644
--- a/resources/lib/youtube_plugin/youtube/helper/yt_old_actions.py
+++ b/resources/lib/youtube_plugin/youtube/helper/yt_old_actions.py
@@ -55,7 +55,7 @@ def process_old_action(provider, context, re_match):
"""
if context.get_system_version().get_version() >= (15, 0):
message = u"You're using old YouTube-Plugin calls - please review the log for updated end points starting with Isengard"
- context.get_ui().show_notification(message, time_milliseconds=15000)
+ context.get_ui().show_notification(message, time_ms=15000)
"""
action = context.get_param('action', '')
diff --git a/resources/lib/youtube_plugin/youtube/helper/yt_play.py b/resources/lib/youtube_plugin/youtube/helper/yt_play.py
index 88a327afb..f6d4cdb76 100644
--- a/resources/lib/youtube_plugin/youtube/helper/yt_play.py
+++ b/resources/lib/youtube_plugin/youtube/helper/yt_play.py
@@ -14,10 +14,10 @@
import random
import traceback
-from ... import kodion
+from ..helper import utils, v3
+from ..youtube_exceptions import YouTubeException
from ...kodion.items import VideoItem
-from ...youtube.helper import utils, v3
-from ...youtube.youtube_exceptions import YouTubeException
+from ...kodion.utils import select_stream
def play_video(provider, context):
@@ -53,10 +53,10 @@ def play_video(provider, context):
if not video_streams:
message = context.localize('error.no_video_streams_found')
- ui.show_notification(message, time_milliseconds=5000)
+ ui.show_notification(message, time_ms=5000)
return False
- video_stream = kodion.utils.select_stream(
+ video_stream = select_stream(
context,
video_streams,
ask_for_quality=ask_for_quality,
@@ -71,7 +71,7 @@ def play_video(provider, context):
if is_video and video_stream['video'].get('rtmpe', False):
message = context.localize('error.rtmpe_not_supported')
- ui.show_notification(message, time_milliseconds=5000)
+ ui.show_notification(message, time_ms=5000)
return False
play_suggested = settings.get_bool('youtube.suggested_videos', False)
diff --git a/resources/lib/youtube_plugin/youtube/helper/yt_playlist.py b/resources/lib/youtube_plugin/youtube/helper/yt_playlist.py
index c0dfa676d..ffb69fdb3 100644
--- a/resources/lib/youtube_plugin/youtube/helper/yt_playlist.py
+++ b/resources/lib/youtube_plugin/youtube/helper/yt_playlist.py
@@ -10,8 +10,9 @@
from __future__ import absolute_import, division, unicode_literals
-from ... import kodion
-from ...youtube.helper import v3
+from ..helper import v3
+from ...kodion import KodionException
+from ...kodion.utils import find_video_id
def _process_add_video(provider, context, keymap_action=False):
@@ -22,7 +23,7 @@ def _process_add_video(provider, context, keymap_action=False):
playlist_id = context.get_param('playlist_id', '')
if not playlist_id:
- raise kodion.KodionException('Playlist/Add: missing playlist_id')
+ raise KodionException('Playlist/Add: missing playlist_id')
if playlist_id.lower() == 'watch_later':
playlist_id = watch_later_id
@@ -30,10 +31,10 @@ def _process_add_video(provider, context, keymap_action=False):
video_id = context.get_param('video_id', '')
if not video_id:
if context.is_plugin_path(listitem_path, 'play/'):
- video_id = kodion.utils.find_video_id(listitem_path)
+ video_id = find_video_id(listitem_path)
keymap_action = True
if not video_id:
- raise kodion.KodionException('Playlist/Add: missing video_id')
+ raise KodionException('Playlist/Add: missing video_id')
if playlist_id != 'HL':
json_data = client.add_video_to_playlist(playlist_id=playlist_id, video_id=video_id)
@@ -47,7 +48,7 @@ def _process_add_video(provider, context, keymap_action=False):
context.get_ui().show_notification(
message=notify_message,
- time_milliseconds=2500,
+ time_ms=2500,
audible=False
)
@@ -80,16 +81,16 @@ def _process_remove_video(provider, context):
keymap_action = True
if not playlist_id:
- raise kodion.KodionException('Playlist/Remove: missing playlist_id')
+ raise KodionException('Playlist/Remove: missing playlist_id')
if not video_id:
- raise kodion.KodionException('Playlist/Remove: missing video_id')
+ raise KodionException('Playlist/Remove: missing video_id')
if not video_name:
if listitem_title:
video_name = listitem_title
else:
- raise kodion.KodionException('Playlist/Remove: missing video_name')
+ raise KodionException('Playlist/Remove: missing video_name')
if playlist_id != 'HL' and playlist_id.strip().lower() != 'wl':
if context.get_ui().on_remove_content(video_name):
@@ -102,7 +103,7 @@ def _process_remove_video(provider, context):
context.get_ui().show_notification(
message=context.localize('playlist.removed_from'),
- time_milliseconds=2500,
+ time_ms=2500,
audible=False
)
@@ -119,11 +120,11 @@ def _process_remove_video(provider, context):
def _process_remove_playlist(provider, context):
playlist_id = context.get_param('playlist_id', '')
if not playlist_id:
- raise kodion.KodionException('Playlist/Remove: missing playlist_id')
+ raise KodionException('Playlist/Remove: missing playlist_id')
playlist_name = context.get_param('playlist_name', '')
if not playlist_name:
- raise kodion.KodionException('Playlist/Remove: missing playlist_name')
+ raise KodionException('Playlist/Remove: missing playlist_name')
if context.get_ui().on_delete_content(playlist_name):
json_data = provider.get_client(context).remove_playlist(playlist_id=playlist_id)
@@ -144,12 +145,12 @@ def _process_select_playlist(provider, context):
video_id = context.get_param('video_id', '')
if not video_id:
if context.is_plugin_path(listitem_path, 'play/'):
- video_id = kodion.utils.find_video_id(listitem_path)
+ video_id = find_video_id(listitem_path)
if video_id:
context.set_param('video_id', video_id)
keymap_action = True
if not video_id:
- raise kodion.KodionException('Playlist/Select: missing video_id')
+ raise KodionException('Playlist/Select: missing video_id')
function_cache = context.get_function_cache()
client = provider.get_client(context)
@@ -228,7 +229,7 @@ def _process_select_playlist(provider, context):
def _process_rename_playlist(provider, context):
playlist_id = context.get_param('playlist_id', '')
if not playlist_id:
- raise kodion.KodionException('playlist/rename: missing playlist_id')
+ raise KodionException('playlist/rename: missing playlist_id')
current_playlist_name = context.get_param('playlist_name', '')
result, text = context.get_ui().on_keyboard_input(context.localize('rename'),
@@ -244,10 +245,10 @@ def _process_rename_playlist(provider, context):
def _watchlater_playlist_id_change(context, method):
playlist_id = context.get_param('playlist_id', '')
if not playlist_id:
- raise kodion.KodionException('watchlater_list/%s: missing playlist_id' % method)
+ raise KodionException('watchlater_list/%s: missing playlist_id' % method)
playlist_name = context.get_param('playlist_name', '')
if not playlist_name:
- raise kodion.KodionException('watchlater_list/%s: missing playlist_name' % method)
+ raise KodionException('watchlater_list/%s: missing playlist_name' % method)
if method == 'set':
if context.get_ui().on_yes_no_input(context.get_name(), context.localize('watch_later.list.set.confirm') % playlist_name):
@@ -267,10 +268,10 @@ def _watchlater_playlist_id_change(context, method):
def _history_playlist_id_change(context, method):
playlist_id = context.get_param('playlist_id', '')
if not playlist_id:
- raise kodion.KodionException('history_list/%s: missing playlist_id' % method)
+ raise KodionException('history_list/%s: missing playlist_id' % method)
playlist_name = context.get_param('playlist_name', '')
if not playlist_name:
- raise kodion.KodionException('history_list/%s: missing playlist_name' % method)
+ raise KodionException('history_list/%s: missing playlist_name' % method)
if method == 'set':
if context.get_ui().on_yes_no_input(context.get_name(), context.localize('history.list.set.confirm') % playlist_name):
@@ -302,4 +303,4 @@ def process(method, category, provider, context):
return _watchlater_playlist_id_change(context, method)
if method in {'set', 'remove'} and category == 'history':
return _history_playlist_id_change(context, method)
- raise kodion.KodionException("Unknown category '%s' or method '%s'" % (category, method))
+ raise KodionException("Unknown category '%s' or method '%s'" % (category, method))
diff --git a/resources/lib/youtube_plugin/youtube/helper/yt_specials.py b/resources/lib/youtube_plugin/youtube/helper/yt_specials.py
index 5ddc33e9c..836a708fb 100644
--- a/resources/lib/youtube_plugin/youtube/helper/yt_specials.py
+++ b/resources/lib/youtube_plugin/youtube/helper/yt_specials.py
@@ -11,17 +11,17 @@
from __future__ import absolute_import, division, unicode_literals
from . import utils
-from ...kodion import KodionException
-from ...kodion.constants import content_type
-from ...kodion.items import DirectoryItem, UriItem
-from ...kodion.utils import strip_html_from_text
-from ...youtube.helper import (
+from ..helper import (
UrlResolver,
UrlToItemConverter,
extract_urls,
tv,
v3,
)
+from ...kodion import KodionException
+from ...kodion.constants import content_type
+from ...kodion.items import DirectoryItem, UriItem
+from ...kodion.utils import strip_html_from_text
def _process_related_videos(provider, context):
diff --git a/resources/lib/youtube_plugin/youtube/helper/yt_subscriptions.py b/resources/lib/youtube_plugin/youtube/helper/yt_subscriptions.py
index 24da6925c..c90893947 100644
--- a/resources/lib/youtube_plugin/youtube/helper/yt_subscriptions.py
+++ b/resources/lib/youtube_plugin/youtube/helper/yt_subscriptions.py
@@ -10,9 +10,9 @@
from __future__ import absolute_import, division, unicode_literals
+from ..helper import v3
+from ...kodion import KodionException
from ...kodion.items import UriItem
-from ... import kodion
-from ...youtube.helper import v3
def _process_list(provider, context):
@@ -43,7 +43,7 @@ def _process_add(provider, context):
context.get_ui().show_notification(
context.localize('subscribed.to.channel'),
- time_milliseconds=2500,
+ time_ms=2500,
audible=False
)
@@ -68,7 +68,7 @@ def _process_remove(provider, context):
context.get_ui().show_notification(
context.localize('unsubscribed.from.channel'),
- time_milliseconds=2500,
+ time_ms=2500,
audible=False
)
@@ -92,6 +92,6 @@ def process(method, provider, context):
elif method == 'remove':
return _process_remove(provider, context)
else:
- raise kodion.KodionException("Unknown subscriptions method '%s'" % method)
+ raise KodionException("Unknown subscriptions method '%s'" % method)
return result
diff --git a/resources/lib/youtube_plugin/youtube/helper/yt_video.py b/resources/lib/youtube_plugin/youtube/helper/yt_video.py
index 699263a9e..63c517875 100644
--- a/resources/lib/youtube_plugin/youtube/helper/yt_video.py
+++ b/resources/lib/youtube_plugin/youtube/helper/yt_video.py
@@ -10,8 +10,9 @@
from __future__ import absolute_import, division, unicode_literals
-from ... import kodion
-from ...youtube.helper import v3
+from ..helper import v3
+from ...kodion import KodionException
+from ...kodion.utils import find_video_id
def _process_rate_video(provider, context, re_match):
@@ -28,10 +29,10 @@ def _process_rate_video(provider, context, re_match):
video_id = re_match.group('video_id')
except IndexError:
if context.is_plugin_path(listitem_path, 'play/'):
- video_id = kodion.utils.find_video_id(listitem_path)
+ video_id = find_video_id(listitem_path)
if not video_id:
- raise kodion.KodionException('video/rate/: missing video_id')
+ raise KodionException('video/rate/: missing video_id')
try:
current_rating = re_match.group('rating')
@@ -82,7 +83,7 @@ def _process_rate_video(provider, context, re_match):
if notify_message:
context.get_ui().show_notification(
message=notify_message,
- time_milliseconds=2500,
+ time_ms=2500,
audible=False
)
@@ -92,7 +93,7 @@ def _process_rate_video(provider, context, re_match):
def _process_more_for_video(context):
video_id = context.get_param('video_id', '')
if not video_id:
- raise kodion.KodionException('video/more/: missing video_id')
+ raise KodionException('video/more/: missing video_id')
items = []
@@ -128,4 +129,4 @@ def process(method, provider, context, re_match):
return _process_rate_video(provider, context, re_match)
if method == 'more':
return _process_more_for_video(context)
- raise kodion.KodionException("Unknown method '%s'" % method)
+ raise KodionException("Unknown method '%s'" % method)
diff --git a/resources/lib/youtube_plugin/youtube/provider.py b/resources/lib/youtube_plugin/youtube/provider.py
index 8c70cb240..d0ae46420 100644
--- a/resources/lib/youtube_plugin/youtube/provider.py
+++ b/resources/lib/youtube_plugin/youtube/provider.py
@@ -17,6 +17,7 @@
import socket
from base64 import b64decode
+from .client import YouTube
from .helper import (
ResourceManager,
UrlResolver,
@@ -29,6 +30,7 @@
yt_playlist,
yt_setup_wizard,
yt_specials,
+ yt_subscriptions,
yt_video,
)
from .youtube_exceptions import InvalidGrant, LoginException
@@ -37,8 +39,6 @@
from ..kodion.items import DirectoryItem, NewSearchItem, SearchItem
from ..kodion.network import get_client_ip_address, is_httpd_live
from ..kodion.utils import find_video_id, strip_html_from_text
-from ..youtube.client import YouTube
-from ..youtube.helper import yt_subscriptions
class Provider(AbstractProvider):
@@ -1513,7 +1513,7 @@ def handle_exception(self, context, exception_to_handle):
if ok_dialog:
context.get_ui().on_ok(title, message)
else:
- context.get_ui().show_notification(message, title, time_milliseconds=message_timeout)
+ context.get_ui().show_notification(message, title, time_ms=message_timeout)
return False
From 82d4441f108c4f4a5ece331b7cbe353891529dd8 Mon Sep 17 00:00:00 2001
From: MoojMidge <56883549+MoojMidge@users.noreply.github.com>
Date: Thu, 14 Dec 2023 13:27:21 +1100
Subject: [PATCH 096/141] Remove unused code
- Introduced as part of reverted tags PR
---
.../youtube/helper/yt_specials.py | 18 ------------------
1 file changed, 18 deletions(-)
diff --git a/resources/lib/youtube_plugin/youtube/helper/yt_specials.py b/resources/lib/youtube_plugin/youtube/helper/yt_specials.py
index 836a708fb..ea2e29a1a 100644
--- a/resources/lib/youtube_plugin/youtube/helper/yt_specials.py
+++ b/resources/lib/youtube_plugin/youtube/helper/yt_specials.py
@@ -270,24 +270,6 @@ def _process_saved_playlists_tv(provider, context):
return tv.saved_playlists_to_items(provider, context, json_data)
-def _process_watch_history_tv(provider, context):
- provider.set_content_type(context, content_type.VIDEOS)
- json_data = provider.get_client(context).get_watch_history(
- page_token=context.get_param('next_page_token', ''),
- offset=context.get_param('offset', 0)
- )
- return tv.tv_videos_to_items(provider, context, json_data)
-
-
-def _process_purchases_tv(provider, context):
- provider.set_content_type(context, content_type.VIDEOS)
- json_data = provider.get_client(context).get_purchases(
- page_token=context.get_param('next_page_token', ''),
- offset=context.get_param('offset', 0)
- )
- return tv.tv_videos_to_items(provider, context, json_data)
-
-
def _process_new_uploaded_videos_tv(provider, context):
provider.set_content_type(context, content_type.VIDEOS)
json_data = provider.get_client(context).get_my_subscriptions(
From e2210d88c30a7eec9361809b75dd20c99ac5d195 Mon Sep 17 00:00:00 2001
From: MoojMidge <56883549+MoojMidge@users.noreply.github.com>
Date: Thu, 14 Dec 2023 13:49:16 +1100
Subject: [PATCH 097/141] Create custom InvalidJSON exception
- Inherits from requests.exceptions.InvalidJSONError and KodionException
---
resources/lib/youtube_plugin/kodion/network/__init__.py | 3 ++-
resources/lib/youtube_plugin/kodion/network/requests.py | 5 ++++-
resources/lib/youtube_plugin/youtube/client/login_client.py | 5 ++---
resources/lib/youtube_plugin/youtube/youtube_exceptions.py | 5 +++++
4 files changed, 13 insertions(+), 5 deletions(-)
diff --git a/resources/lib/youtube_plugin/kodion/network/__init__.py b/resources/lib/youtube_plugin/kodion/network/__init__.py
index 960dd0e91..45fe2ae78 100644
--- a/resources/lib/youtube_plugin/kodion/network/__init__.py
+++ b/resources/lib/youtube_plugin/kodion/network/__init__.py
@@ -11,7 +11,7 @@
from .http_server import get_client_ip_address, get_http_server, is_httpd_live
from .ip_api import Locator
-from .requests import BaseRequestsClass
+from .requests import BaseRequestsClass, InvalidJSONError
__all__ = (
@@ -19,5 +19,6 @@
'get_http_server',
'is_httpd_live',
'BaseRequestsClass',
+ 'InvalidJSONError',
'Locator',
)
diff --git a/resources/lib/youtube_plugin/kodion/network/requests.py b/resources/lib/youtube_plugin/kodion/network/requests.py
index c41473b6a..bdd2981a5 100644
--- a/resources/lib/youtube_plugin/kodion/network/requests.py
+++ b/resources/lib/youtube_plugin/kodion/network/requests.py
@@ -14,7 +14,7 @@
from requests import Session
from requests.adapters import HTTPAdapter, Retry
-from requests.exceptions import RequestException
+from requests.exceptions import InvalidJSONError, RequestException
from ..compatibility import xbmcaddon
from ..logger import log_error
@@ -143,3 +143,6 @@ def request(self, url, method='GET',
raise self._default_exc(error_title)(exc)
return response
+
+
+__all__ = ('BaseRequestsClass', 'InvalidJSONError')
diff --git a/resources/lib/youtube_plugin/youtube/client/login_client.py b/resources/lib/youtube_plugin/youtube/client/login_client.py
index 5249f4fc4..90205c3bb 100644
--- a/resources/lib/youtube_plugin/youtube/client/login_client.py
+++ b/resources/lib/youtube_plugin/youtube/client/login_client.py
@@ -12,8 +12,6 @@
import time
-from requests.exceptions import InvalidJSONError
-
from .__config__ import (
api,
developer_keys,
@@ -23,6 +21,7 @@
from .request_client import YouTubeRequestClient
from ..youtube_exceptions import (
InvalidGrant,
+ InvalidJSON,
LoginException,
YouTubeException,
)
@@ -91,7 +90,7 @@ def _login_json_hook(response):
json_data=json_data,
response=response)
except ValueError as error:
- raise InvalidJSONError(error, response=response)
+ raise InvalidJSON(error, response=response)
response.raise_for_status()
return json_data
diff --git a/resources/lib/youtube_plugin/youtube/youtube_exceptions.py b/resources/lib/youtube_plugin/youtube/youtube_exceptions.py
index fe4e24f91..761187896 100644
--- a/resources/lib/youtube_plugin/youtube/youtube_exceptions.py
+++ b/resources/lib/youtube_plugin/youtube/youtube_exceptions.py
@@ -11,6 +11,7 @@
from __future__ import absolute_import, division, unicode_literals
from ..kodion import KodionException
+from ..kodion.network import InvalidJSONError
class LoginException(KodionException):
@@ -23,3 +24,7 @@ class YouTubeException(KodionException):
class InvalidGrant(KodionException):
pass
+
+
+class InvalidJSON(KodionException, InvalidJSONError):
+ pass
From ba686a520cb99c9cb27a944af5fb3d541daf6417 Mon Sep 17 00:00:00 2001
From: MoojMidge <56883549+MoojMidge@users.noreply.github.com>
Date: Thu, 14 Dec 2023 17:04:16 +1100
Subject: [PATCH 098/141] Fixup log out on key change after b3b08d2
---
.../youtube/client/__config__.py | 34 ++++++++-----------
1 file changed, 14 insertions(+), 20 deletions(-)
diff --git a/resources/lib/youtube_plugin/youtube/client/__config__.py b/resources/lib/youtube_plugin/youtube/client/__config__.py
index e6279c3f1..f2946e7aa 100644
--- a/resources/lib/youtube_plugin/youtube/client/__config__.py
+++ b/resources/lib/youtube_plugin/youtube/client/__config__.py
@@ -88,7 +88,14 @@ def _on_init(self):
refresh_token = self._settings.user_refresh_token()
token_expires = self._settings.user_token_expiration()
last_hash = self._settings.api_last_hash()
- updated_hash = self._api_keys_changed(switch)
+
+ current_set_hash = self._get_key_set_hash(switch)
+ if current_set_hash != user_details.get('last_key_hash', ''):
+ self.changed = True
+ updated_hash = current_set_hash
+ else:
+ self.changed = False
+ updated_hash = None
if access_token or refresh_token or last_hash:
self._settings.user_access_token('')
@@ -99,26 +106,23 @@ def _on_init(self):
if updated_hash or (access_token and refresh_token
and not (user_details.get('access_token')
and user_details.get('refresh_token'))):
- if switch == 'own':
- own_key_hash = self._get_key_set_hash('own')
- if (last_hash == self._get_key_set_hash('own', True)
- or last_hash == own_key_hash):
- last_hash = own_key_hash
- else:
- last_hash = None
- else:
+ if (last_hash and switch == 'own' and (last_hash == current_set_hash
+ or last_hash == self._get_key_set_hash('own', old=True))):
last_hash = None
if updated_hash:
- last_hash = updated_hash
self._context.log_warning('User: |{user}|, '
'Switching API key set to: |{switch}|'
.format(user=self.get_current_user(),
switch=switch))
+
+ if last_hash:
self._context.log_debug('API key set changed: Signing out')
self._context.execute('RunPlugin(plugin://plugin.video.youtube/'
'sign/out/?confirmed=true)')
+ last_hash = updated_hash if updated_hash else current_set_hash
+
self._access_manager.update_access_token(
access_token, token_expires, refresh_token, last_hash
)
@@ -173,16 +177,6 @@ def get_api_keys(self, switch):
'id': ''.join((client_id, '.apps.googleusercontent.com')),
'secret': client_secret}
- def _api_keys_changed(self, switch):
- user_details = self._access_manager.get_current_user_details()
- last_set_hash = user_details.get('last_key_hash', '')
- current_set_hash = self._get_key_set_hash(switch)
- if last_set_hash != current_set_hash:
- self.changed = True
- return current_set_hash
- self.changed = False
- return None
-
def _get_key_set_hash(self, switch, old=False):
api_key, client_id, client_secret = self.get_api_keys(switch)
if old and switch == 'own':
From a58f31ee78dcaf00b3961f947727483f98cf4024 Mon Sep 17 00:00:00 2001
From: MoojMidge <56883549+MoojMidge@users.noreply.github.com>
Date: Thu, 14 Dec 2023 21:59:51 +1100
Subject: [PATCH 099/141] Remove all key set setting related functions
- Fixes after ba686a5 and b3b08d2
- Functionality was deprecated and should have been removed not refactored
---
.../kodion/json_store/access_manager.py | 36 +++++----
.../youtube/client/__config__.py | 80 +++++--------------
2 files changed, 42 insertions(+), 74 deletions(-)
diff --git a/resources/lib/youtube_plugin/kodion/json_store/access_manager.py b/resources/lib/youtube_plugin/kodion/json_store/access_manager.py
index f4d152802..82eb9febf 100644
--- a/resources/lib/youtube_plugin/kodion/json_store/access_manager.py
+++ b/resources/lib/youtube_plugin/kodion/json_store/access_manager.py
@@ -131,7 +131,7 @@ def get_current_user_details(self):
"""
:return: current user
"""
- return self.get_users()[self._user]
+ return self.get_users()[self._user].copy()
def get_current_user_id(self):
"""
@@ -435,14 +435,12 @@ def is_access_token_expired(self):
def update_access_token(self,
access_token,
unix_timestamp=None,
- refresh_token=None,
- last_key_hash=None):
+ refresh_token=None):
"""
Updates the old access token with the new one.
:param access_token:
:param unix_timestamp:
:param refresh_token:
- :param last_key_hash:
:return:
"""
current_user = self.get_current_user_details()
@@ -454,9 +452,18 @@ def update_access_token(self,
if refresh_token is not None:
current_user['refresh_token'] = refresh_token
- if last_key_hash is not None:
- current_user['last_key_hash'] = last_key_hash
+ data = {
+ 'access_manager': {
+ 'users': {
+ self._user: current_user
+ }
+ }
+ }
+ self.save(data, update=True)
+ def set_last_key_hash(self, key_hash):
+ current_user = self.get_current_user_details()
+ current_user['last_key_hash'] = key_hash
data = {
'access_manager': {
'users': {
@@ -586,7 +593,7 @@ def set_dev_last_key_hash(self, addon_id, key_hash):
def dev_keys_changed(self, addon_id, api_key, client_id, client_secret):
last_hash = self.get_dev_last_key_hash(addon_id)
- current_hash = self.__calc_key_hash(api_key, client_id, client_secret)
+ current_hash = self.calc_key_hash(api_key, client_id, client_secret)
if not last_hash and current_hash:
self.set_dev_last_key_hash(addon_id, current_hash)
@@ -599,16 +606,15 @@ def dev_keys_changed(self, addon_id, api_key, client_id, client_secret):
return False
@staticmethod
- def __calc_key_hash(api_key, client_id, client_secret):
-
+ def calc_key_hash(key, id, secret):
md5_hash = md5()
try:
- md5_hash.update(api_key.encode('utf-8'))
- md5_hash.update(client_id.encode('utf-8'))
- md5_hash.update(client_secret.encode('utf-8'))
+ md5_hash.update(key.encode('utf-8'))
+ md5_hash.update(id.encode('utf-8'))
+ md5_hash.update(secret.encode('utf-8'))
except:
- md5_hash.update(api_key)
- md5_hash.update(client_id)
- md5_hash.update(client_secret)
+ md5_hash.update(key)
+ md5_hash.update(id)
+ md5_hash.update(secret)
return md5_hash.hexdigest()
diff --git a/resources/lib/youtube_plugin/youtube/client/__config__.py b/resources/lib/youtube_plugin/youtube/client/__config__.py
index f2946e7aa..fbe3eaf3f 100644
--- a/resources/lib/youtube_plugin/youtube/client/__config__.py
+++ b/resources/lib/youtube_plugin/youtube/client/__config__.py
@@ -10,7 +10,6 @@
from __future__ import absolute_import, division, unicode_literals
from base64 import b64decode
-from hashlib import md5
from ... import key_sets
from ...kodion import Context
@@ -83,54 +82,19 @@ def _on_init(self):
switch = self.get_current_switch()
user_details = self._access_manager.get_current_user_details()
-
- access_token = self._settings.user_access_token()
- refresh_token = self._settings.user_refresh_token()
- token_expires = self._settings.user_token_expiration()
- last_hash = self._settings.api_last_hash()
-
+ last_hash = user_details.get('last_key_hash', '')
current_set_hash = self._get_key_set_hash(switch)
- if current_set_hash != user_details.get('last_key_hash', ''):
- self.changed = True
- updated_hash = current_set_hash
- else:
- self.changed = False
- updated_hash = None
-
- if access_token or refresh_token or last_hash:
- self._settings.user_access_token('')
- self._settings.user_refresh_token('')
- self._settings.user_token_expiration(-1)
- self._settings.api_last_hash('')
-
- if updated_hash or (access_token and refresh_token
- and not (user_details.get('access_token')
- and user_details.get('refresh_token'))):
- if (last_hash and switch == 'own' and (last_hash == current_set_hash
- or last_hash == self._get_key_set_hash('own', old=True))):
- last_hash = None
-
- if updated_hash:
- self._context.log_warning('User: |{user}|, '
- 'Switching API key set to: |{switch}|'
- .format(user=self.get_current_user(),
- switch=switch))
-
- if last_hash:
- self._context.log_debug('API key set changed: Signing out')
- self._context.execute('RunPlugin(plugin://plugin.video.youtube/'
- 'sign/out/?confirmed=true)')
-
- last_hash = updated_hash if updated_hash else current_set_hash
-
- self._access_manager.update_access_token(
- access_token, token_expires, refresh_token, last_hash
- )
- elif not updated_hash:
- self._context.log_debug('User: |{user}|, '
- 'Using API key set: |{switch}|'
- .format(user=self.get_current_user(),
- switch=switch))
+ self.changed = current_set_hash != last_hash
+
+ self._context.log_debug('User: |{user}|, '
+ 'Using API key set: |{switch}|'
+ .format(user=self.get_current_user(),
+ switch=switch))
+ if self.changed:
+ self._context.log_debug('API key set changed: Signing out')
+ self._context.execute('RunPlugin(plugin://plugin.video.youtube/'
+ 'sign/out/?confirmed=true)')
+ self._access_manager.set_last_key_hash(current_set_hash)
@staticmethod
def get_current_switch():
@@ -173,20 +137,18 @@ def get_api_keys(self, switch):
client_id = b64decode(client_id).decode('utf-8')
client_secret = b64decode(client_secret).decode('utf-8')
+ client_id += '.apps.googleusercontent.com'
return {'key': api_key,
- 'id': ''.join((client_id, '.apps.googleusercontent.com')),
+ 'id': client_id,
'secret': client_secret}
- def _get_key_set_hash(self, switch, old=False):
- api_key, client_id, client_secret = self.get_api_keys(switch)
- if old and switch == 'own':
- client_id = client_id.replace('.apps.googleusercontent.com', '')
- md5_hash = md5()
- md5_hash.update(api_key.encode('utf-8'))
- md5_hash.update(client_id.encode('utf-8'))
- md5_hash.update(client_secret.encode('utf-8'))
-
- return md5_hash.hexdigest()
+ def _get_key_set_hash(self, switch):
+ key_set = self.get_api_keys(switch)
+ if switch == 'own':
+ client_id = key_set['id'].replace('.apps.googleusercontent.com',
+ '')
+ key_set['id'] = client_id
+ return self._access_manager.calc_key_hash(**key_set)
def _strip_api_keys(self, api_key, client_id, client_secret):
From 14e78890638231fa7bbb5c5db005030f4dd895f5 Mon Sep 17 00:00:00 2001
From: MoojMidge <56883549+MoojMidge@users.noreply.github.com>
Date: Thu, 14 Dec 2023 22:28:59 +1100
Subject: [PATCH 100/141] Reduce whitespace in description
- Looks worse but there is limited space on many list/widget views
---
resources/lib/youtube_plugin/youtube/helper/utils.py | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/resources/lib/youtube_plugin/youtube/helper/utils.py b/resources/lib/youtube_plugin/youtube/helper/utils.py
index 331846962..2efeb40ca 100644
--- a/resources/lib/youtube_plugin/youtube/helper/utils.py
+++ b/resources/lib/youtube_plugin/youtube/helper/utils.py
@@ -506,11 +506,10 @@ def update_video_infos(provider, context, video_id_dict,
description = strip_html_from_text(snippet['description'])
if show_details:
description = ''.join((
- ui.bold(channel_name, cr_after=2) if channel_name else '',
+ ui.bold(channel_name, cr_after=1) if channel_name else '',
ui.new_line(stats, cr_after=1) if stats else '',
(ui.italic(start_at, cr_after=1) if video_item.upcoming
else ui.new_line(start_at, cr_after=1)) if start_at else '',
- ui.new_line() if stats or start_at else '',
description,
))
video_item.set_studio(channel_name)
From f7590a0ecdc5d5f37d615dc36cf148e2e700fef0 Mon Sep 17 00:00:00 2001
From: MoojMidge <56883549+MoojMidge@users.noreply.github.com>
Date: Thu, 14 Dec 2023 23:11:19 +1100
Subject: [PATCH 101/141] Fix not updating channel fanart after 0889e7b
---
.../lib/youtube_plugin/youtube/helper/utils.py | 2 +-
resources/lib/youtube_plugin/youtube/helper/v3.py | 15 +++++++++------
2 files changed, 10 insertions(+), 7 deletions(-)
diff --git a/resources/lib/youtube_plugin/youtube/helper/utils.py b/resources/lib/youtube_plugin/youtube/helper/utils.py
index 2efeb40ca..d2ccf5439 100644
--- a/resources/lib/youtube_plugin/youtube/helper/utils.py
+++ b/resources/lib/youtube_plugin/youtube/helper/utils.py
@@ -543,7 +543,7 @@ def update_video_infos(provider, context, video_id_dict,
# update channel mapping
channel_id = snippet.get('channelId', '')
- if channel_items_dict is not None:
+ if channel_id and channel_items_dict is not None:
if channel_id not in channel_items_dict:
channel_items_dict[channel_id] = []
channel_items_dict[channel_id].append(video_item)
diff --git a/resources/lib/youtube_plugin/youtube/helper/v3.py b/resources/lib/youtube_plugin/youtube/helper/v3.py
index 22e6ac0dd..e0e66f463 100644
--- a/resources/lib/youtube_plugin/youtube/helper/v3.py
+++ b/resources/lib/youtube_plugin/youtube/helper/v3.py
@@ -341,17 +341,20 @@ def _fetch(resource):
resource['updater'](*resource['upd_args'], **resource['upd_kwargs'])
for resource in resources:
+ if resource['defer']:
+ running += 1
+ continue
+
if not resource['args'][0]:
resource['complete'] = True
continue
running += 1
- if not resource['defer']:
- # _fetch(resource)
- thread = Thread(target=_fetch, args=(resource, ))
- thread.daemon = True
- thread.start()
- resource['thread'] = thread
+ # _fetch(resource)
+ thread = Thread(target=_fetch, args=(resource, ))
+ thread.daemon = True
+ thread.start()
+ resource['thread'] = thread
while running > 0:
for resource in resources:
From f50468183c3208ecf6c7380c889734e7795742e7 Mon Sep 17 00:00:00 2001
From: MoojMidge <56883549+MoojMidge@users.noreply.github.com>
Date: Fri, 15 Dec 2023 05:32:58 +1100
Subject: [PATCH 102/141] Update response/error hook implementation
---
.../youtube_plugin/kodion/network/requests.py | 25 ++++++++++----
.../youtube/client/login_client.py | 33 ++++++++++---------
2 files changed, 36 insertions(+), 22 deletions(-)
diff --git a/resources/lib/youtube_plugin/kodion/network/requests.py b/resources/lib/youtube_plugin/kodion/network/requests.py
index bdd2981a5..34d4a8545 100644
--- a/resources/lib/youtube_plugin/kodion/network/requests.py
+++ b/resources/lib/youtube_plugin/kodion/network/requests.py
@@ -57,9 +57,12 @@ def request(self, url, method='GET',
auth=None, timeout=None, allow_redirects=None, proxies=None,
hooks=None, stream=None, verify=None, cert=None, json=None,
# Custom event hook implementation
- # See _login_json_hook and _login_error_hook in login_client.py
+ # See _response_hook and _error_hook in login_client.py
# for example usage
- response_hook=None, error_hook=None,
+ response_hook=None,
+ response_hook_kwargs=None,
+ error_hook=None,
+ error_hook_kwargs=None,
error_title=None, error_info=None, raise_exc=False, **_):
if timeout is None:
timeout = self._timeout
@@ -86,7 +89,10 @@ def request(self, url, method='GET',
cert=cert,
json=json,)
if response_hook:
- response = response_hook(response)
+ if response_hook_kwargs is None:
+ response_hook_kwargs = {}
+ response_hook_kwargs['response'] = response
+ response = response_hook(**response_hook_kwargs)
else:
response.raise_for_status()
@@ -94,14 +100,21 @@ def request(self, url, method='GET',
response_text = exc.response and exc.response.text
stack_trace = format_stack()
exc_tb = format_exc()
+ error_details = {'exc': exc}
if error_hook:
- error_response = error_hook(exc, response)
- _title, _info, _response, _trace, _exc = error_response
+ if error_hook_kwargs is None:
+ error_hook_kwargs = {}
+ error_hook_kwargs['exc'] = exc
+ error_hook_kwargs['response'] = response
+ error_response = error_hook(**error_hook_kwargs)
+ _title, _info, _detail, _response, _trace, _exc = error_response
if _title is not None:
error_title = _title
if _info is not None:
error_info = _info
+ if _detail is not None:
+ error_details.update(_detail)
if _response is not None:
response = _response
response_text = str(_response)
@@ -117,7 +130,7 @@ def request(self, url, method='GET',
error_info = str(exc)
elif '{' in error_info:
try:
- error_info = error_info.format(exc=exc)
+ error_info = error_info.format(**error_details)
except (AttributeError, IndexError, KeyError):
error_info = str(exc)
diff --git a/resources/lib/youtube_plugin/youtube/client/login_client.py b/resources/lib/youtube_plugin/youtube/client/login_client.py
index 90205c3bb..77b4ecd8d 100644
--- a/resources/lib/youtube_plugin/youtube/client/login_client.py
+++ b/resources/lib/youtube_plugin/youtube/client/login_client.py
@@ -82,7 +82,8 @@ def __init__(self, config=None, language='en-US', region='',
super(LoginClient, self).__init__(exc_type=LoginException)
@staticmethod
- def _login_json_hook(response):
+ def _response_hook(**kwargs):
+ response = kwargs['response']
try:
json_data = response.json()
if 'error' in json_data:
@@ -95,16 +96,16 @@ def _login_json_hook(response):
return json_data
@staticmethod
- def _login_error_hook(error, _response):
- json_data = getattr(error, 'json_data', None)
- if not json_data:
- return None, None, None, None, LoginException
+ def _error_hook(**kwargs):
+ json_data = getattr(kwargs['exc'], 'json_data', None)
+ if not json_data or 'error' not in json_data:
+ return None, None, None, None, None, LoginException
if json_data['error'] == 'authorization_pending':
- return None, None, json_data, False, False
+ return None, None, None, json_data, False, False
if (json_data['error'] == 'invalid_grant'
and json_data.get('code') == '400'):
- return None, None, json_data, False, InvalidGrant(json_data)
- return None, None, json_data, False, LoginException(json_data)
+ return None, None, None, json_data, False, InvalidGrant(json_data)
+ return None, None, None, json_data, False, LoginException(json_data)
def set_log_error(self, callback):
self._log_error_callback = callback
@@ -138,8 +139,8 @@ def revoke(self, refresh_token):
method='POST',
data=post_data,
headers=headers,
- response_hook=self._login_json_hook,
- error_hook=self._login_error_hook,
+ response_hook=self._response_hook,
+ error_hook=self._error_hook,
error_title='Logout Failed',
error_info='Revoke failed: {exc}',
raise_exc=True)
@@ -178,8 +179,8 @@ def refresh_token(self, refresh_token, client_id='', client_secret=''):
method='POST',
data=post_data,
headers=headers,
- response_hook=self._login_json_hook,
- error_hook=self._login_error_hook,
+ response_hook=self._response_hook,
+ error_hook=self._error_hook,
error_title='Login Failed',
error_info=('Refresh token failed'
' {client}: {{exc}}'
@@ -226,8 +227,8 @@ def request_access_token(self, code, client_id='', client_secret=''):
method='POST',
data=post_data,
headers=headers,
- response_hook=self._login_json_hook,
- error_hook=self._login_error_hook,
+ response_hook=self._response_hook,
+ error_hook=self._error_hook,
error_title='Login Failed: Unknown response',
error_info=('Access token request failed'
' {client}: {{exc}}'
@@ -262,8 +263,8 @@ def request_device_and_user_code(self, client_id=''):
method='POST',
data=post_data,
headers=headers,
- response_hook=self._login_json_hook,
- error_hook=self._login_error_hook,
+ response_hook=self._response_hook,
+ error_hook=self._error_hook,
error_title='Login Failed: Unknown response',
error_info=('Device/user code request failed'
' {client}: {{exc}}'
From 764caec393ff9f7fee777cfa9c8bf497573d9801 Mon Sep 17 00:00:00 2001
From: MoojMidge <56883549+MoojMidge@users.noreply.github.com>
Date: Fri, 15 Dec 2023 14:47:28 +1100
Subject: [PATCH 103/141] Standardise input to SQL storage
- Input/output will be always be JSON de(serialized) and (un)pickled
- Also set ensure_ascii=False
---
resources/lib/youtube_plugin/kodion/abstract_provider.py | 3 +--
.../lib/youtube_plugin/kodion/json_store/json_store.py | 6 +++---
.../youtube_plugin/kodion/player/xbmc/xbmc_playlist.py | 2 +-
.../lib/youtube_plugin/kodion/sql_store/data_cache.py | 6 +++---
.../youtube_plugin/kodion/sql_store/playback_history.py | 2 +-
resources/lib/youtube_plugin/kodion/sql_store/storage.py | 9 +++++----
resources/lib/youtube_plugin/youtube/client/youtube.py | 6 +++---
.../lib/youtube_plugin/youtube/helper/video_info.py | 7 +++----
resources/lib/youtube_plugin/youtube/helper/yt_play.py | 3 ++-
9 files changed, 22 insertions(+), 22 deletions(-)
diff --git a/resources/lib/youtube_plugin/kodion/abstract_provider.py b/resources/lib/youtube_plugin/kodion/abstract_provider.py
index b346a196e..a35a8bad5 100644
--- a/resources/lib/youtube_plugin/kodion/abstract_provider.py
+++ b/resources/lib/youtube_plugin/kodion/abstract_provider.py
@@ -247,8 +247,7 @@ def _internal_search(self, context, re_match):
channel_id = context.get_param('channel_id', '')
self._data_cache.set_item('search_query',
- json.dumps({'query': quote(query)},
- ensure_ascii=False))
+ {'query': quote(query)})
if not incognito and not channel_id:
try:
diff --git a/resources/lib/youtube_plugin/kodion/json_store/json_store.py b/resources/lib/youtube_plugin/kodion/json_store/json_store.py
index 1f5a98c29..ca6baad78 100644
--- a/resources/lib/youtube_plugin/kodion/json_store/json_store.py
+++ b/resources/lib/youtube_plugin/kodion/json_store/json_store.py
@@ -56,7 +56,7 @@ def save(self, data, update=False, process=None):
try:
if not data:
raise ValueError
- _data = json.loads(json.dumps(data))
+ _data = json.loads(json.dumps(data, ensure_ascii=False))
with open(self.filename, mode='w', encoding='utf-8') as jsonfile:
jsonfile.write(to_unicode(json.dumps(_data,
ensure_ascii=False,
@@ -98,12 +98,12 @@ def get_data(self, process=None):
try:
if not self._data:
raise ValueError
- _data = json.loads(json.dumps(self._data))
+ _data = json.loads(json.dumps(self._data, ensure_ascii=False))
return process(_data) if process is not None else _data
except (TypeError, ValueError):
log_error('JSONStore.get_data - invalid data:\n|{data}|'.format(
data=self._data
))
self.set_defaults(reset=True)
- _data = json.loads(json.dumps(self._data))
+ _data = json.loads(json.dumps(self._data, ensure_ascii=False))
return process(_data) if process is not None else _data
diff --git a/resources/lib/youtube_plugin/kodion/player/xbmc/xbmc_playlist.py b/resources/lib/youtube_plugin/kodion/player/xbmc/xbmc_playlist.py
index 29e03de19..e6bdc14c2 100644
--- a/resources/lib/youtube_plugin/kodion/player/xbmc/xbmc_playlist.py
+++ b/resources/lib/youtube_plugin/kodion/player/xbmc/xbmc_playlist.py
@@ -64,7 +64,7 @@ def get_items(self, properties=None, dumps=False):
result = response['result']['items']
else:
result = []
- return json.dumps(result) if dumps else result
+ return json.dumps(result, ensure_ascii=False) if dumps else result
if 'error' in response:
message = response['error']['message']
diff --git a/resources/lib/youtube_plugin/kodion/sql_store/data_cache.py b/resources/lib/youtube_plugin/kodion/sql_store/data_cache.py
index 8eb571ce0..f6f7d08b3 100644
--- a/resources/lib/youtube_plugin/kodion/sql_store/data_cache.py
+++ b/resources/lib/youtube_plugin/kodion/sql_store/data_cache.py
@@ -26,7 +26,7 @@ def is_empty(self):
return self._is_empty()
def get_items(self, content_ids, seconds):
- query_result = self._get_by_ids(content_ids, process=json.loads)
+ query_result = self._get_by_ids(content_ids)
if not query_result:
return {}
@@ -48,7 +48,7 @@ def get_item(self, content_id, seconds):
if self.get_seconds_diff(query_result[1] or current_time) > seconds:
return None
- return json.loads(query_result[0])
+ return query_result[0]
def set_item(self, content_id, item):
self._set(content_id, item)
@@ -63,7 +63,7 @@ def remove(self, content_id):
self._remove(content_id)
def update(self, content_id, item):
- self._set(str(content_id), json.dumps(item))
+ self._set(str(content_id), item)
def _optimize_item_count(self):
pass
diff --git a/resources/lib/youtube_plugin/kodion/sql_store/playback_history.py b/resources/lib/youtube_plugin/kodion/sql_store/playback_history.py
index 0f9a263ad..ff4763b2b 100644
--- a/resources/lib/youtube_plugin/kodion/sql_store/playback_history.py
+++ b/resources/lib/youtube_plugin/kodion/sql_store/playback_history.py
@@ -21,7 +21,7 @@ def is_empty(self):
@staticmethod
def _process_item(item):
- return item.split(',')
+ return item.strip('"').split(',')
def get_items(self, keys):
query_result = self._get_by_ids(keys, process=self._process_item)
diff --git a/resources/lib/youtube_plugin/kodion/sql_store/storage.py b/resources/lib/youtube_plugin/kodion/sql_store/storage.py
index d8a41b7d1..b41898429 100644
--- a/resources/lib/youtube_plugin/kodion/sql_store/storage.py
+++ b/resources/lib/youtube_plugin/kodion/sql_store/storage.py
@@ -165,7 +165,7 @@ def _set(self, item_id, item):
# add 1 microsecond, required for dbapi2
now = since_epoch(datetime.now()) + 0.000001
self._open()
- self._execute(True, self._set_query, values=[item_id,
+ self._execute(True, self._set_query, values=[str(item_id),
now,
self._encode(item)])
self._close()
@@ -176,7 +176,7 @@ def _set_all(self, items):
now = since_epoch(datetime.now()) + 0.000001
self._open()
self._execute(True, self._set_query,
- values=[(key, now, self._encode(json.dumps(item)))
+ values=[(str(key), now, self._encode(item))
for key, item in items.items()],
many=True)
self._close()
@@ -222,12 +222,13 @@ def _decode(obj, process=None):
decoded_obj = pickle.loads(obj)
if process:
return process(decoded_obj)
- return decoded_obj
+ return json.loads(decoded_obj)
@staticmethod
def _encode(obj):
return sqlite3.Binary(pickle.dumps(
- obj, protocol=pickle.HIGHEST_PROTOCOL
+ json.dumps(obj, ensure_ascii=False),
+ protocol=pickle.HIGHEST_PROTOCOL
))
def _get(self, item_id):
diff --git a/resources/lib/youtube_plugin/youtube/client/youtube.py b/resources/lib/youtube_plugin/youtube/client/youtube.py
index b6874c97b..002267b0f 100644
--- a/resources/lib/youtube_plugin/youtube/client/youtube.py
+++ b/resources/lib/youtube_plugin/youtube/client/youtube.py
@@ -381,7 +381,7 @@ def helper(video_id, responses):
# Truncate items to keep it manageable, and cache
items = items[:500]
- cache.set_item(cache_items_key, json.dumps(items))
+ cache.set_item(cache_items_key, items)
# Build the result set
items.sort(
@@ -438,7 +438,7 @@ def _sort_by_date_time(item):
}
"""
# Update cache
- cache.set_item(cache_home_key, json.dumps(payload))
+ cache.set_item(cache_home_key, payload)
# If there are no sorted_items we fall back to default API behaviour
return payload
@@ -884,7 +884,7 @@ def _sort_by_date_time(item):
_result['items'].sort(reverse=True, key=_sort_by_date_time)
# Update cache
- cache.set_item(cache_items_key, json.dumps(_result['items']))
+ cache.set_item(cache_items_key, _result['items'])
""" no cache, get uploads data from web """
# trim result
diff --git a/resources/lib/youtube_plugin/youtube/helper/video_info.py b/resources/lib/youtube_plugin/youtube/helper/video_info.py
index 2326c5f1b..3fc2bea5e 100644
--- a/resources/lib/youtube_plugin/youtube/helper/video_info.py
+++ b/resources/lib/youtube_plugin/youtube/helper/video_info.py
@@ -733,7 +733,7 @@ def _get_player_js(self):
return ''
js_url = self._normalize_url(js_url)
- self._data_cache.set_item('player_js_url', json_dumps({'url': js_url}))
+ self._data_cache.set_item('player_js_url', {'url': js_url})
js_cache_key = quote(js_url)
cached = self._data_cache.get_item(js_cache_key,
@@ -752,7 +752,7 @@ def _get_player_js(self):
return ''
javascript = result.text
- self._data_cache.set_item(js_cache_key, json_dumps({'js': javascript}))
+ self._data_cache.set_item(js_cache_key, {'js': javascript})
return javascript
@staticmethod
@@ -938,8 +938,7 @@ def _process_signature_cipher(self, stream_map):
'Failed to extract URL from signatureCipher'
)
return None
- self._data_cache.set_item(encrypted_signature,
- json_dumps({'sig': signature}))
+ self._data_cache.set_item(encrypted_signature, {'sig': signature})
if signature:
url = '{0}&{1}={2}'.format(url, query_var, signature)
diff --git a/resources/lib/youtube_plugin/youtube/helper/yt_play.py b/resources/lib/youtube_plugin/youtube/helper/yt_play.py
index f6d4cdb76..a233a7bc0 100644
--- a/resources/lib/youtube_plugin/youtube/helper/yt_play.py
+++ b/resources/lib/youtube_plugin/youtube/helper/yt_play.py
@@ -125,7 +125,8 @@ def play_video(provider, context):
'refresh_only': screensaver
}
- ui.set_property('playback_json', json.dumps(playback_json))
+ ui.set_property('playback_json', json.dumps(playback_json,
+ ensure_ascii=False))
context.send_notification('PlaybackInit', {
'video_id': video_id,
'channel_id': playback_json.get('channel_id', ''),
From 5c278282f7ac188bdabe0cccc16342754b8d3132 Mon Sep 17 00:00:00 2001
From: MoojMidge <56883549+MoojMidge@users.noreply.github.com>
Date: Fri, 15 Dec 2023 14:48:23 +1100
Subject: [PATCH 104/141] Add context manager to progress dialogs
---
.../youtube_plugin/kodion/ui/abstract_progress_dialog.py | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/resources/lib/youtube_plugin/kodion/ui/abstract_progress_dialog.py b/resources/lib/youtube_plugin/kodion/ui/abstract_progress_dialog.py
index 0af140254..3406a7ca4 100644
--- a/resources/lib/youtube_plugin/kodion/ui/abstract_progress_dialog.py
+++ b/resources/lib/youtube_plugin/kodion/ui/abstract_progress_dialog.py
@@ -19,6 +19,12 @@ def __init__(self, dialog, heading, text, total=100):
self._position = 1
self.update(steps=-1)
+ def __enter__(self):
+ return self
+
+ def __exit__(self, exc_type, exc_val, exc_tb):
+ self.close()
+
def get_total(self):
return self._total
From e9ed03f81384ce5abb87dc04561e99c7e583a45e Mon Sep 17 00:00:00 2001
From: MoojMidge <56883549+MoojMidge@users.noreply.github.com>
Date: Fri, 15 Dec 2023 14:52:59 +1100
Subject: [PATCH 105/141] Fix incorrect date sorting after a3af85f
- Sorting was messed up due to sorting based on localised date format
- No default sorting for non-live listings, will use order from API
- TODO: Ensure correct order is kept when using cache
---
resources/lib/youtube_plugin/kodion/items/base_item.py | 4 ++--
resources/lib/youtube_plugin/kodion/ui/xbmc/info_labels.py | 4 ++--
resources/lib/youtube_plugin/youtube/provider.py | 3 +--
3 files changed, 5 insertions(+), 6 deletions(-)
diff --git a/resources/lib/youtube_plugin/kodion/items/base_item.py b/resources/lib/youtube_plugin/kodion/items/base_item.py
index 44d8a89d3..2dee09a18 100644
--- a/resources/lib/youtube_plugin/kodion/items/base_item.py
+++ b/resources/lib/youtube_plugin/kodion/items/base_item.py
@@ -111,7 +111,7 @@ def set_date(self, year, month, day, hour=0, minute=0, second=0):
def set_date_from_datetime(self, date_time):
self._date = date_time
- def get_date(self, as_text=True, short=False):
+ def get_date(self, as_text=False, short=False):
if not self._date:
return ''
if short:
@@ -131,7 +131,7 @@ def set_dateadded(self, year, month, day, hour=0, minute=0, second=0):
def set_dateadded_from_datetime(self, date_time):
self._dateadded = date_time
- def get_dateadded(self, as_text=True):
+ def get_dateadded(self, as_text=False):
if not self._dateadded:
return ''
if as_text:
diff --git a/resources/lib/youtube_plugin/kodion/ui/xbmc/info_labels.py b/resources/lib/youtube_plugin/kodion/ui/xbmc/info_labels.py
index 674d2f2bc..0a2715bd4 100644
--- a/resources/lib/youtube_plugin/kodion/ui/xbmc/info_labels.py
+++ b/resources/lib/youtube_plugin/kodion/ui/xbmc/info_labels.py
@@ -93,7 +93,7 @@ def create_from_item(base_item):
info_labels = {}
# 'date' = '1982-03-09' (string)
- _process_datetime_value(info_labels, 'date', base_item.get_date(as_text=False))
+ _process_datetime_value(info_labels, 'date', base_item.get_date())
# 'count' = 12 (integer)
# Can be used to store an id for later, or for sorting purposes
@@ -138,7 +138,7 @@ def create_from_item(base_item):
_process_list_value(info_labels, 'artist', base_item.get_artist())
# 'dateadded' = '2014-08-11 13:08:56' (string) will be taken from 'dateadded'
- _process_datetime_value(info_labels, 'dateadded', base_item.get_dateadded(as_text=False))
+ _process_datetime_value(info_labels, 'dateadded', base_item.get_dateadded())
# TODO: starting with Helix this could be seconds
# 'duration' = '3:18' (string)
diff --git a/resources/lib/youtube_plugin/youtube/provider.py b/resources/lib/youtube_plugin/youtube/provider.py
index d0ae46420..e6d5740ad 100644
--- a/resources/lib/youtube_plugin/youtube/provider.py
+++ b/resources/lib/youtube_plugin/youtube/provider.py
@@ -493,8 +493,7 @@ def _on_channel(self, context, re_match):
if not v3.handle_error(context, json_data):
return False
- result.extend(
- v3.response_to_items(self, context, json_data, sort=lambda x: x.get_date()))
+ result.extend(v3.response_to_items(self, context, json_data))
return result
From 860defdfc00fc73c1e9f893a30e02b71175c9043 Mon Sep 17 00:00:00 2001
From: MoojMidge <56883549+MoojMidge@users.noreply.github.com>
Date: Fri, 15 Dec 2023 15:05:27 +1100
Subject: [PATCH 106/141] Use same response/error hooks for
Youtube.perform_v3_request
- Partially fixes #545
- Remove the other handle_error methods
---
.../lib/youtube_plugin/kodion/utils/player.py | 11 +-
.../youtube_plugin/youtube/client/youtube.py | 408 +++++++++++++-----
.../lib/youtube_plugin/youtube/helper/v3.py | 34 --
.../youtube_plugin/youtube/helper/yt_play.py | 13 +-
.../youtube/helper/yt_playlist.py | 10 +-
.../youtube/helper/yt_specials.py | 18 +-
.../youtube/helper/yt_subscriptions.py | 6 +-
.../youtube_plugin/youtube/helper/yt_video.py | 2 +-
.../lib/youtube_plugin/youtube/provider.py | 13 +-
resources/lib/youtube_requests.py | 121 ++++--
10 files changed, 422 insertions(+), 214 deletions(-)
diff --git a/resources/lib/youtube_plugin/kodion/utils/player.py b/resources/lib/youtube_plugin/kodion/utils/player.py
index 830f299c6..8c3adc136 100644
--- a/resources/lib/youtube_plugin/kodion/utils/player.py
+++ b/resources/lib/youtube_plugin/kodion/utils/player.py
@@ -348,17 +348,11 @@ def run(self):
json_data = client.remove_video_from_playlist(
watch_later_id, playlist_item_id
)
- _ = self.provider.v3_handle_error(self.provider,
- self._context,
- json_data)
history_playlist_id = access_manager.get_watch_history_id()
if history_playlist_id and history_playlist_id != 'HL':
json_data = client.add_video_to_playlist(history_playlist_id,
self.video_id)
- _ = self.provider.v3_handle_error(self.provider,
- self._context,
- json_data)
# rate video
if settings.get_bool('youtube.post.play.rate', False):
@@ -370,10 +364,7 @@ def run(self):
if do_rating:
json_data = client.get_video_rating(self.video_id)
- success = self.provider.v3_handle_error(self.provider,
- self._context,
- json_data)
- if success:
+ if json_data:
items = json_data.get('items', [{'rating': 'none'}])
rating = items[0].get('rating', 'none')
if rating == 'none':
diff --git a/resources/lib/youtube_plugin/youtube/client/youtube.py b/resources/lib/youtube_plugin/youtube/client/youtube.py
index 002267b0f..741b56d52 100644
--- a/resources/lib/youtube_plugin/youtube/client/youtube.py
+++ b/resources/lib/youtube_plugin/youtube/client/youtube.py
@@ -11,15 +11,15 @@
from __future__ import absolute_import, division, unicode_literals
import copy
-import json
import re
import threading
import xml.etree.ElementTree as ET
from .login_client import LoginClient
from ..helper.video_info import VideoInfo
+from ..youtube_exceptions import InvalidJSON, YouTubeException
from ...kodion import Context
-from ...kodion.utils import datetime_parser, to_unicode
+from ...kodion.utils import datetime_parser, strip_html_from_text, to_unicode
_context = Context(plugin_id='plugin.video.youtube')
@@ -153,52 +153,76 @@ def get_video_streams(self, context, video_id):
return video_streams
- def remove_playlist(self, playlist_id):
+ def remove_playlist(self, playlist_id, **kwargs):
params = {'id': playlist_id,
'mine': 'true'}
- return self.perform_v3_request(method='DELETE', path='playlists', params=params)
+ return self.perform_v3_request(method='DELETE',
+ path='playlists',
+ params=params,
+ **kwargs)
- def get_supported_languages(self, language=None):
+ def get_supported_languages(self, language=None, **kwargs):
_language = language
if not _language:
_language = self._language
_language = _language.replace('-', '_')
params = {'part': 'snippet',
'hl': _language}
- return self.perform_v3_request(method='GET', path='i18nLanguages', params=params)
+ return self.perform_v3_request(method='GET',
+ path='i18nLanguages',
+ params=params,
+ **kwargs)
- def get_supported_regions(self, language=None):
+ def get_supported_regions(self, language=None, **kwargs):
_language = language
if not _language:
_language = self._language
_language = _language.replace('-', '_')
params = {'part': 'snippet',
'hl': _language}
- return self.perform_v3_request(method='GET', path='i18nRegions', params=params)
-
- def rename_playlist(self, playlist_id, new_title, privacy_status='private'):
+ return self.perform_v3_request(method='GET',
+ path='i18nRegions',
+ params=params,
+ **kwargs)
+
+ def rename_playlist(self,
+ playlist_id,
+ new_title,
+ privacy_status='private',
+ **kwargs):
params = {'part': 'snippet,id,status'}
post_data = {'kind': 'youtube#playlist',
'id': playlist_id,
'snippet': {'title': new_title},
'status': {'privacyStatus': privacy_status}}
- return self.perform_v3_request(method='PUT', path='playlists', params=params, post_data=post_data)
+ return self.perform_v3_request(method='PUT',
+ path='playlists',
+ params=params,
+ post_data=post_data,
+ **kwargs)
- def create_playlist(self, title, privacy_status='private'):
+ def create_playlist(self, title, privacy_status='private', **kwargs):
params = {'part': 'snippet,status'}
post_data = {'kind': 'youtube#playlist',
'snippet': {'title': title},
'status': {'privacyStatus': privacy_status}}
- return self.perform_v3_request(method='POST', path='playlists', params=params, post_data=post_data)
+ return self.perform_v3_request(method='POST',
+ path='playlists',
+ params=params,
+ post_data=post_data,
+ **kwargs)
- def get_video_rating(self, video_id):
+ def get_video_rating(self, video_id, **kwargs):
if isinstance(video_id, list):
video_id = ','.join(video_id)
params = {'id': video_id}
- return self.perform_v3_request(method='GET', path='videos/getRating', params=params)
+ return self.perform_v3_request(method='GET',
+ path='videos/getRating',
+ params=params,
+ **kwargs)
- def rate_video(self, video_id, rating='like'):
+ def rate_video(self, video_id, rating='like', **kwargs):
"""
Rate a video
:param video_id: if of the video
@@ -207,34 +231,58 @@ def rate_video(self, video_id, rating='like'):
"""
params = {'id': video_id,
'rating': rating}
- return self.perform_v3_request(method='POST', path='videos/rate', params=params)
+ return self.perform_v3_request(method='POST',
+ path='videos/rate',
+ params=params,
+ **kwargs)
- def add_video_to_playlist(self, playlist_id, video_id):
+ def add_video_to_playlist(self, playlist_id, video_id, **kwargs):
params = {'part': 'snippet',
'mine': 'true'}
post_data = {'kind': 'youtube#playlistItem',
'snippet': {'playlistId': playlist_id,
'resourceId': {'kind': 'youtube#video',
'videoId': video_id}}}
- return self.perform_v3_request(method='POST', path='playlistItems', params=params, post_data=post_data)
+ return self.perform_v3_request(method='POST',
+ path='playlistItems',
+ params=params,
+ post_data=post_data,
+ **kwargs)
# noinspection PyUnusedLocal
- def remove_video_from_playlist(self, playlist_id, playlist_item_id):
+ def remove_video_from_playlist(self,
+ playlist_id,
+ playlist_item_id,
+ **kwargs):
params = {'id': playlist_item_id}
- return self.perform_v3_request(method='DELETE', path='playlistItems', params=params)
+ return self.perform_v3_request(method='DELETE',
+ path='playlistItems',
+ params=params,
+ **kwargs)
- def unsubscribe(self, subscription_id):
+ def unsubscribe(self, subscription_id, **kwargs):
params = {'id': subscription_id}
- return self.perform_v3_request(method='DELETE', path='subscriptions', params=params)
+ return self.perform_v3_request(method='DELETE',
+ path='subscriptions',
+ params=params,
+ **kwargs)
- def subscribe(self, channel_id):
+ def subscribe(self, channel_id, **kwargs):
params = {'part': 'snippet'}
post_data = {'kind': 'youtube#subscription',
'snippet': {'resourceId': {'kind': 'youtube#channel',
'channelId': channel_id}}}
- return self.perform_v3_request(method='POST', path='subscriptions', params=params, post_data=post_data)
-
- def get_subscription(self, channel_id, order='alphabetical', page_token=''):
+ return self.perform_v3_request(method='POST',
+ path='subscriptions',
+ params=params,
+ post_data=post_data,
+ **kwargs)
+
+ def get_subscription(self,
+ channel_id,
+ order='alphabetical',
+ page_token='',
+ **kwargs):
"""
:param channel_id: [channel-id|'mine']
@@ -252,9 +300,12 @@ def get_subscription(self, channel_id, order='alphabetical', page_token=''):
if page_token:
params['pageToken'] = page_token
- return self.perform_v3_request(method='GET', path='subscriptions', params=params)
+ return self.perform_v3_request(method='GET',
+ path='subscriptions',
+ params=params,
+ **kwargs)
- def get_guide_category(self, guide_category_id, page_token=''):
+ def get_guide_category(self, guide_category_id, page_token='', **kwargs):
params = {'part': 'snippet,contentDetails,brandingSettings',
'maxResults': str(self._max_results),
'categoryId': guide_category_id,
@@ -262,9 +313,12 @@ def get_guide_category(self, guide_category_id, page_token=''):
'hl': self._language}
if page_token:
params['pageToken'] = page_token
- return self.perform_v3_request(method='GET', path='channels', params=params)
+ return self.perform_v3_request(method='GET',
+ path='channels',
+ params=params,
+ **kwargs)
- def get_guide_categories(self, page_token=''):
+ def get_guide_categories(self, page_token='', **kwargs):
params = {'part': 'snippet',
'maxResults': str(self._max_results),
'regionCode': self._region,
@@ -272,9 +326,12 @@ def get_guide_categories(self, page_token=''):
if page_token:
params['pageToken'] = page_token
- return self.perform_v3_request(method='GET', path='guideCategories', params=params)
+ return self.perform_v3_request(method='GET',
+ path='guideCategories',
+ params=params,
+ **kwargs)
- def get_popular_videos(self, page_token=''):
+ def get_popular_videos(self, page_token='', **kwargs):
params = {'part': 'snippet,status',
'maxResults': str(self._max_results),
'regionCode': self._region,
@@ -282,9 +339,12 @@ def get_popular_videos(self, page_token=''):
'chart': 'mostPopular'}
if page_token:
params['pageToken'] = page_token
- return self.perform_v3_request(method='GET', path='videos', params=params)
+ return self.perform_v3_request(method='GET',
+ path='videos',
+ params=params,
+ **kwargs)
- def get_video_category(self, video_category_id, page_token=''):
+ def get_video_category(self, video_category_id, page_token='', **kwargs):
params = {'part': 'snippet,contentDetails,status',
'maxResults': str(self._max_results),
'videoCategoryId': video_category_id,
@@ -293,9 +353,12 @@ def get_video_category(self, video_category_id, page_token=''):
'hl': self._language}
if page_token:
params['pageToken'] = page_token
- return self.perform_v3_request(method='GET', path='videos', params=params)
+ return self.perform_v3_request(method='GET',
+ path='videos',
+ params=params,
+ **kwargs)
- def get_video_categories(self, page_token=''):
+ def get_video_categories(self, page_token='', **kwargs):
params = {'part': 'snippet',
'maxResults': str(self._max_results),
'regionCode': self._region,
@@ -303,7 +366,10 @@ def get_video_categories(self, page_token=''):
if page_token:
params['pageToken'] = page_token
- return self.perform_v3_request(method='GET', path='videoCategories', params=params)
+ return self.perform_v3_request(method='GET',
+ path='videoCategories',
+ params=params,
+ **kwargs)
def _get_recommendations_for_home(self):
# YouTube has deprecated this API, so use history and related items to form
@@ -443,7 +509,7 @@ def _sort_by_date_time(item):
# If there are no sorted_items we fall back to default API behaviour
return payload
- def get_activities(self, channel_id, page_token=''):
+ def get_activities(self, channel_id, page_token='', **kwargs):
params = {'part': 'snippet,contentDetails',
'maxResults': str(self._max_results),
'regionCode': self._region,
@@ -462,9 +528,12 @@ def get_activities(self, channel_id, page_token=''):
if page_token:
params['pageToken'] = page_token
- return self.perform_v3_request(method='GET', path='activities', params=params)
+ return self.perform_v3_request(method='GET',
+ path='activities',
+ params=params,
+ **kwargs)
- def get_channel_sections(self, channel_id):
+ def get_channel_sections(self, channel_id, **kwargs):
params = {'part': 'snippet,contentDetails',
'regionCode': self._region,
'hl': self._language}
@@ -472,9 +541,12 @@ def get_channel_sections(self, channel_id):
params['mine'] = 'true'
else:
params['channelId'] = channel_id
- return self.perform_v3_request(method='GET', path='channelSections', params=params)
+ return self.perform_v3_request(method='GET',
+ path='channelSections',
+ params=params,
+ **kwargs)
- def get_playlists_of_channel(self, channel_id, page_token=''):
+ def get_playlists_of_channel(self, channel_id, page_token='', **kwargs):
params = {'part': 'snippet',
'maxResults': str(self._max_results)}
if channel_id != 'mine':
@@ -484,7 +556,10 @@ def get_playlists_of_channel(self, channel_id, page_token=''):
if page_token:
params['pageToken'] = page_token
- return self.perform_v3_request(method='GET', path='playlists', params=params)
+ return self.perform_v3_request(method='GET',
+ path='playlists',
+ params=params,
+ **kwargs)
def get_playlist_item_id_of_video_id(self, playlist_id, video_id, page_token=''):
old_max_results = self._max_results
@@ -506,7 +581,11 @@ def get_playlist_item_id_of_video_id(self, playlist_id, video_id, page_token='')
return None
- def get_playlist_items(self, playlist_id, page_token='', max_results=None):
+ def get_playlist_items(self,
+ playlist_id,
+ page_token='',
+ max_results=None,
+ **kwargs):
# prepare params
max_results = str(self._max_results) if max_results is None else str(max_results)
params = {'part': 'snippet',
@@ -515,9 +594,12 @@ def get_playlist_items(self, playlist_id, page_token='', max_results=None):
if page_token:
params['pageToken'] = page_token
- return self.perform_v3_request(method='GET', path='playlistItems', params=params)
+ return self.perform_v3_request(method='GET',
+ path='playlistItems',
+ params=params,
+ **kwargs)
- def get_channel_by_username(self, username):
+ def get_channel_by_username(self, username, **kwargs):
"""
Returns a collection of zero or more channel resources that match the request criteria.
:param username: retrieve channel_id for username
@@ -529,9 +611,12 @@ def get_channel_by_username(self, username):
else:
params.update({'forUsername': username})
- return self.perform_v3_request(method='GET', path='channels', params=params)
+ return self.perform_v3_request(method='GET',
+ path='channels',
+ params=params,
+ **kwargs)
- def get_channels(self, channel_id):
+ def get_channels(self, channel_id, **kwargs):
"""
Returns a collection of zero or more channel resources that match the request criteria.
:param channel_id: list or comma-separated list of the YouTube channel ID(s)
@@ -545,9 +630,12 @@ def get_channels(self, channel_id):
params['id'] = channel_id
else:
params['mine'] = 'true'
- return self.perform_v3_request(method='GET', path='channels', params=params)
+ return self.perform_v3_request(method='GET',
+ path='channels',
+ params=params,
+ **kwargs)
- def get_disliked_videos(self, page_token=''):
+ def get_disliked_videos(self, page_token='', **kwargs):
# prepare page token
if not page_token:
page_token = ''
@@ -559,9 +647,12 @@ def get_disliked_videos(self, page_token=''):
if page_token:
params['pageToken'] = page_token
- return self.perform_v3_request(method='GET', path='videos', params=params)
+ return self.perform_v3_request(method='GET',
+ path='videos',
+ params=params,
+ **kwargs)
- def get_videos(self, video_id, live_details=False):
+ def get_videos(self, video_id, live_details=False, **kwargs):
"""
Returns a list of videos that match the API request parameters
:param video_id: list of video ids
@@ -577,19 +668,29 @@ def get_videos(self, video_id, live_details=False):
params = {'part': ''.join(parts),
'id': video_id}
- return self.perform_v3_request(method='GET', path='videos', params=params)
+ return self.perform_v3_request(method='GET',
+ path='videos',
+ params=params,
+ **kwargs)
- def get_playlists(self, playlist_id):
+ def get_playlists(self, playlist_id, **kwargs):
if isinstance(playlist_id, list):
playlist_id = ','.join(playlist_id)
params = {'part': 'snippet,contentDetails',
'id': playlist_id}
- return self.perform_v3_request(method='GET', path='playlists', params=params)
-
- def get_live_events(self, event_type='live', order='relevance', page_token='', location=False):
+ return self.perform_v3_request(method='GET',
+ path='playlists',
+ params=params,
+ **kwargs)
+
+ def get_live_events(self,
+ event_type='live',
+ order='relevance',
+ page_token='',
+ location=False,
+ **kwargs):
"""
-
:param event_type: one of: 'live', 'completed', 'upcoming'
:param order: one of: 'date', 'rating', 'relevance', 'title', 'videoCount', 'viewCount'
:param page_token:
@@ -619,9 +720,16 @@ def get_live_events(self, event_type='live', order='relevance', page_token='', l
if page_token:
params['pageToken'] = page_token
- return self.perform_v3_request(method='GET', path='search', params=params)
+ return self.perform_v3_request(method='GET',
+ path='search',
+ params=params,
+ **kwargs)
- def get_related_videos(self, video_id, page_token='', max_results=0):
+ def get_related_videos(self,
+ video_id,
+ page_token='',
+ max_results=0,
+ **kwargs):
# prepare page token
if not page_token:
page_token = ''
@@ -638,9 +746,16 @@ def get_related_videos(self, video_id, page_token='', max_results=0):
if page_token:
params['pageToken'] = page_token
- return self.perform_v3_request(method='GET', path='search', params=params)
+ return self.perform_v3_request(method='GET',
+ path='search',
+ params=params,
+ **kwargs)
- def get_parent_comments(self, video_id, page_token='', max_results=0):
+ def get_parent_comments(self,
+ video_id,
+ page_token='',
+ max_results=0,
+ **kwargs):
max_results = self._max_results if max_results <= 0 else max_results
# prepare params
@@ -652,9 +767,17 @@ def get_parent_comments(self, video_id, page_token='', max_results=0):
if page_token:
params['pageToken'] = page_token
- return self.perform_v3_request(method='GET', path='commentThreads', params=params, no_login=True)
-
- def get_child_comments(self, parent_id, page_token='', max_results=0):
+ return self.perform_v3_request(method='GET',
+ path='commentThreads',
+ params=params,
+ no_login=True,
+ **kwargs)
+
+ def get_child_comments(self,
+ parent_id,
+ page_token='',
+ max_results=0,
+ **kwargs):
max_results = self._max_results if max_results <= 0 else max_results
# prepare params
@@ -665,9 +788,13 @@ def get_child_comments(self, parent_id, page_token='', max_results=0):
if page_token:
params['pageToken'] = page_token
- return self.perform_v3_request(method='GET', path='comments', params=params, no_login=True)
+ return self.perform_v3_request(method='GET',
+ path='comments',
+ params=params,
+ no_login=True,
+ **kwargs)
- def get_channel_videos(self, channel_id, page_token=''):
+ def get_channel_videos(self, channel_id, page_token='', **kwargs):
"""
Returns a collection of video search results for the specified channel_id
"""
@@ -687,10 +814,21 @@ def get_channel_videos(self, channel_id, page_token=''):
if page_token:
params['pageToken'] = page_token
- return self.perform_v3_request(method='GET', path='search', params=params)
-
- def search(self, q, search_type=None, event_type='', channel_id='',
- order='relevance', safe_search='moderate', page_token='', location=False):
+ return self.perform_v3_request(method='GET',
+ path='search',
+ params=params,
+ **kwargs)
+
+ def search(self,
+ q,
+ search_type=None,
+ event_type='',
+ channel_id='',
+ 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
@@ -754,9 +892,12 @@ def search(self, q, search_type=None, event_type='', channel_id='',
params['location'] = location
params['locationRadius'] = _context.get_settings().get_location_radius()
- return self.perform_v3_request(method='GET', path='search', params=params)
+ return self.perform_v3_request(method='GET',
+ path='search',
+ params=params,
+ **kwargs)
- def get_my_subscriptions(self, page_token=None, offset=0):
+ def get_my_subscriptions(self, page_token=None, offset=0, **kwargs):
"""
modified by PureHemp, using YouTube RSS for fetching latest videos
"""
@@ -805,19 +946,22 @@ def _perform(_page_token, _offset, _result):
if sub_page_token:
params['pageToken'] = sub_page_token
- sub_json_data = self.perform_v3_request(method='GET', path='subscriptions', params=params)
+ json_data = self.perform_v3_request(method='GET',
+ path='subscriptions',
+ params=params,
+ **kwargs)
- if not sub_json_data:
- sub_json_data = {}
+ if not json_data:
+ json_data = {}
- items = sub_json_data.get('items', [])
+ items = json_data.get('items', [])
for item in items:
item = item.get('snippet', {}).get('resourceId', {}).get('channelId', '')
sub_channel_ids.append(item)
# get next token if exists
- sub_page_token = sub_json_data.get('nextPageToken', '')
+ sub_page_token = json_data.get('nextPageToken', '')
# terminate loop when last page
if not sub_page_token:
@@ -1045,19 +1189,82 @@ def _perform(_playlist_idx, _page_token, _offset, _result):
return result
- def perform_v3_request(self, method='GET', headers=None, path=None,
- post_data=None, params=None, no_login=False):
+ @staticmethod
+ def _response_hook(**kwargs):
+ response = kwargs['response']
+ _context.log_debug('[data] v3 response: |{0.status_code}|\n'
+ '\theaders: |{0.headers}|'.format(response))
+ try:
+ json_data = response.json()
+ if 'error' in json_data:
+ raise YouTubeException('"error" in response JSON data',
+ json_data=json_data,
+ **kwargs)
+ except ValueError as error:
+ raise InvalidJSON(error, **kwargs)
+ response.raise_for_status()
+ return json_data
+ @staticmethod
+ def _error_hook(**kwargs):
+ exc = kwargs['exc']
+ json_data = getattr(exc, 'json_data', None)
+ data = getattr(exc, 'pass_data', False) and json_data
+ exception = getattr(exc, 'raise_exc', False) and YouTubeException
+
+ if not json_data or 'error' not in json_data:
+ return None, None, None, data, None, exception
+
+ details = json_data['error']
+ reason = details.get('errors', [{}])[0].get('reason', 'Unknown')
+ message = strip_html_from_text(details.get('message', 'Unknown error'))
+
+ notify = getattr(exc, 'notify', True)
+ if notify:
+ ok_dialog = False
+ timeout = 5000
+ if reason == 'accessNotConfigured':
+ notification = _context.localize('key.requirement.notification')
+ ok_dialog = True
+ elif reason == 'keyInvalid' and message == 'Bad Request':
+ notification = _context.localize('api.key.incorrect')
+ timeout = 7000
+ elif reason in ('quotaExceeded', 'dailyLimitExceeded'):
+ notification = message
+ timeout = 7000
+ else:
+ notification = message
+
+ title = '{0}: {1}'.format(_context.get_name(), reason)
+ if ok_dialog:
+ _context.get_ui().on_ok(title, notification)
+ else:
+ _context.get_ui().show_notification(notification,
+ title,
+ time_ms=timeout)
+
+ info = ('[data] v3 error: {reason}\n'
+ '\texc: |{exc}|\n'
+ '\tmessage: |{message}|')
+ details = {'reason': reason, 'message': message}
+ return '', info, details, data, False, exception
+
+ def perform_v3_request(self, method='GET', headers=None, path=None,
+ post_data=None, params=None, no_login=False,
+ **kwargs):
# params
_params = {}
# headers
_headers = {'Host': 'www.googleapis.com',
- 'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.36 Safari/537.36',
+ 'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64)'
+ ' AppleWebKit/537.36 (KHTML, like Gecko)'
+ ' Chrome/39.0.2171.36 Safari/537.36',
'Accept-Encoding': 'gzip, deflate'}
# a config can decide if a token is allowed
- if self._access_token and self._config.get('token-allowed', True) and not no_login:
+ if (not no_login and self._access_token
+ and self._config.get('token-allowed', True)):
_headers['Authorization'] = 'Bearer %s' % self._access_token
else:
_params['key'] = self._config_tv['key']
@@ -1074,23 +1281,26 @@ def perform_v3_request(self, method='GET', headers=None, path=None,
log_params['location'] = 'xx.xxxx,xx.xxxx'
else:
log_params = None
- _context.log_debug('[data] v3 request: |{0}| path: |{1}| params: |{2}| post_data: |{3}|'.format(method, path, log_params, post_data))
-
- result = self.request(_url, method=method, headers=_headers, json=post_data, params=_params)
- if result is None:
- return {}
-
- _context.log_debug('[data] v3 response: |{0}| headers: |{1}|'.format(result.status_code, result.headers))
- if result.headers.get('content-type', '').startswith('application/json'):
- try:
- return result.json()
- except ValueError:
- return {
- 'status_code': result.status_code,
- 'payload': result.text
- }
- return {}
+ _context.log_debug('[data] v3 request: |{method}|\n'
+ '\tpath: |{path}|\n'
+ '\tparams: |{params}|\n'
+ '\tpost_data: |{data}|\n'
+ '\theaders: |{headers}|'.format(method=method,
+ path=path,
+ params=log_params,
+ data=post_data,
+ headers=_headers))
+
+ json_data = self.request(_url,
+ method=method,
+ headers=_headers,
+ json=post_data,
+ params=_params,
+ response_hook=self._response_hook,
+ response_hook_kwargs=kwargs,
+ error_hook=self._error_hook)
+ return json_data
def perform_v1_tv_request(self, method='GET', headers=None, path=None,
post_data=None, params=None, no_login=False):
diff --git a/resources/lib/youtube_plugin/youtube/helper/v3.py b/resources/lib/youtube_plugin/youtube/helper/v3.py
index e0e66f463..295c25efa 100644
--- a/resources/lib/youtube_plugin/youtube/helper/v3.py
+++ b/resources/lib/youtube_plugin/youtube/helper/v3.py
@@ -24,7 +24,6 @@
from ..helper import yt_context_menu
from ...kodion import KodionException
from ...kodion.items import DirectoryItem, NextPageItem, VideoItem
-from ...kodion.utils import strip_html_from_text
def _process_list_response(provider, context, json_data):
@@ -434,39 +433,6 @@ def response_to_items(provider, context, json_data, sort=None, reverse=False, pr
return result
-def handle_error(context, json_data):
- if json_data and 'error' in json_data:
- ok_dialog = False
- message_timeout = 5000
-
- message = strip_html_from_text(json_data['error'].get('message', ''))
- log_message = strip_html_from_text(json_data['error'].get('message', ''))
- reason = json_data['error']['errors'][0].get('reason', '')
- title = '%s: %s' % (context.get_name(), reason)
-
- context.log_error('Error reason: |%s| with message: |%s|' % (reason, log_message))
-
- if reason == 'accessNotConfigured':
- message = context.localize('key.requirement.notification')
- ok_dialog = True
-
- if reason == 'keyInvalid' and message == 'Bad Request':
- message = context.localize('api.key.incorrect')
- message_timeout = 7000
-
- if reason in {'quotaExceeded', 'dailyLimitExceeded'}:
- message_timeout = 7000
-
- if ok_dialog:
- context.get_ui().on_ok(title, message)
- else:
- context.get_ui().show_notification(message, title, time_ms=message_timeout)
-
- return False
-
- return True
-
-
def _parse_kind(item):
parts = item.get('kind', '').split('#')
is_youtube = parts[0] == 'youtube'
diff --git a/resources/lib/youtube_plugin/youtube/helper/yt_play.py b/resources/lib/youtube_plugin/youtube/helper/yt_play.py
index a233a7bc0..92ad9b9b1 100644
--- a/resources/lib/youtube_plugin/youtube/helper/yt_play.py
+++ b/resources/lib/youtube_plugin/youtube/helper/yt_play.py
@@ -246,11 +246,18 @@ def play_channel_live(provider, context):
index = context.get_param('live') - 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)
- if not v3.handle_error(context, json_data):
+ json_data = provider.get_client(context).search(q='',
+ search_type='video',
+ event_type='live',
+ channel_id=channel_id,
+ safe_search=False)
+ if not json_data:
return False
- video_items = v3.response_to_items(provider, context, json_data, process_next_page=False)
+ video_items = v3.response_to_items(provider,
+ context,
+ json_data,
+ process_next_page=False)
try:
video_item = video_items[index]
diff --git a/resources/lib/youtube_plugin/youtube/helper/yt_playlist.py b/resources/lib/youtube_plugin/youtube/helper/yt_playlist.py
index ffb69fdb3..b5fbacca6 100644
--- a/resources/lib/youtube_plugin/youtube/helper/yt_playlist.py
+++ b/resources/lib/youtube_plugin/youtube/helper/yt_playlist.py
@@ -38,7 +38,7 @@ def _process_add_video(provider, context, keymap_action=False):
if playlist_id != 'HL':
json_data = client.add_video_to_playlist(playlist_id=playlist_id, video_id=video_id)
- if not v3.handle_error(context, json_data):
+ if not json_data:
return False
if playlist_id == watch_later_id:
@@ -96,7 +96,7 @@ def _process_remove_video(provider, context):
if context.get_ui().on_remove_content(video_name):
json_data = provider.get_client(context).remove_video_from_playlist(playlist_id=playlist_id,
playlist_item_id=video_id)
- if not v3.handle_error(context, json_data):
+ if not json_data:
return False
context.get_ui().refresh_container()
@@ -128,7 +128,7 @@ def _process_remove_playlist(provider, context):
if context.get_ui().on_delete_content(playlist_name):
json_data = provider.get_client(context).remove_playlist(playlist_id=playlist_id)
- if not v3.handle_error(context, json_data):
+ if not json_data:
return False
context.get_ui().refresh_container()
@@ -203,7 +203,7 @@ def _process_select_playlist(provider, context):
context.localize('playlist.create'))
if result and text:
json_data = client.create_playlist(title=text)
- if not v3.handle_error(context, json_data):
+ if not json_data:
break
playlist_id = json_data.get('id', '')
@@ -236,7 +236,7 @@ def _process_rename_playlist(provider, context):
default=current_playlist_name)
if result and text:
json_data = provider.get_client(context).rename_playlist(playlist_id=playlist_id, new_title=text)
- if not v3.handle_error(context, json_data):
+ if not json_data:
return
context.get_ui().refresh_container()
diff --git a/resources/lib/youtube_plugin/youtube/helper/yt_specials.py b/resources/lib/youtube_plugin/youtube/helper/yt_specials.py
index ea2e29a1a..5a4456ba8 100644
--- a/resources/lib/youtube_plugin/youtube/helper/yt_specials.py
+++ b/resources/lib/youtube_plugin/youtube/helper/yt_specials.py
@@ -33,7 +33,7 @@ def _process_related_videos(provider, context):
json_data = provider.get_client(context).get_related_videos(
video_id=video_id, page_token=context.get_param('page_token', '')
)
- if not v3.handle_error(context, json_data):
+ if not json_data:
return False
return v3.response_to_items(provider,
context,
@@ -50,7 +50,7 @@ def _process_parent_comments(provider, context):
json_data = provider.get_client(context).get_parent_comments(
video_id=video_id, page_token=context.get_param('page_token', '')
)
- if not v3.handle_error(context, json_data):
+ if not json_data:
return False
return v3.response_to_items(provider, context, json_data)
@@ -64,7 +64,7 @@ def _process_child_comments(provider, context):
json_data = provider.get_client(context).get_child_comments(
parent_id=parent_id, page_token=context.get_param('page_token', '')
)
- if not v3.handle_error(context, json_data):
+ if not json_data:
return False
return v3.response_to_items(provider, context, json_data)
@@ -74,7 +74,7 @@ def _process_recommendations(provider, context):
json_data = provider.get_client(context).get_activities(
channel_id='home', page_token=context.get_param('page_token', '')
)
- if not v3.handle_error(context, json_data):
+ if not json_data:
return False
return v3.response_to_items(provider, context, json_data)
@@ -84,7 +84,7 @@ def _process_popular_right_now(provider, context):
json_data = provider.get_client(context).get_popular_videos(
page_token=context.get_param('page_token', '')
)
- if not v3.handle_error(context, json_data):
+ if not json_data:
return False
return v3.response_to_items(provider, context, json_data)
@@ -95,14 +95,14 @@ def _process_browse_channels(provider, context):
guide_id = context.get_param('guide_id', '')
if guide_id:
json_data = client.get_guide_category(guide_id)
- if not v3.handle_error(context, json_data):
+ if not json_data:
return False
return v3.response_to_items(provider, context, json_data)
function_cache = context.get_function_cache()
json_data = function_cache.get(client.get_guide_categories,
function_cache.ONE_MONTH)
- if not v3.handle_error(context, json_data):
+ if not json_data:
return False
return v3.response_to_items(provider, context, json_data)
@@ -112,7 +112,7 @@ def _process_disliked_videos(provider, context):
json_data = provider.get_client(context).get_disliked_videos(
page_token=context.get_param('page_token', '')
)
- if not v3.handle_error(context, json_data):
+ if not json_data:
return False
return v3.response_to_items(provider, context, json_data)
@@ -128,7 +128,7 @@ def _sort(x):
page_token=context.get_param('page_token', ''),
location=context.get_param('location', False),
)
- if not v3.handle_error(context, json_data):
+ if not json_data:
return False
return v3.response_to_items(provider, context, json_data, sort=_sort)
diff --git a/resources/lib/youtube_plugin/youtube/helper/yt_subscriptions.py b/resources/lib/youtube_plugin/youtube/helper/yt_subscriptions.py
index c90893947..39e81309b 100644
--- a/resources/lib/youtube_plugin/youtube/helper/yt_subscriptions.py
+++ b/resources/lib/youtube_plugin/youtube/helper/yt_subscriptions.py
@@ -21,7 +21,7 @@ def _process_list(provider, context):
page_token = context.get_param('page_token', '')
# no caching
json_data = provider.get_client(context).get_subscription('mine', page_token=page_token)
- if not v3.handle_error(context, json_data):
+ if not json_data:
return []
result.extend(v3.response_to_items(provider, context, json_data))
@@ -38,7 +38,7 @@ def _process_add(provider, context):
if subscription_id:
json_data = provider.get_client(context).subscribe(subscription_id)
- if not v3.handle_error(context, json_data):
+ if not json_data:
return False
context.get_ui().show_notification(
@@ -61,7 +61,7 @@ def _process_remove(provider, context):
if subscription_id:
json_data = provider.get_client(context).unsubscribe(subscription_id)
- if not v3.handle_error(context, json_data):
+ if not json_data:
return False
context.get_ui().refresh_container()
diff --git a/resources/lib/youtube_plugin/youtube/helper/yt_video.py b/resources/lib/youtube_plugin/youtube/helper/yt_video.py
index 63c517875..9ce0f8523 100644
--- a/resources/lib/youtube_plugin/youtube/helper/yt_video.py
+++ b/resources/lib/youtube_plugin/youtube/helper/yt_video.py
@@ -42,7 +42,7 @@ def _process_rate_video(provider, context, re_match):
if not current_rating:
client = provider.get_client(context)
json_data = client.get_video_rating(video_id)
- if not v3.handle_error(context, json_data):
+ if not json_data:
return False
items = json_data.get('items', [])
diff --git a/resources/lib/youtube_plugin/youtube/provider.py b/resources/lib/youtube_plugin/youtube/provider.py
index e6d5740ad..4bdc07206 100644
--- a/resources/lib/youtube_plugin/youtube/provider.py
+++ b/resources/lib/youtube_plugin/youtube/provider.py
@@ -49,7 +49,6 @@ def __init__(self):
self._client = None
self._is_logged_in = False
- self.v3_handle_error = v3.handle_error
self.yt_video = yt_video
def get_wizard_supported_views(self):
@@ -354,7 +353,7 @@ def _on_channel_playlists(self, context, re_match):
# no caching
json_data = self.get_client(context).get_playlists_of_channel(channel_id, page_token)
- if not v3.handle_error(context, json_data):
+ if not json_data:
return False
result.extend(v3.response_to_items(self, context, json_data))
@@ -377,7 +376,7 @@ def _on_channel_live(self, context, re_match):
# no caching
json_data = self.get_client(context).search(q='', search_type='video', event_type='live', channel_id=channel_id, page_token=page_token, safe_search=safe_search)
- if not v3.handle_error(context, json_data):
+ if not json_data:
return False
result.extend(v3.response_to_items(self, context, json_data))
@@ -428,7 +427,7 @@ def _on_channel(self, context, re_match):
json_data = function_cache.get(client.get_channel_by_username,
function_cache.ONE_DAY,
channel_id)
- if not v3.handle_error(context, json_data):
+ if not json_data:
return False
# we correct the channel id based on the username
@@ -490,7 +489,7 @@ def _on_channel(self, context, re_match):
function_cache.ONE_MINUTE * 5,
upload_playlist,
page_token=page_token)
- if not v3.handle_error(context, json_data):
+ if not json_data:
return False
result.extend(v3.response_to_items(self, context, json_data))
@@ -821,7 +820,7 @@ def _search_channel_or_playlist(self, context, id_string):
elif re.match(r'[OP]L[0-9a-zA-Z_\-]{30,40}', id_string):
json_data = self.get_client(context).get_playlists(id_string)
- if not json_data or not v3.handle_error(context, json_data):
+ if not json_data:
return []
result.extend(v3.response_to_items(self, context, json_data))
@@ -893,7 +892,7 @@ def on_search(self, search_text, context, re_match):
page_token=page_token,
channel_id=channel_id,
location=location)
- if not v3.handle_error(context, json_data):
+ if not json_data:
return False
result.extend(v3.response_to_items(self, context, json_data))
return result
diff --git a/resources/lib/youtube_requests.py b/resources/lib/youtube_requests.py
index b25204c74..391018b8f 100644
--- a/resources/lib/youtube_requests.py
+++ b/resources/lib/youtube_requests.py
@@ -30,17 +30,6 @@ def __get_core_components(addon_id=None):
return provider, context, client
-def handle_error(context, json_data):
- if json_data and 'error' in json_data:
- message = json_data['error'].get('message', '')
- reason = json_data['error']['errors'][0].get('reason', '')
- context.log_error('Error reason: |%s| with message: |%s|' % (reason, message))
-
- return False
-
- return True
-
-
def v3_request(method='GET', headers=None, path=None, post_data=None, params=None, addon_id=None):
"""
https://developers.google.com/youtube/v3/docs/
@@ -53,7 +42,14 @@ def v3_request(method='GET', headers=None, path=None, post_data=None, params=Non
:type addon_id: str
"""
provider, context, client = __get_core_components(addon_id)
- return client.perform_v3_request(method=method, headers=headers, path=path, post_data=post_data, params=params)
+ return client.perform_v3_request(method=method,
+ headers=headers,
+ path=path,
+ post_data=post_data,
+ params=params,
+ notify=False,
+ pass_data=True,
+ raise_exc=False)
def _append_missing_page_token(items):
@@ -76,11 +72,14 @@ def get_videos(video_id, addon_id=None):
"""
provider, context, client = __get_core_components(addon_id)
- json_data = client.get_videos(video_id)
- if not handle_error(context, json_data):
+ json_data = client.get_videos(video_id,
+ notify=False,
+ pass_data=True,
+ raise_exc=False)
+ if not json_data or 'error' in json_data:
return [json_data]
- return json_data.get('items', [])
+ return json_data.get('items', [{}])
def get_activities(channel_id, page_token='', all_pages=False, addon_id=None):
@@ -103,11 +102,15 @@ def get_activities(channel_id, page_token='', all_pages=False, addon_id=None):
items = []
def get_items(_page_token=''):
- json_data = client.get_activities(channel_id, page_token=_page_token)
- if not handle_error(context, json_data):
+ json_data = client.get_activities(channel_id,
+ page_token=_page_token,
+ notify=False,
+ pass_data=True,
+ raise_exc=False)
+ if not json_data or 'error' in json_data:
return [json_data]
- items.extend(json_data.get('items', []))
+ items.extend(json_data.get('items', [{}]))
error = False
next_page_token = json_data.get('nextPageToken')
@@ -148,11 +151,15 @@ def get_playlist_items(playlist_id, page_token='', all_pages=False, addon_id=Non
items = []
def get_items(_page_token=''):
- json_data = client.get_playlist_items(playlist_id, page_token=_page_token)
- if not handle_error(context, json_data):
+ json_data = client.get_playlist_items(playlist_id,
+ page_token=_page_token,
+ notify=False,
+ pass_data=True,
+ raise_exc=False)
+ if not json_data or 'error' in json_data:
return [json_data]
- items.extend(json_data.get('items', []))
+ items.extend(json_data.get('items', [{}]))
error = False
next_page_token = json_data.get('nextPageToken')
@@ -185,11 +192,14 @@ def get_channel_id(channel_name, addon_id=None):
"""
provider, context, client = __get_core_components(addon_id)
- json_data = client.get_channel_by_username(channel_name)
- if not handle_error(context, json_data):
+ json_data = client.get_channel_by_username(channel_name,
+ notify=False,
+ pass_data=True,
+ raise_exc=False)
+ if not json_data or 'error' in json_data:
return [json_data]
- return json_data.get('items', [])
+ return json_data.get('items', [{}])
def get_channels(channel_id, addon_id=None):
@@ -205,11 +215,14 @@ def get_channels(channel_id, addon_id=None):
"""
provider, context, client = __get_core_components(addon_id)
- json_data = client.get_channels(channel_id)
- if not handle_error(context, json_data):
+ json_data = client.get_channels(channel_id,
+ notify=False,
+ pass_data=True,
+ raise_exc=False)
+ if not json_data or 'error' in json_data:
return [json_data]
- return json_data.get('items', [])
+ return json_data.get('items', [{}])
def get_channel_sections(channel_id, addon_id=None):
@@ -225,11 +238,14 @@ def get_channel_sections(channel_id, addon_id=None):
"""
provider, context, client = __get_core_components(addon_id)
- json_data = client.get_channel_sections(channel_id)
- if not handle_error(context, json_data):
+ json_data = client.get_channel_sections(channel_id,
+ notify=False,
+ pass_data=True,
+ raise_exc=False)
+ if not json_data or 'error' in json_data:
return [json_data]
- return json_data.get('items', [])
+ return json_data.get('items', [{}])
def get_playlists_of_channel(channel_id, page_token='', all_pages=False, addon_id=None):
@@ -253,11 +269,15 @@ def get_playlists_of_channel(channel_id, page_token='', all_pages=False, addon_i
items = []
def get_items(_page_token=''):
- json_data = client.get_playlists_of_channel(channel_id, page_token=_page_token)
- if not handle_error(context, json_data):
+ json_data = client.get_playlists_of_channel(channel_id,
+ page_token=_page_token,
+ notify=False,
+ pass_data=True,
+ raise_exc=False)
+ if not json_data or 'error' in json_data:
return [json_data]
- items.extend(json_data.get('items', []))
+ items.extend(json_data.get('items', [{}]))
error = False
next_page_token = json_data.get('nextPageToken')
@@ -290,11 +310,14 @@ def get_playlists(playlist_id, addon_id=None):
"""
provider, context, client = __get_core_components(addon_id)
- json_data = client.get_playlists(playlist_id)
- if not handle_error(context, json_data):
+ json_data = client.get_playlists(playlist_id,
+ notify=False,
+ pass_data=True,
+ raise_exc=False)
+ if not json_data or 'error' in json_data:
return [json_data]
- return json_data.get('items', [])
+ return json_data.get('items', [{}])
def get_related_videos(video_id, page_token='', addon_id=None):
@@ -317,11 +340,15 @@ def get_related_videos(video_id, page_token='', addon_id=None):
items = []
def get_items(_page_token=''):
- json_data = client.get_related_videos(video_id, page_token=_page_token)
- if not handle_error(context, json_data):
+ json_data = client.get_related_videos(video_id,
+ page_token=_page_token,
+ notify=False,
+ pass_data=True,
+ raise_exc=False)
+ if not json_data or 'error' in json_data:
return [json_data]
- items.extend([item for item in json_data.get('items', [])
+ items.extend([item for item in json_data.get('items', [{}])
if 'snippet' in item])
error = False
@@ -369,12 +396,20 @@ def get_search(q, search_type='', event_type='', channel_id='', order='relevance
items = []
def get_items(_page_token=''):
- json_data = client.search(q, search_type=search_type, event_type=event_type, channel_id=channel_id,
- order=order, safe_search=safe_search, page_token=_page_token)
- if not handle_error(context, json_data):
+ json_data = client.search(q,
+ search_type=search_type,
+ event_type=event_type,
+ channel_id=channel_id,
+ order=order,
+ safe_search=safe_search,
+ page_token=_page_token,
+ notify=False,
+ pass_data=True,
+ raise_exc=False)
+ if not json_data or 'error' in json_data:
return [json_data]
- items.extend(json_data.get('items', []))
+ items.extend(json_data.get('items', [{}]))
error = False
next_page_token = json_data.get('nextPageToken')
From b209931f4c570f77203d6c15dd262f4e6b070a82 Mon Sep 17 00:00:00 2001
From: MoojMidge <56883549+MoojMidge@users.noreply.github.com>
Date: Fri, 15 Dec 2023 15:14:17 +1100
Subject: [PATCH 107/141] Add caching of playlistitems requests
- Partially fixes #545
- Results will be cached for 1 hour
- Also re-sorts cached values so they match the original requested order
---
.../youtube/helper/resource_manager.py | 431 ++++++++++--------
.../youtube_plugin/youtube/helper/yt_play.py | 129 +++---
.../lib/youtube_plugin/youtube/provider.py | 37 +-
3 files changed, 302 insertions(+), 295 deletions(-)
diff --git a/resources/lib/youtube_plugin/youtube/helper/resource_manager.py b/resources/lib/youtube_plugin/youtube/helper/resource_manager.py
index 60e3aa4b4..8e29b9ece 100644
--- a/resources/lib/youtube_plugin/youtube/helper/resource_manager.py
+++ b/resources/lib/youtube_plugin/youtube/helper/resource_manager.py
@@ -10,180 +10,220 @@
from __future__ import absolute_import, division, unicode_literals
-from ..youtube_exceptions import YouTubeException
-from ...kodion.utils import strip_html_from_text
-
class ResourceManager(object):
def __init__(self, context, client):
self._context = context
self._client = client
- self._channel_data = {}
- self._video_data = {}
- self._playlist_data = {}
- self._enable_channel_fanart = context.get_settings().get_bool('youtube.channel.fanart.show', True)
+ self._data_cache = context.get_data_cache()
+ self._func_cache = context.get_function_cache()
+ self._show_fanart = context.get_settings().get_bool(
+ 'youtube.channel.fanart.show', True
+ )
+
+ @staticmethod
+ def _list_batch(input_list, n=50):
+ if not isinstance(input_list, (list, tuple)):
+ input_list = list(input_list)
+ for i in range(0, len(input_list), n):
+ yield input_list[i:i + n]
def clear(self):
- self._context.get_function_cache().clear()
- self._context.get_data_cache().clear()
-
- def _get_channel_data(self, channel_id):
- return self._channel_data.get(channel_id, {})
-
- def _get_video_data(self, video_id):
- return self._video_data.get(video_id, {})
-
- def _get_playlist_data(self, playlist_id):
- return self._playlist_data.get(playlist_id, {})
-
- def _update_channels(self, channel_ids):
- json_data = None
- updated_channel_ids = []
- function_cache = self._context.get_function_cache()
-
- for channel_id in channel_ids:
- if channel_id == 'mine':
- json_data = function_cache.get(self._client.get_channel_by_username,
- function_cache.ONE_DAY,
- channel_id)
- items = json_data.get('items', [{'id': 'mine'}])
-
- try:
- channel_id = items[0]['id']
- except IndexError:
- self._context.log_debug('Channel "mine" not found: %s' % json_data)
- channel_id = None
-
- json_data = None
-
- if channel_id:
- updated_channel_ids.append(channel_id)
-
- channel_ids = updated_channel_ids
-
- data_cache = self._context.get_data_cache()
- channel_data = data_cache.get_items(channel_ids, data_cache.ONE_MONTH)
-
- channel_ids = set(channel_ids)
- channel_ids_cached = set(channel_data)
- channel_ids_to_update = channel_ids - channel_ids_cached
- channel_ids_cached = channel_ids & channel_ids_cached
-
- result = channel_data
- if channel_ids_cached:
- self._context.log_debug('Found cached data for channels |%s|' % ', '.join(channel_ids_cached))
-
- if channel_ids_to_update:
- self._context.log_debug('No data for channels |%s| cached' % ', '.join(channel_ids_to_update))
- json_data = [
- self._client.get_channels(list_of_50)
- for list_of_50 in self._list_batch(channel_ids_to_update, n=50)
- ]
- channel_data = {
+ self._func_cache.clear()
+ self._data_cache.clear()
+
+ def get_channels(self, ids):
+ updated = []
+ for channel_id in ids:
+ if not channel_id:
+ continue
+
+ if channel_id != 'mine':
+ updated.append(channel_id)
+ continue
+
+ data = self._func_cache.get(self._client.get_channel_by_username,
+ self._func_cache.ONE_DAY,
+ channel_id)
+ items = data.get('items', [{'id': 'mine'}])
+
+ try:
+ channel_id = items[0]['id']
+ updated.append(channel_id)
+ except IndexError:
+ self._context.log_error('Channel not found:\n\t{data}'
+ .format(data=data))
+
+ ids = updated
+ result = self._data_cache.get_items(ids, self._data_cache.ONE_MONTH)
+ to_update = [id_ for id_ in ids if id_ not in result]
+
+ if result:
+ self._context.log_debug('Found cached data for channels:\n|{ids}|'
+ .format(ids=list(result)))
+
+ if to_update:
+ new_data = [self._client.get_channels(list_of_50)
+ for list_of_50 in self._list_batch(to_update, n=50)]
+ if not any(new_data):
+ new_data = None
+ else:
+ new_data = None
+
+ if new_data:
+ self._context.log_debug('Got data for channels:\n|{ids}|'
+ .format(ids=to_update))
+ new_data = {
yt_item['id']: yt_item
- for batch in json_data
+ for batch in new_data
for yt_item in batch.get('items', [])
if yt_item
}
- result.update(channel_data)
- data_cache.set_items(channel_data)
- self._context.log_debug('Cached data for channels |%s|' % ', '.join(channel_data))
-
- if self.handle_error(json_data):
- return result
- return {}
-
- def _update_videos(self, video_ids, live_details=False, suppress_errors=False):
- json_data = None
- data_cache = self._context.get_data_cache()
- video_data = data_cache.get_items(video_ids, data_cache.ONE_MONTH)
-
- video_ids = set(video_ids)
- video_ids_cached = set(video_data)
- video_ids_to_update = video_ids - video_ids_cached
- video_ids_cached = video_ids & video_ids_cached
-
- result = video_data
- if video_ids_cached:
- self._context.log_debug('Found cached data for videos |%s|' % ', '.join(video_ids_cached))
-
- if video_ids_to_update:
- self._context.log_debug('No data for videos |%s| cached' % ', '.join(video_ids_to_update))
- json_data = self._client.get_videos(video_ids_to_update, live_details)
- video_data = dict.fromkeys(video_ids_to_update, {})
- video_data.update({
- yt_item['id']: yt_item or {}
- for yt_item in json_data.get('items', [])
- })
- result.update(video_data)
- data_cache.set_items(video_data)
- self._context.log_debug('Cached data for videos |%s|' % ', '.join(video_data))
-
- if self._context.get_settings().use_local_history():
- playback_history = self._context.get_playback_history()
- played_items = playback_history.get_items(video_ids)
- for video_id, play_data in played_items.items():
- result[video_id]['play_data'] = play_data
+ result.update(new_data)
+ self._data_cache.set_items(new_data)
+ self._context.log_debug('Cached data for channels:\n|{ids}|'
+ .format(ids=list(new_data)))
+
+ # Re-sort result to match order of requested IDs
+ # Will only work in Python v3.7+
+ if list(result) != ids:
+ result = {
+ id: result[id]
+ for id in ids
+ if id in result
+ }
- if self.handle_error(json_data, suppress_errors) or suppress_errors:
- return result
- return {}
+ return result
- @staticmethod
- def _list_batch(input_list, n=50):
- if not isinstance(input_list, (list, tuple)):
- input_list = list(input_list)
- for i in range(0, len(input_list), n):
- yield input_list[i:i + n]
+ def get_fanarts(self, channel_ids):
+ if not self._show_fanart:
+ return {}
- def get_videos(self, video_ids, live_details=False, suppress_errors=False):
- list_of_50s = self._list_batch(video_ids, n=50)
+ result = self.get_channels(channel_ids)
+ banners = ['bannerTvMediumImageUrl', 'bannerTvLowImageUrl',
+ 'bannerTvImageUrl', 'bannerExternalUrl']
+ # transform
+ for key, item in result.items():
+ images = item.get('brandingSettings', {}).get('image', {})
+ for banner in banners:
+ image = images.get(banner)
+ if not image:
+ continue
+ result[key] = image
+ break
+ else:
+ # set an empty url
+ result[key] = ''
- result = {}
- for list_of_50 in list_of_50s:
- result.update(self._update_videos(list_of_50, live_details, suppress_errors))
return result
- def _update_playlists(self, playlists_ids):
- json_data = None
- data_cache = self._context.get_data_cache()
- playlist_data = data_cache.get_items(playlists_ids, data_cache.ONE_MONTH)
+ def get_playlists(self, ids):
+ result = self._data_cache.get_items(ids, self._data_cache.ONE_MONTH)
+ to_update = [id_ for id_ in ids if id_ not in result]
- playlists_ids = set(playlists_ids)
- playlists_ids_cached = set(playlist_data)
- playlist_ids_to_update = playlists_ids - playlists_ids_cached
- playlists_ids_cached = playlists_ids & playlists_ids_cached
+ if result:
+ self._context.log_debug('Found cached data for playlists:\n|{ids}|'
+ .format(ids=list(result)))
- result = playlist_data
- if playlists_ids_cached:
- self._context.log_debug('Found cached data for playlists |%s|' % ', '.join(playlists_ids_cached))
+ if to_update:
+ new_data = [self._client.get_playlists(list_of_50)
+ for list_of_50 in self._list_batch(to_update, n=50)]
+ if not any(new_data):
+ new_data = None
+ else:
+ new_data = None
- if playlist_ids_to_update:
- self._context.log_debug('No data for playlists |%s| cached' % ', '.join(playlist_ids_to_update))
- json_data = self._client.get_playlists(playlist_ids_to_update)
- playlist_data = {
+ if new_data:
+ self._context.log_debug('Got data for playlists:\n|{ids}|'
+ .format(ids=to_update))
+ new_data = {
yt_item['id']: yt_item
- for yt_item in json_data.get('items', [])
+ for batch in new_data
+ for yt_item in batch.get('items', [])
if yt_item
}
- result.update(playlist_data)
- data_cache.set_items(playlist_data)
- self._context.log_debug('Cached data for playlists |%s|' % ', '.join(playlist_data))
+ result.update(new_data)
+ self._data_cache.set_items(new_data)
+ self._context.log_debug('Cached data for playlists:\n|{ids}|'
+ .format(ids=list(new_data)))
+
+ # Re-sort result to match order of requested IDs
+ # Will only work in Python v3.7+
+ if list(result) != ids:
+ result = {
+ id: result[id]
+ for id in ids
+ if id in result
+ }
- if self.handle_error(json_data):
- return result
- return {}
+ return result
- def get_playlists(self, playlists_ids):
- list_of_50s = self._list_batch(playlists_ids, n=50)
+ def get_playlist_items(self, ids=None, batch_id=None):
+ if not ids and not batch_id:
+ return None
+ if batch_id:
+ ids = [batch_id[0]]
+ page_token = batch_id[1]
+ fetch_next = False
+ else:
+ page_token = None
+ fetch_next = True
+
+ batch_ids = []
+ to_update = []
result = {}
- for list_of_50 in list_of_50s:
- result.update(self._update_playlists(list_of_50))
+ for playlist_id in ids:
+ page_token = page_token or 0
+ while 1:
+ batch_id = (playlist_id, page_token)
+ batch_ids.append(batch_id)
+ batch = self._data_cache.get_item(batch_id,
+ self._data_cache.ONE_HOUR)
+ if not batch:
+ to_update.append(batch_id)
+ break
+ result[batch_id] = batch
+ page_token = batch.get('nextPageToken') if fetch_next else None
+ if page_token is None:
+ break
+
+ if result:
+ self._context.log_debug('Found cached items for playlists:\n|{ids}|'
+ .format(ids=list(result)))
+
+ new_data = {}
+ for playlist_id, page_token in to_update:
+ while 1:
+ batch_id = (playlist_id, page_token)
+ batch = self._client.get_playlist_items(*batch_id)
+ new_data[batch_id] = batch
+ page_token = batch.get('nextPageToken') if fetch_next else None
+ if page_token is None:
+ break
+
+ if new_data:
+ to_update = list(new_data)
+ self._context.log_debug('Got items for playlists:\n|{ids}|'
+ .format(ids=to_update))
+ self._data_cache.set_items(new_data)
+ result.update(new_data)
+ self._context.log_debug('Cached items for playlists:\n|{ids}|'
+ .format(ids=to_update))
+
+ # Re-sort result to match order of requested IDs
+ # Will only work in Python v3.7+
+ if list(result) != batch_ids:
+ result = {
+ id: result[id]
+ for id in batch_ids
+ if id in result
+ }
+
return result
def get_related_playlists(self, channel_id):
- result = self._update_channels([channel_id])
+ result = self.get_channels([channel_id])
# transform
item = None
@@ -199,65 +239,52 @@ def get_related_playlists(self, channel_id):
return item.get('contentDetails', {}).get('relatedPlaylists', {})
- def get_channels(self, channel_ids):
- list_of_50s = self._list_batch(channel_ids, n=50)
-
- result = {}
- for list_of_50 in list_of_50s:
- result.update(self._update_channels(list_of_50))
- return result
+ def get_videos(self, ids, live_details=False, suppress_errors=False):
+ result = self._data_cache.get_items(ids, self._data_cache.ONE_MONTH)
+ to_update = [id_ for id_ in ids if id_ not in result]
+
+ if result:
+ self._context.log_debug('Found cached data for videos:\n|{ids}|'
+ .format(ids=list(result)))
+
+ if to_update:
+ notify_and_raise = not suppress_errors
+ new_data = [self._client.get_videos(list_of_50,
+ live_details,
+ notify=notify_and_raise,
+ raise_exc=notify_and_raise)
+ for list_of_50 in self._list_batch(to_update, n=50)]
+ if not any(new_data):
+ new_data = None
+ else:
+ new_data = None
- def get_fanarts(self, channel_ids):
- if not self._enable_channel_fanart:
- return {}
+ if new_data:
+ self._context.log_debug('Got data for videos:\n|{ids}|'
+ .format(ids=to_update))
+ new_data = dict(dict.fromkeys(to_update, {}), **{
+ yt_item['id']: yt_item or {}
+ for batch in new_data
+ for yt_item in batch.get('items', [])
+ })
+ result.update(new_data)
+ self._data_cache.set_items(new_data)
+ self._context.log_debug('Cached data for videos:\n|{ids}|'
+ .format(ids=list(new_data)))
+
+ # Re-sort result to match order of requested IDs
+ # Will only work in Python v3.7+
+ if list(result) != ids:
+ result = {
+ id: result[id]
+ for id in ids
+ if id in result
+ }
- result = self._update_channels(channel_ids)
- banners = ['bannerTvMediumImageUrl', 'bannerTvLowImageUrl',
- 'bannerTvImageUrl', 'bannerExternalUrl']
- # transform
- for key, item in result.items():
- images = item.get('brandingSettings', {}).get('image', {})
- for banner in banners:
- image = images.get(banner)
- if not image:
- continue
- result[key] = image
- break
- else:
- # set an empty url
- result[key] = ''
+ if self._context.get_settings().use_local_history():
+ playback_history = self._context.get_playback_history()
+ played_items = playback_history.get_items(ids)
+ for video_id, play_data in played_items.items():
+ result[video_id]['play_data'] = play_data
return result
-
- def handle_error(self, json_data, suppress_errors=False):
- context = self._context
- if json_data and 'error' in json_data:
- ok_dialog = False
- message_timeout = 5000
- message = json_data['error'].get('message', '')
- message = strip_html_from_text(message)
- reason = json_data['error']['errors'][0].get('reason', '')
- title = '%s: %s' % (context.get_name(), reason)
- error_message = 'Error reason: |%s| with message: |%s|' % (reason, message)
-
- context.log_error(error_message)
-
- if reason == 'accessNotConfigured':
- message = context.localize('key.requirement.notification')
- ok_dialog = True
-
- elif reason in {'quotaExceeded', 'dailyLimitExceeded'}:
- message_timeout = 7000
-
- if not suppress_errors:
- if ok_dialog:
- context.get_ui().on_ok(title, message)
- else:
- context.get_ui().show_notification(message, title,
- time_ms=message_timeout)
-
- raise YouTubeException(error_message)
-
- return False
-
- return True
diff --git a/resources/lib/youtube_plugin/youtube/helper/yt_play.py b/resources/lib/youtube_plugin/youtube/helper/yt_play.py
index 92ad9b9b1..8274f5b58 100644
--- a/resources/lib/youtube_plugin/youtube/helper/yt_play.py
+++ b/resources/lib/youtube_plugin/youtube/helper/yt_play.py
@@ -146,35 +146,32 @@ def play_playlist(provider, context):
if not playlist_ids:
playlist_ids = [params.get('playlist_id')]
- client = provider.get_client(context)
+ resource_manager = provider.get_resource_manager(context)
ui = context.get_ui()
- progress_dialog = ui.create_progress_dialog(
+ with ui.create_progress_dialog(
context.localize('playlist.progress.updating'),
context.localize('please_wait'),
background=True
- )
-
- # start the loop and fill the list with video items
- total = 0
- for playlist_id in playlist_ids:
- page_token = 0
- while page_token is not None:
- json_data = client.get_playlist_items(playlist_id, page_token)
- if not v3.handle_error(context, json_data):
- break
-
- if page_token == 0:
- playlist_total = int(json_data.get('pageInfo', {})
- .get('totalResults', 0))
- if not playlist_total:
- break
- total += playlist_total
- progress_dialog.set_total(total)
+ ) as progress_dialog:
+ json_data = resource_manager.get_playlist_items(playlist_ids)
+
+ total = sum(len(chunk.get('items', [])) for chunk in json_data.values())
+ progress_dialog.set_total(total)
+ progress_dialog.update(
+ steps=0,
+ text='{wait} {current}/{total}'.format(
+ wait=context.localize('please_wait'),
+ current=0,
+ total=total
+ )
+ )
+ # start the loop and fill the list with video items
+ for chunk in json_data.values():
result = v3.response_to_items(provider,
context,
- json_data,
+ chunk,
process_next_page=False)
videos.extend(result)
@@ -187,51 +184,51 @@ def play_playlist(provider, context):
)
)
- page_token = json_data.get('nextPageToken') or None
-
- # select order
- order = params.get('order', '')
- if not order:
- order_list = ['default', 'reverse', 'shuffle']
- items = [(context.localize('playlist.play.%s' % order), order)
- for order in order_list]
- order = ui.on_select(context.localize('playlist.play.select'), items)
- if order not in order_list:
- order = 'default'
-
- # reverse the list
- if order == 'reverse':
- videos = videos[::-1]
- elif order == 'shuffle':
- # we have to shuffle the playlist by our self.
- # The implementation of XBMC/KODI is quite weak :(
- random.shuffle(videos)
-
- # clear the playlist
- playlist = context.get_video_playlist()
- playlist.clear()
-
- # select unshuffle
- if order == 'shuffle':
- playlist.unshuffle()
-
- # check if we have a video as starting point for the playlist
- video_id = params.get('video_id', '')
- # add videos to playlist
- playlist_position = 0
- for idx, video in enumerate(videos):
- playlist.add(video)
- if video_id and not playlist_position and video_id in video.get_uri():
- playlist_position = idx
-
- # we use the shuffle implementation of the playlist
- """
- if order == 'shuffle':
- playlist.shuffle()
- """
-
- if progress_dialog:
- progress_dialog.close()
+ if not videos:
+ return False
+
+ # select order
+ order = params.get('order', '')
+ if not order:
+ order_list = ['default', 'reverse', 'shuffle']
+ items = [(context.localize('playlist.play.%s' % order), order)
+ for order in order_list]
+ order = ui.on_select(context.localize('playlist.play.select'),
+ items)
+ if order not in order_list:
+ order = 'default'
+
+ # reverse the list
+ if order == 'reverse':
+ videos = videos[::-1]
+ elif order == 'shuffle':
+ # we have to shuffle the playlist by our self.
+ # The implementation of XBMC/KODI is quite weak :(
+ random.shuffle(videos)
+
+ # clear the playlist
+ playlist = context.get_video_playlist()
+ playlist.clear()
+
+ # select unshuffle
+ if order == 'shuffle':
+ playlist.unshuffle()
+
+ # check if we have a video as starting point for the playlist
+ video_id = params.get('video_id', '')
+ # add videos to playlist
+ playlist_position = 0
+ for idx, video in enumerate(videos):
+ playlist.add(video)
+ if (video_id and not playlist_position
+ and video_id in video.get_uri()):
+ playlist_position = idx
+
+ # we use the shuffle implementation of the playlist
+ """
+ if order == 'shuffle':
+ playlist.shuffle()
+ """
if not params.get('play'):
return videos
diff --git a/resources/lib/youtube_plugin/youtube/provider.py b/resources/lib/youtube_plugin/youtube/provider.py
index 4bdc07206..d5950382d 100644
--- a/resources/lib/youtube_plugin/youtube/provider.py
+++ b/resources/lib/youtube_plugin/youtube/provider.py
@@ -277,45 +277,28 @@ def on_uri2addon(self, context, re_match):
return False
- @RegisterProviderPath('^/playlist/(?P[^/]+)/$')
- def _on_playlist(self, context, re_match):
- self.set_content_type(context, constants.content_type.VIDEOS)
-
- result = []
-
- playlist_id = re_match.group('playlist_id')
- page_token = context.get_param('page_token', '')
-
- # no caching
- json_data = self.get_client(context).get_playlist_items(playlist_id=playlist_id, page_token=page_token)
- if not v3.handle_error(context, json_data):
- return False
- result.extend(v3.response_to_items(self, context, json_data))
-
- return result
-
"""
Lists the videos of a playlist.
path : '/channel/(?P[^/]+)/playlist/(?P[^/]+)/'
+ or
+ path : '/playlist/(?P[^/]+)/'
channel_id : ['mine'|]
playlist_id:
"""
- @RegisterProviderPath('^/channel/(?P[^/]+)/playlist/(?P[^/]+)/$')
- def _on_channel_playlist(self, context, re_match):
+ @RegisterProviderPath('^(?:/channel/(?P[^/]+))?/playlist/(?P[^/]+)/$')
+ def _on_playlist(self, context, re_match):
self.set_content_type(context, constants.content_type.VIDEOS)
client = self.get_client(context)
- result = []
+ resource_manager = self.get_resource_manager(context)
- playlist_id = re_match.group('playlist_id')
- page_token = context.get_param('page_token', '')
+ batch_id = (re_match.group('playlist_id'),
+ context.get_param('page_token') or 0)
- # no caching
- json_data = client.get_playlist_items(playlist_id=playlist_id, page_token=page_token)
- if not v3.handle_error(context, json_data):
+ json_data = resource_manager.get_playlist_items(batch_id=batch_id)
+ if not json_data:
return False
- result.extend(v3.response_to_items(self, context, json_data))
-
+ result = v3.response_to_items(self, context, json_data[batch_id])
return result
"""
From cb6b85a181944c1794fa1e15cdb6044b33c57a4d Mon Sep 17 00:00:00 2001
From: MoojMidge <56883549+MoojMidge@users.noreply.github.com>
Date: Fri, 15 Dec 2023 22:45:37 +1100
Subject: [PATCH 108/141] Remove JSON (de)serialisation for SQL storage
- Partially revert 764caec which added missing JSON (de)serialisation
- Data is already pickled and stored as a binary blob
- Add versioning via table name
- Bump to v2 to force cached data to be removed
- Old table(s) will be automatically removed
- Also remove row factory
---
.../kodion/sql_store/data_cache.py | 5 +-
.../kodion/sql_store/function_cache.py | 28 ++-
.../kodion/sql_store/playback_history.py | 29 +--
.../kodion/sql_store/storage.py | 167 +++++++++---------
.../kodion/sql_store/watch_later_list.py | 6 +-
.../lib/youtube_plugin/kodion/utils/player.py | 11 +-
.../lib/youtube_plugin/youtube/provider.py | 8 +-
7 files changed, 112 insertions(+), 142 deletions(-)
diff --git a/resources/lib/youtube_plugin/kodion/sql_store/data_cache.py b/resources/lib/youtube_plugin/kodion/sql_store/data_cache.py
index f6f7d08b3..defce41f6 100644
--- a/resources/lib/youtube_plugin/kodion/sql_store/data_cache.py
+++ b/resources/lib/youtube_plugin/kodion/sql_store/data_cache.py
@@ -10,7 +10,6 @@
from __future__ import absolute_import, division, unicode_literals
-import json
from datetime import datetime
from .storage import Storage
@@ -45,10 +44,10 @@ def get_item(self, content_id, seconds):
return None
current_time = datetime.now()
- if self.get_seconds_diff(query_result[1] or current_time) > seconds:
+ if self.get_seconds_diff(query_result[0] or current_time) > seconds:
return None
- return query_result[0]
+ return query_result[1]
def set_item(self, content_id, item):
self._set(content_id, item)
diff --git a/resources/lib/youtube_plugin/kodion/sql_store/function_cache.py b/resources/lib/youtube_plugin/kodion/sql_store/function_cache.py
index 91839a64b..fa492ce8e 100644
--- a/resources/lib/youtube_plugin/kodion/sql_store/function_cache.py
+++ b/resources/lib/youtube_plugin/kodion/sql_store/function_cache.py
@@ -19,7 +19,8 @@
class FunctionCache(Storage):
def __init__(self, filename, max_file_size_mb=5):
max_file_size_kb = max_file_size_mb * 1024
- super(FunctionCache, self).__init__(filename, max_file_size_kb=max_file_size_kb)
+ super(FunctionCache, self).__init__(filename,
+ max_file_size_kb=max_file_size_kb)
self._enabled = True
@@ -67,10 +68,9 @@ def get_cached_only(self, func, *args, **keywords):
# only return before cached data
data, cache_id = self._get_cached_data(partial_func)
- if data is not None:
- return data[0]
-
- return None
+ if data is None:
+ return None
+ return data[1]
def get(self, func, seconds, *args, **keywords):
"""
@@ -86,22 +86,14 @@ def get(self, func, seconds, *args, **keywords):
if not self._enabled:
return partial_func()
- cached_data = None
- cached_time = None
data, cache_id = self._get_cached_data(partial_func)
if data is not None:
- cached_data = data[0]
- cached_time = data[1]
-
- diff_seconds = 0
-
- if cached_time is not None:
- diff_seconds = self.get_seconds_diff(cached_time)
-
- if cached_data is None or diff_seconds > seconds:
- cached_data = partial_func()
- self._set(cache_id, cached_data)
+ cached_time, cached_data = data
+ if data is None or self.get_seconds_diff(cached_time) > seconds:
+ data = partial_func()
+ self._set(cache_id, data)
+ return data
return cached_data
def _optimize_item_count(self):
diff --git a/resources/lib/youtube_plugin/kodion/sql_store/playback_history.py b/resources/lib/youtube_plugin/kodion/sql_store/playback_history.py
index ff4763b2b..4a26265d0 100644
--- a/resources/lib/youtube_plugin/kodion/sql_store/playback_history.py
+++ b/resources/lib/youtube_plugin/kodion/sql_store/playback_history.py
@@ -19,23 +19,14 @@ def __init__(self, filename):
def is_empty(self):
return self._is_empty()
- @staticmethod
- def _process_item(item):
- return item.strip('"').split(',')
-
def get_items(self, keys):
- query_result = self._get_by_ids(keys, process=self._process_item)
+ query_result = self._get_by_ids(keys)
if not query_result:
return {}
result = {
- item[0]: {
- 'play_count': int(item[2][0]),
- 'total_time': float(item[2][1]),
- 'played_time': float(item[2][2]),
- 'played_percent': int(item[2][3]),
- 'last_played': str(item[1]),
- } for item in query_result
+ item[0]: dict(item[2], last_played=item[1])
+ for item in query_result
}
return result
@@ -44,14 +35,7 @@ def get_item(self, key):
if not query_result:
return {}
- values = query_result[0].split(',')
- result = {key: {
- 'play_count': int(values[0]),
- 'total_time': float(values[1]),
- 'played_time': float(values[2]),
- 'played_percent': int(values[3]),
- 'last_played': str(query_result[1]),
- }}
+ result = {key: dict(query_result[1], last_played=query_result[0])}
return result
def clear(self):
@@ -60,9 +44,8 @@ def clear(self):
def remove(self, video_id):
self._remove(video_id)
- def update(self, video_id, play_count, total_time, played_time, played_percent):
- item = ','.join([str(play_count), str(total_time), str(played_time), str(played_percent)])
- self._set(str(video_id), item)
+ def update(self, video_id, play_data):
+ self._set(video_id, play_data)
def _optimize_item_count(self):
pass
diff --git a/resources/lib/youtube_plugin/kodion/sql_store/storage.py b/resources/lib/youtube_plugin/kodion/sql_store/storage.py
index b41898429..833517aeb 100644
--- a/resources/lib/youtube_plugin/kodion/sql_store/storage.py
+++ b/resources/lib/youtube_plugin/kodion/sql_store/storage.py
@@ -10,7 +10,6 @@
from __future__ import absolute_import, division, unicode_literals
-import json
import os
import pickle
import sqlite3
@@ -29,23 +28,19 @@ class Storage(object):
ONE_WEEK = 7 * ONE_DAY
ONE_MONTH = 4 * ONE_WEEK
- _key = str('key')
- _time = str('time')
- _value = str('value')
- _timestamp = str('timestamp')
-
- _table_name = 'storage'
- _clear_query = 'DELETE FROM %s' % _table_name
- _create_table_query = 'CREATE TABLE IF NOT EXISTS %s (key TEXT PRIMARY KEY, time TIMESTAMP, value BLOB)' % _table_name
- _get_query = 'SELECT * FROM %s WHERE key = ?' % _table_name
- _get_by_query = 'SELECT * FROM %s WHERE key in ({0})' % _table_name
- _get_all_asc_query = 'SELECT * FROM %s ORDER BY time ASC LIMIT {0}' % _table_name
- _get_all_desc_query = 'SELECT * FROM %s ORDER BY time DESC LIMIT {0}' % _table_name
- _is_empty_query = 'SELECT EXISTS(SELECT 1 FROM %s LIMIT 1)' % _table_name
- _optimize_item_query = 'SELECT key FROM %s ORDER BY time DESC LIMIT -1 OFFSET {0}' % _table_name
- _remove_query = 'DELETE FROM %s WHERE key = ?' % _table_name
- _remove_all_query = 'DELETE FROM %s WHERE key in ({0})' % _table_name
- _set_query = 'REPLACE INTO %s (key, time, value) VALUES(?, ?, ?)' % _table_name
+ _table_name = 'storage_v2'
+ _clear_sql = 'DELETE FROM %s' % _table_name
+ _create_table_sql = 'CREATE TABLE IF NOT EXISTS %s (key TEXT PRIMARY KEY, time TIMESTAMP, value BLOB)' % _table_name
+ _drop_old_tables_sql = 'DELETE FROM sqlite_master WHERE type = "table" and name IS NOT "%s"' % _table_name
+ _get_sql = 'SELECT * FROM %s WHERE key = ?' % _table_name
+ _get_by_sql = 'SELECT * FROM %s WHERE key in ({0})' % _table_name
+ _get_all_asc_sql = 'SELECT * FROM %s ORDER BY time ASC LIMIT {0}' % _table_name
+ _get_all_desc_sql = 'SELECT * FROM %s ORDER BY time DESC LIMIT {0}' % _table_name
+ _is_empty_sql = 'SELECT EXISTS(SELECT 1 FROM %s LIMIT 1)' % _table_name
+ _optimize_item_sql = 'SELECT key FROM %s ORDER BY time DESC LIMIT -1 OFFSET {0}' % _table_name
+ _remove_sql = 'DELETE FROM %s WHERE key = ?' % _table_name
+ _remove_all_sql = 'DELETE FROM %s WHERE key in ({0})' % _table_name
+ _set_sql = 'REPLACE INTO %s (key, time, value) VALUES(?, ?, ?)' % _table_name
def __init__(self, filename, max_item_count=-1, max_file_size_kb=-1):
self._filename = filename
@@ -59,7 +54,7 @@ def __init__(self, filename, max_item_count=-1, max_file_size_kb=-1):
self._table_created = False
self._needs_commit = False
- sqlite3.register_converter(self._timestamp, self._convert_timestamp)
+ sqlite3.register_converter(str('timestamp'), self._convert_timestamp)
def set_max_item_count(self, max_item_count):
self._max_item_count = max_item_count
@@ -68,10 +63,19 @@ def set_max_file_size_kb(self, max_file_size_kb):
self._max_file_size_kb = max_file_size_kb
def __del__(self):
+ self._close(True)
+
+ def __enter__(self):
+ self._open()
+ return self
+
+ def __exit__(self, exc_type, exc_val, exc_tb):
self._close()
def _open(self):
if self._db:
+ if not self._cursor:
+ self._cursor = self._db.cursor()
return
self._optimize_file_size()
@@ -83,7 +87,6 @@ def _open(self):
db = sqlite3.connect(self._filename, check_same_thread=False,
detect_types=sqlite3.PARSE_DECLTYPES,
timeout=1, isolation_level=None)
- db.row_factory = sqlite3.Row
cursor = db.cursor()
# cursor.execute('PRAGMA journal_mode=MEMORY')
cursor.execute('PRAGMA journal_mode=WAL')
@@ -96,6 +99,14 @@ def _open(self):
self._db = db
self._cursor = cursor
self._create_table()
+ self._drop_old_tables()
+
+ def _drop_old_tables(self):
+ self._execute(True, 'PRAGMA writable_schema=1')
+ self._execute(True, self._drop_old_tables_sql)
+ self._execute(True, 'PRAGMA writable_schema=0')
+ self._sync()
+ self._execute(False, 'VACUUM')
def _execute(self, needs_commit, query, values=None, many=False):
if values is None:
@@ -120,12 +131,13 @@ def _execute(self, needs_commit, query, values=None, many=False):
time.sleep(0.1)
return []
- def _close(self):
- if self._db:
+ def _close(self, full=False):
+ if self._db and self._cursor:
self._sync()
self._db.commit()
self._cursor.close()
self._cursor = None
+ if full and self._db:
self._db.close()
self._db = None
@@ -152,7 +164,7 @@ def _optimize_file_size(self):
def _create_table(self):
if self._table_created:
return
- self._execute(True, self._create_table_query)
+ self._execute(True, self._create_table_sql)
self._table_created = True
def _sync(self):
@@ -164,22 +176,18 @@ def _sync(self):
def _set(self, item_id, item):
# add 1 microsecond, required for dbapi2
now = since_epoch(datetime.now()) + 0.000001
- self._open()
- self._execute(True, self._set_query, values=[str(item_id),
- now,
- self._encode(item)])
- self._close()
+ with self as db:
+ db._execute(True, db._set_sql,
+ values=[str(item_id), now, db._encode(item)])
self._optimize_item_count()
def _set_all(self, items):
# add 1 microsecond, required for dbapi2
now = since_epoch(datetime.now()) + 0.000001
- self._open()
- self._execute(True, self._set_query,
- values=[(str(key), now, self._encode(item))
- for key, item in items.items()],
- many=True)
- self._close()
+ with self as db:
+ db._execute(True, db._set_sql, many=True,
+ values=[(str(item_id), now, db._encode(item))
+ for item_id, item in items.items()])
self._optimize_item_count()
def _optimize_item_count(self):
@@ -189,32 +197,28 @@ def _optimize_item_count(self):
return
if self._max_item_count < 0:
return
- query = self._optimize_item_query.format(self._max_item_count)
- self._open()
- item_ids = self._execute(False, query)
- key = self._key
- item_ids = [item_id[key] for item_id in item_ids]
- if item_ids:
- self._remove_all(item_ids)
- self._close()
+ query = self._optimize_item_sql.format(self._max_item_count)
+ with self as db:
+ item_ids = db._execute(False, query)
+ item_ids = [item_id[0] for item_id in item_ids]
+ if item_ids:
+ db._remove_all(item_ids)
def _clear(self):
- self._open()
- self._execute(True, self._clear_query)
- self._create_table()
- self._sync()
- self._execute(False, 'VACUUM')
- self._close()
+ with self as db:
+ db._execute(True, db._clear_sql)
+ db._create_table()
+ db._sync()
+ db._execute(False, 'VACUUM')
def _is_empty(self):
- self._open()
- result = self._execute(False, self._is_empty_query)
- for item in result:
- is_empty = item[0] == 0
- break
- else:
- is_empty = True
- self._close()
+ with self as db:
+ result = db._execute(False, db._is_empty_sql)
+ for item in result:
+ is_empty = item[0] == 0
+ break
+ else:
+ is_empty = True
return is_empty
@staticmethod
@@ -222,58 +226,51 @@ def _decode(obj, process=None):
decoded_obj = pickle.loads(obj)
if process:
return process(decoded_obj)
- return json.loads(decoded_obj)
+ return decoded_obj
@staticmethod
def _encode(obj):
return sqlite3.Binary(pickle.dumps(
- json.dumps(obj, ensure_ascii=False),
- protocol=pickle.HIGHEST_PROTOCOL
+ obj, protocol=pickle.HIGHEST_PROTOCOL
))
- def _get(self, item_id):
- self._open()
- result = self._execute(False, self._get_query, [item_id])
- if result:
- result = result.fetchone()
- self._close()
- if result:
- return self._decode(result[self._value]), result[self._time]
- return None
+ def _get(self, item_id, process=None):
+ with self as db:
+ result = db._execute(False, db._get_sql, [item_id])
+ if result:
+ result = result.fetchone()
+ if not result:
+ return None
+ return result[1], self._decode(result[2], process)
def _get_by_ids(self, item_ids=None, oldest_first=True, limit=-1,
process=None):
if not item_ids:
if oldest_first:
- query = self._get_all_asc_query
+ query = self._get_all_asc_sql
else:
- query = self._get_all_desc_query
+ query = self._get_all_desc_sql
query = query.format(limit)
else:
num_ids = len(item_ids)
- query = self._get_by_query.format(('?,' * (num_ids - 1)) + '?')
+ query = self._get_by_sql.format(('?,' * (num_ids - 1)) + '?')
item_ids = tuple(item_ids)
- self._open()
- result = self._execute(False, query, item_ids)
- key = self._key
- time = self._time
- value = self._value
- result = [
- (item[key], item[time], self._decode(item[value], process))
- for item in result
- ]
- self._close()
+ with self as db:
+ result = db._execute(False, query, item_ids)
+ result = [
+ (item[0], item[1], db._decode(item[2], process))
+ for item in result
+ ]
return result
def _remove(self, item_id):
- self._open()
- self._execute(True, self._remove_query, [item_id])
+ with self as db:
+ db._execute(True, db._remove_sql, [item_id])
def _remove_all(self, item_ids):
num_ids = len(item_ids)
- query = self._remove_all_query.format(('?,' * (num_ids - 1)) + '?')
- self._open()
+ query = self._remove_all_sql.format(('?,' * (num_ids - 1)) + '?')
self._execute(True, query, tuple(item_ids))
@staticmethod
diff --git a/resources/lib/youtube_plugin/kodion/sql_store/watch_later_list.py b/resources/lib/youtube_plugin/kodion/sql_store/watch_later_list.py
index cb8e39743..658424a30 100644
--- a/resources/lib/youtube_plugin/kodion/sql_store/watch_later_list.py
+++ b/resources/lib/youtube_plugin/kodion/sql_store/watch_later_list.py
@@ -13,7 +13,7 @@
from datetime import datetime
from .storage import Storage
-from .. import items
+from ..items import to_json, from_json
class WatchLaterList(Storage):
@@ -28,13 +28,13 @@ def _sort_item(_item):
return _item[2].get_date()
def get_items(self):
- result = self._get_by_ids(process=items.from_json)
+ result = self._get_by_ids(process=from_json)
return sorted(result, key=self._sort_item, reverse=False)
def add(self, base_item):
base_item.set_date_from_datetime(datetime.now())
- item_json_data = items.to_json(base_item)
+ item_json_data = to_json(base_item)
self._set(base_item.get_id(), item_json_data)
def remove(self, base_item):
diff --git a/resources/lib/youtube_plugin/kodion/utils/player.py b/resources/lib/youtube_plugin/kodion/utils/player.py
index 8c3adc136..f68f8ec00 100644
--- a/resources/lib/youtube_plugin/kodion/utils/player.py
+++ b/resources/lib/youtube_plugin/kodion/utils/player.py
@@ -329,11 +329,14 @@ def run(self):
refresh_only = True
if use_local_history:
+ play_data = {
+ 'play_count': play_count,
+ 'total_time': self.total_time,
+ 'played_time': self.current_time,
+ 'played_percent': self.percent_complete,
+ }
self._context.get_playback_history().update(self.video_id,
- play_count,
- self.total_time,
- self.current_time,
- self.percent_complete)
+ play_data)
if not refresh_only and is_logged_in:
if settings.get_bool('youtube.playlist.watchlater.autoremove',
diff --git a/resources/lib/youtube_plugin/youtube/provider.py b/resources/lib/youtube_plugin/youtube/provider.py
index d5950382d..2c40c1d3f 100644
--- a/resources/lib/youtube_plugin/youtube/provider.py
+++ b/resources/lib/youtube_plugin/youtube/provider.py
@@ -473,7 +473,7 @@ def _on_channel(self, context, re_match):
upload_playlist,
page_token=page_token)
if not json_data:
- return False
+ return result
result.extend(v3.response_to_items(self, context, json_data))
@@ -1171,11 +1171,7 @@ def on_playback_history(self, context, re_match):
play_data['played_time'] = 0
play_data['played_percent'] = 0
- playback_history.update(video_id,
- play_data.get('play_count', 0),
- play_data.get('total_time', 0),
- play_data.get('played_time', 0),
- play_data.get('played_percent', 0))
+ playback_history.update(video_id, play_data)
context.get_ui().refresh_container()
return True
From d1cdfbe4df2cd29db48a267e4a561c059704f0fe Mon Sep 17 00:00:00 2001
From: MoojMidge <56883549+MoojMidge@users.noreply.github.com>
Date: Sat, 16 Dec 2023 07:00:44 +1100
Subject: [PATCH 109/141] Fix initial discard of subsequent pages of
playlistitems
---
.../youtube/helper/resource_manager.py | 41 +++++++++++--------
1 file changed, 25 insertions(+), 16 deletions(-)
diff --git a/resources/lib/youtube_plugin/youtube/helper/resource_manager.py b/resources/lib/youtube_plugin/youtube/helper/resource_manager.py
index 8e29b9ece..a685217ae 100644
--- a/resources/lib/youtube_plugin/youtube/helper/resource_manager.py
+++ b/resources/lib/youtube_plugin/youtube/helper/resource_manager.py
@@ -86,11 +86,11 @@ def get_channels(self, ids):
# Re-sort result to match order of requested IDs
# Will only work in Python v3.7+
- if list(result) != ids:
+ if list(result) != ids[:len(result)]:
result = {
- id: result[id]
- for id in ids
- if id in result
+ id_: result[id_]
+ for id_ in ids
+ if id_ in result
}
return result
@@ -118,6 +118,7 @@ def get_fanarts(self, channel_ids):
return result
def get_playlists(self, ids):
+ ids = tuple(ids)
result = self._data_cache.get_items(ids, self._data_cache.ONE_MONTH)
to_update = [id_ for id_ in ids if id_ not in result]
@@ -149,11 +150,11 @@ def get_playlists(self, ids):
# Re-sort result to match order of requested IDs
# Will only work in Python v3.7+
- if list(result) != ids:
+ if list(result) != ids[:len(result)]:
result = {
- id: result[id]
- for id in ids
- if id in result
+ id_: result[id_]
+ for id_ in ids
+ if id_ in result
}
return result
@@ -193,13 +194,20 @@ def get_playlist_items(self, ids=None, batch_id=None):
.format(ids=list(result)))
new_data = {}
+ insert_point = 0
for playlist_id, page_token in to_update:
+ new_batch_ids = []
+ batch_id = (playlist_id, page_token)
+ insert_point = batch_ids.index(batch_id, insert_point)
while 1:
batch_id = (playlist_id, page_token)
+ new_batch_ids.append(batch_id)
batch = self._client.get_playlist_items(*batch_id)
new_data[batch_id] = batch
page_token = batch.get('nextPageToken') if fetch_next else None
if page_token is None:
+ batch_ids[insert_point:insert_point] = new_batch_ids
+ insert_point += len(new_batch_ids)
break
if new_data:
@@ -213,11 +221,11 @@ def get_playlist_items(self, ids=None, batch_id=None):
# Re-sort result to match order of requested IDs
# Will only work in Python v3.7+
- if list(result) != batch_ids:
+ if list(result) != batch_ids[:len(result)]:
result = {
- id: result[id]
- for id in batch_ids
- if id in result
+ id_: result[id_]
+ for id_ in batch_ids
+ if id_ in result
}
return result
@@ -240,6 +248,7 @@ def get_related_playlists(self, channel_id):
return item.get('contentDetails', {}).get('relatedPlaylists', {})
def get_videos(self, ids, live_details=False, suppress_errors=False):
+ ids = tuple(ids)
result = self._data_cache.get_items(ids, self._data_cache.ONE_MONTH)
to_update = [id_ for id_ in ids if id_ not in result]
@@ -274,11 +283,11 @@ def get_videos(self, ids, live_details=False, suppress_errors=False):
# Re-sort result to match order of requested IDs
# Will only work in Python v3.7+
- if list(result) != ids:
+ if list(result) != ids[:len(result)]:
result = {
- id: result[id]
- for id in ids
- if id in result
+ id_: result[id_]
+ for id_ in ids
+ if id_ in result
}
if self._context.get_settings().use_local_history():
From 0c499af608cdcd5ba295f03bcef55a8002a7f9d8 Mon Sep 17 00:00:00 2001
From: MoojMidge <56883549+MoojMidge@users.noreply.github.com>
Date: Sun, 17 Dec 2023 00:21:14 +1100
Subject: [PATCH 110/141] Minor optimisations for SQL storage
- Remove unnecessary type conversions
- Remove unnecessary sorting
- Remove unnecessary iteration
---
.../lib/youtube_plugin/kodion/items/utils.py | 2 +-
.../kodion/sql_store/data_cache.py | 23 +---
.../kodion/sql_store/favorite_list.py | 6 +-
.../kodion/sql_store/function_cache.py | 20 ++--
.../kodion/sql_store/playback_history.py | 24 ++--
.../kodion/sql_store/search_history.py | 5 +-
.../kodion/sql_store/storage.py | 109 ++++++------------
.../kodion/sql_store/watch_later_list.py | 8 +-
8 files changed, 63 insertions(+), 134 deletions(-)
diff --git a/resources/lib/youtube_plugin/kodion/items/utils.py b/resources/lib/youtube_plugin/kodion/items/utils.py
index 64e0966ba..a5a5971a1 100644
--- a/resources/lib/youtube_plugin/kodion/items/utils.py
+++ b/resources/lib/youtube_plugin/kodion/items/utils.py
@@ -26,7 +26,7 @@
}
-def from_json(json_data):
+def from_json(json_data, *_args):
"""
Creates a instance of the given json dump or dict.
:param json_data:
diff --git a/resources/lib/youtube_plugin/kodion/sql_store/data_cache.py b/resources/lib/youtube_plugin/kodion/sql_store/data_cache.py
index defce41f6..e3a5a538a 100644
--- a/resources/lib/youtube_plugin/kodion/sql_store/data_cache.py
+++ b/resources/lib/youtube_plugin/kodion/sql_store/data_cache.py
@@ -25,29 +25,12 @@ def is_empty(self):
return self._is_empty()
def get_items(self, content_ids, seconds):
- query_result = self._get_by_ids(content_ids)
- if not query_result:
- return {}
-
- current_time = datetime.now()
- result = {
- item[0]: item[2]
- for item in query_result
- if self.get_seconds_diff(item[1] or current_time) <= seconds
- }
+ result = self._get_by_ids(content_ids, seconds=seconds, as_dict=True)
return result
def get_item(self, content_id, seconds):
- content_id = str(content_id)
- query_result = self._get(content_id)
- if not query_result:
- return None
-
- current_time = datetime.now()
- if self.get_seconds_diff(query_result[0] or current_time) > seconds:
- return None
-
- return query_result[1]
+ result = self._get(content_id, seconds=seconds)
+ return result
def set_item(self, content_id, item):
self._set(content_id, item)
diff --git a/resources/lib/youtube_plugin/kodion/sql_store/favorite_list.py b/resources/lib/youtube_plugin/kodion/sql_store/favorite_list.py
index 788ba1ff4..119cd95dd 100644
--- a/resources/lib/youtube_plugin/kodion/sql_store/favorite_list.py
+++ b/resources/lib/youtube_plugin/kodion/sql_store/favorite_list.py
@@ -22,11 +22,11 @@ def clear(self):
self._clear()
@staticmethod
- def _sort_item(_item):
- return _item[2].get_name().upper()
+ def _sort_item(item):
+ return item.get_name().upper()
def get_items(self):
- result = self._get_by_ids(process=from_json)
+ result = self._get_by_ids(process=from_json, values_only=True)
return sorted(result, key=self._sort_item, reverse=False)
def add(self, base_item):
diff --git a/resources/lib/youtube_plugin/kodion/sql_store/function_cache.py b/resources/lib/youtube_plugin/kodion/sql_store/function_cache.py
index fa492ce8e..e244ba940 100644
--- a/resources/lib/youtube_plugin/kodion/sql_store/function_cache.py
+++ b/resources/lib/youtube_plugin/kodion/sql_store/function_cache.py
@@ -55,9 +55,9 @@ def _create_id_from_func(partial_func):
md5_hash.update(str(partial_func.keywords).encode('utf-8'))
return md5_hash.hexdigest()
- def _get_cached_data(self, partial_func):
+ def _get_cached_data(self, partial_func, seconds=None):
cache_id = self._create_id_from_func(partial_func)
- return self._get(cache_id), cache_id
+ return self._get(cache_id, seconds=seconds), cache_id
def get_cached_only(self, func, *args, **keywords):
partial_func = partial(func, *args, **keywords)
@@ -67,10 +67,8 @@ def get_cached_only(self, func, *args, **keywords):
return partial_func()
# only return before cached data
- data, cache_id = self._get_cached_data(partial_func)
- if data is None:
- return None
- return data[1]
+ data, _ = self._get_cached_data(partial_func)
+ return data
def get(self, func, seconds, *args, **keywords):
"""
@@ -86,15 +84,11 @@ def get(self, func, seconds, *args, **keywords):
if not self._enabled:
return partial_func()
- data, cache_id = self._get_cached_data(partial_func)
- if data is not None:
- cached_time, cached_data = data
-
- if data is None or self.get_seconds_diff(cached_time) > seconds:
+ data, cache_id = self._get_cached_data(partial_func, seconds=seconds)
+ if data is None:
data = partial_func()
self._set(cache_id, data)
- return data
- return cached_data
+ return data
def _optimize_item_count(self):
# override method Storage._optimize_item_count
diff --git a/resources/lib/youtube_plugin/kodion/sql_store/playback_history.py b/resources/lib/youtube_plugin/kodion/sql_store/playback_history.py
index 4a26265d0..a0eae31e4 100644
--- a/resources/lib/youtube_plugin/kodion/sql_store/playback_history.py
+++ b/resources/lib/youtube_plugin/kodion/sql_store/playback_history.py
@@ -19,23 +19,19 @@ def __init__(self, filename):
def is_empty(self):
return self._is_empty()
- def get_items(self, keys):
- query_result = self._get_by_ids(keys)
- if not query_result:
- return {}
-
- result = {
- item[0]: dict(item[2], last_played=item[1])
- for item in query_result
- }
+ def _add_last_played(self, value, item):
+ value['last_played'] = self._convert_timestamp(item[1])
+ return value
+
+ def get_items(self, keys=None):
+ result = self._get_by_ids(keys,
+ oldest_first=False,
+ process=self._add_last_played,
+ as_dict=True)
return result
def get_item(self, key):
- query_result = self._get(key)
- if not query_result:
- return {}
-
- result = {key: dict(query_result[1], last_played=query_result[0])}
+ result = self._get(key, process=self._add_last_played)
return result
def clear(self):
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 bf83574ac..a0f7046ea 100644
--- a/resources/lib/youtube_plugin/kodion/sql_store/search_history.py
+++ b/resources/lib/youtube_plugin/kodion/sql_store/search_history.py
@@ -25,8 +25,9 @@ def is_empty(self):
def get_items(self):
result = self._get_by_ids(oldest_first=False,
- limit=self._max_item_count)
- return [item[2] for item in result]
+ limit=self._max_item_count,
+ values_only=True)
+ return result
def clear(self):
self._clear()
diff --git a/resources/lib/youtube_plugin/kodion/sql_store/storage.py b/resources/lib/youtube_plugin/kodion/sql_store/storage.py
index 833517aeb..c360bb219 100644
--- a/resources/lib/youtube_plugin/kodion/sql_store/storage.py
+++ b/resources/lib/youtube_plugin/kodion/sql_store/storage.py
@@ -30,7 +30,7 @@ class Storage(object):
_table_name = 'storage_v2'
_clear_sql = 'DELETE FROM %s' % _table_name
- _create_table_sql = 'CREATE TABLE IF NOT EXISTS %s (key TEXT PRIMARY KEY, time TIMESTAMP, value BLOB)' % _table_name
+ _create_table_sql = 'CREATE TABLE IF NOT EXISTS %s (key TEXT PRIMARY KEY, time REAL, value BLOB)' % _table_name
_drop_old_tables_sql = 'DELETE FROM sqlite_master WHERE type = "table" and name IS NOT "%s"' % _table_name
_get_sql = 'SELECT * FROM %s WHERE key = ?' % _table_name
_get_by_sql = 'SELECT * FROM %s WHERE key in ({0})' % _table_name
@@ -54,8 +54,6 @@ def __init__(self, filename, max_item_count=-1, max_file_size_kb=-1):
self._table_created = False
self._needs_commit = False
- sqlite3.register_converter(str('timestamp'), self._convert_timestamp)
-
def set_max_item_count(self, max_item_count):
self._max_item_count = max_item_count
@@ -174,16 +172,14 @@ def _sync(self):
return self._execute(False, 'COMMIT')
def _set(self, item_id, item):
- # add 1 microsecond, required for dbapi2
- now = since_epoch(datetime.now()) + 0.000001
+ now = since_epoch(datetime.now())
with self as db:
db._execute(True, db._set_sql,
values=[str(item_id), now, db._encode(item)])
self._optimize_item_count()
def _set_all(self, items):
- # add 1 microsecond, required for dbapi2
- now = since_epoch(datetime.now()) + 0.000001
+ now = since_epoch(datetime.now())
with self as db:
db._execute(True, db._set_sql, many=True,
values=[(str(item_id), now, db._encode(item))
@@ -222,10 +218,10 @@ def _is_empty(self):
return is_empty
@staticmethod
- def _decode(obj, process=None):
+ def _decode(obj, process=None, item=None):
decoded_obj = pickle.loads(obj)
if process:
- return process(decoded_obj)
+ return process(decoded_obj, item)
return decoded_obj
@staticmethod
@@ -234,17 +230,20 @@ def _encode(obj):
obj, protocol=pickle.HIGHEST_PROTOCOL
))
- def _get(self, item_id, process=None):
+ def _get(self, item_id, process=None, seconds=None):
with self as db:
- result = db._execute(False, db._get_sql, [item_id])
- if result:
- result = result.fetchone()
- if not result:
+ result = db._execute(False, db._get_sql, [str(item_id)])
+ item = result.fetchone() if result else None
+ if not item:
return None
- return result[1], self._decode(result[2], process)
+ cut_off = since_epoch(datetime.now()) - seconds if seconds else 0
+ if not cut_off or item[1] >= cut_off:
+ return self._decode(item[2], process, item)
+ return None
def _get_by_ids(self, item_ids=None, oldest_first=True, limit=-1,
- process=None):
+ seconds=None, process=None,
+ as_dict=False, values_only=False):
if not item_ids:
if oldest_first:
query = self._get_all_asc_sql
@@ -258,10 +257,24 @@ def _get_by_ids(self, item_ids=None, oldest_first=True, limit=-1,
with self as db:
result = db._execute(False, query, item_ids)
- result = [
- (item[0], item[1], db._decode(item[2], process))
- for item in result
- ]
+ cut_off = since_epoch(datetime.now()) - seconds if seconds else 0
+ if as_dict:
+ result = {
+ item[0]: db._decode(item[2], process, item)
+ for item in result if not cut_off or item[1] >= cut_off
+ }
+ elif values_only:
+ result = [
+ db._decode(item[2], process, item)
+ for item in result if not cut_off or item[1] >= cut_off
+ ]
+ else:
+ result = [
+ (item[0],
+ self._convert_timestamp(item[1]),
+ db._decode(item[2], process, item))
+ for item in result if not cut_off or item[1] >= cut_off
+ ]
return result
def _remove(self, item_id):
@@ -273,60 +286,6 @@ def _remove_all(self, item_ids):
query = self._remove_all_sql.format(('?,' * (num_ids - 1)) + '?')
self._execute(True, query, tuple(item_ids))
- @staticmethod
- def strptime(stamp, stamp_fmt):
- # noinspection PyUnresolvedReferences
- import _strptime
- try:
- time.strptime('01 01 2012', '%d %m %Y') # dummy call
- except:
- pass
- return time.strptime(stamp, stamp_fmt)
-
@classmethod
def _convert_timestamp(cls, val):
- val = val.decode('utf-8')
- if '-' in val or ':' in val:
- return cls._parse_datetime_string(val)
- return datetime.fromtimestamp(float(val))
-
- @classmethod
- def _parse_datetime_string(cls, current_stamp):
- for stamp_format in ['%Y-%m-%d %H:%M:%S.%f', '%Y-%m-%d %H:%M:%S']:
- try:
- stamp_datetime = datetime(
- *(cls.strptime(current_stamp, stamp_format)[0:6])
- )
- break
- except ValueError: # current_stamp has no microseconds
- continue
- except TypeError:
- log_error('Exception while parsing timestamp:\n'
- 'current_stamp |{cs}|{cst}|\n'
- 'stamp_format |{sf}|{sft}|\n{tb}'
- .format(cs=current_stamp,
- cst=type(current_stamp),
- sf=stamp_format,
- sft=type(stamp_format),
- tb=print_exc()))
- else:
- return None
- return stamp_datetime
-
- def get_seconds_diff(self, current_stamp):
- if not current_stamp:
- return 86400 # 24 hrs
-
- current_datetime = datetime.now()
- if isinstance(current_stamp, datetime):
- time_delta = current_datetime - current_stamp
- return time_delta.total_seconds()
-
- if isinstance(current_stamp, (float, int)):
- return since_epoch(current_datetime) - current_stamp
-
- stamp_datetime = self._parse_datetime_string(current_stamp)
- if not stamp_datetime:
- return 604800 # one week
- time_delta = current_datetime - stamp_datetime
- return time_delta.total_seconds()
+ return datetime.fromtimestamp(val)
diff --git a/resources/lib/youtube_plugin/kodion/sql_store/watch_later_list.py b/resources/lib/youtube_plugin/kodion/sql_store/watch_later_list.py
index 658424a30..70a1b7f9e 100644
--- a/resources/lib/youtube_plugin/kodion/sql_store/watch_later_list.py
+++ b/resources/lib/youtube_plugin/kodion/sql_store/watch_later_list.py
@@ -23,13 +23,9 @@ def __init__(self, filename):
def clear(self):
self._clear()
- @staticmethod
- def _sort_item(_item):
- return _item[2].get_date()
-
def get_items(self):
- result = self._get_by_ids(process=from_json)
- return sorted(result, key=self._sort_item, reverse=False)
+ result = self._get_by_ids(process=from_json, values_only=True)
+ return result
def add(self, base_item):
base_item.set_date_from_datetime(datetime.now())
From 14099bd9f2d10f81108822721e59b331edd6b26b Mon Sep 17 00:00:00 2001
From: MoojMidge <56883549+MoojMidge@users.noreply.github.com>
Date: Sun, 17 Dec 2023 04:05:06 +1100
Subject: [PATCH 111/141] Update type/attr/key checking
- Allow passing tuples and dict.keys instead of only lists as parameters
- Other misc fixes to prevent possible exceptions
---
.../youtube_plugin/kodion/abstract_provider.py | 9 +++++----
.../youtube_plugin/kodion/items/video_item.py | 3 ++-
.../kodion/plugin/xbmc/xbmc_runner.py | 2 +-
.../lib/youtube_plugin/kodion/utils/methods.py | 4 ++--
.../youtube/client/request_client.py | 2 +-
.../lib/youtube_plugin/youtube/client/youtube.py | 16 ++++++++--------
.../youtube_plugin/youtube/helper/subtitles.py | 2 +-
resources/lib/youtube_requests.py | 4 ++--
8 files changed, 22 insertions(+), 20 deletions(-)
diff --git a/resources/lib/youtube_plugin/kodion/abstract_provider.py b/resources/lib/youtube_plugin/kodion/abstract_provider.py
index a35a8bad5..4f543e10d 100644
--- a/resources/lib/youtube_plugin/kodion/abstract_provider.py
+++ b/resources/lib/youtube_plugin/kodion/abstract_provider.py
@@ -52,9 +52,10 @@ def __init__(self):
"""
for method_name in dir(self):
- method = getattr(self, method_name)
- if hasattr(method, 'kodion_re_path'):
- self.register_path(method.kodion_re_path, method_name)
+ method = getattr(self, method_name, None)
+ path = method and getattr(method, 'kodion_re_path', None)
+ if path:
+ self.register_path(path, method_name)
def get_alternative_fanart(self, context):
return context.get_fanart()
@@ -96,7 +97,7 @@ def navigate(self, context):
re_match = re.search(key, path, re.UNICODE)
if re_match is not None:
method_name = self._dict_path.get(key, '')
- method = getattr(self, method_name)
+ method = getattr(self, method_name, None)
if method is not None:
result = method(context, re_match)
if not isinstance(result, tuple):
diff --git a/resources/lib/youtube_plugin/kodion/items/video_item.py b/resources/lib/youtube_plugin/kodion/items/video_item.py
index 2e40a3f6a..87b3483c1 100644
--- a/resources/lib/youtube_plugin/kodion/items/video_item.py
+++ b/resources/lib/youtube_plugin/kodion/items/video_item.py
@@ -263,7 +263,8 @@ def get_mediatype(self):
return self._mediatype
def set_subtitles(self, value):
- self.subtitles = value if value and isinstance(value, list) else None
+ if value and isinstance(value, (list, tuple)):
+ self.subtitles = value
def set_headers(self, value):
self._headers = value
diff --git a/resources/lib/youtube_plugin/kodion/plugin/xbmc/xbmc_runner.py b/resources/lib/youtube_plugin/kodion/plugin/xbmc/xbmc_runner.py
index 131451578..d23e8e48f 100644
--- a/resources/lib/youtube_plugin/kodion/plugin/xbmc/xbmc_runner.py
+++ b/resources/lib/youtube_plugin/kodion/plugin/xbmc/xbmc_runner.py
@@ -71,7 +71,7 @@ def run(self, provider, context):
if isinstance(result, DirectoryItem):
item_count = 1
items = [self._add_directory(result, show_fanart)]
- elif isinstance(result, list):
+ elif isinstance(result, (list, tuple)):
item_count = len(result)
items = [
self._add_directory(item, show_fanart) if isinstance(item, DirectoryItem)
diff --git a/resources/lib/youtube_plugin/kodion/utils/methods.py b/resources/lib/youtube_plugin/kodion/utils/methods.py
index de4d03f9f..2dbfc8ced 100644
--- a/resources/lib/youtube_plugin/kodion/utils/methods.py
+++ b/resources/lib/youtube_plugin/kodion/utils/methods.py
@@ -178,7 +178,7 @@ def _find_best_fit_video(_stream_data):
def create_path(*args):
comps = []
for arg in args:
- if isinstance(arg, list):
+ if isinstance(arg, (list, tuple)):
return create_path(*arg)
comps.append(str(arg.strip('/').replace('\\', '/').replace('//', '/')))
@@ -193,7 +193,7 @@ def create_path(*args):
def create_uri_path(*args):
comps = []
for arg in args:
- if isinstance(arg, list):
+ if isinstance(arg, (list, tuple)):
return create_uri_path(*arg)
comps.append(str(arg.strip('/').replace('\\', '/').replace('//', '/')))
diff --git a/resources/lib/youtube_plugin/youtube/client/request_client.py b/resources/lib/youtube_plugin/youtube/client/request_client.py
index 42f438214..045e35006 100644
--- a/resources/lib/youtube_plugin/youtube/client/request_client.py
+++ b/resources/lib/youtube_plugin/youtube/client/request_client.py
@@ -271,7 +271,7 @@ def json_traverse(json_data, path):
result = json_data
for keys in path:
is_dict = isinstance(result, dict)
- if not is_dict and not isinstance(result, list):
+ if not is_dict and not isinstance(result, (list, tuple)):
return None
if not isinstance(keys, (list, tuple)):
diff --git a/resources/lib/youtube_plugin/youtube/client/youtube.py b/resources/lib/youtube_plugin/youtube/client/youtube.py
index 741b56d52..e484bcf54 100644
--- a/resources/lib/youtube_plugin/youtube/client/youtube.py
+++ b/resources/lib/youtube_plugin/youtube/client/youtube.py
@@ -213,7 +213,7 @@ def create_playlist(self, title, privacy_status='private', **kwargs):
**kwargs)
def get_video_rating(self, video_id, **kwargs):
- if isinstance(video_id, list):
+ if not isinstance(video_id, str):
video_id = ','.join(video_id)
params = {'id': video_id}
@@ -622,7 +622,7 @@ def get_channels(self, channel_id, **kwargs):
:param channel_id: list or comma-separated list of the YouTube channel ID(s)
:return:
"""
- if isinstance(channel_id, list):
+ if not isinstance(channel_id, str):
channel_id = ','.join(channel_id)
params = {'part': 'snippet,contentDetails,brandingSettings'}
@@ -659,14 +659,14 @@ def get_videos(self, video_id, live_details=False, **kwargs):
:param live_details: also retrieve liveStreamingDetails
:return:
"""
- if isinstance(video_id, list):
+ if not isinstance(video_id, str):
video_id = ','.join(video_id)
- parts = ['snippet,contentDetails,status,statistics']
+ parts = ['snippet', 'contentDetails', 'status', 'statistics']
if live_details:
- parts.append(',liveStreamingDetails')
+ parts.append('liveStreamingDetails')
- params = {'part': ''.join(parts),
+ params = {'part': ','.join(parts),
'id': video_id}
return self.perform_v3_request(method='GET',
path='videos',
@@ -674,7 +674,7 @@ def get_videos(self, video_id, live_details=False, **kwargs):
**kwargs)
def get_playlists(self, playlist_id, **kwargs):
- if isinstance(playlist_id, list):
+ if not isinstance(playlist_id, str):
playlist_id = ','.join(playlist_id)
params = {'part': 'snippet,contentDetails',
@@ -850,7 +850,7 @@ def search(self,
# prepare search type
if not search_type:
search_type = ''
- if isinstance(search_type, list):
+ if not isinstance(search_type, str):
search_type = ','.join(search_type)
# prepare page token
diff --git a/resources/lib/youtube_plugin/youtube/helper/subtitles.py b/resources/lib/youtube_plugin/youtube/helper/subtitles.py
index 485d538e5..94a35d90f 100644
--- a/resources/lib/youtube_plugin/youtube/helper/subtitles.py
+++ b/resources/lib/youtube_plugin/youtube/helper/subtitles.py
@@ -284,7 +284,7 @@ def _get_language_name(track):
lang_name = track.get(key, {}).get('simpleText')
if not lang_name:
track_name = track.get(key, {}).get('runs', [{}])
- if isinstance(track_name, list) and len(track_name) >= 1:
+ if isinstance(track_name, (list, tuple)) and len(track_name) >= 1:
lang_name = track_name[0].get('text')
if lang_name:
diff --git a/resources/lib/youtube_requests.py b/resources/lib/youtube_requests.py
index 391018b8f..81b532f88 100644
--- a/resources/lib/youtube_requests.py
+++ b/resources/lib/youtube_requests.py
@@ -53,7 +53,7 @@ def v3_request(method='GET', headers=None, path=None, post_data=None, params=Non
def _append_missing_page_token(items):
- if items and isinstance(items, list) and (items[-1].get('nextPageToken') is None):
+ if items and isinstance(items, list) and 'nextPageToken' not in items[-1]:
items.append({'nextPageToken': ''})
return items
@@ -481,7 +481,7 @@ def get_live(channel_id=None, user=None, url=None, addon_id=None):
if matched_type == 'user':
items = get_channel_id(matched_id, addon_id=addon_id)
- if not items or not isinstance(items, list):
+ if not items or not isinstance(items, list) or 'id' not in items[0]:
return None
matched_id = items[0]['id']
From 5c48572d88e7d648941234fed06620eb2a86066f Mon Sep 17 00:00:00 2001
From: MoojMidge <56883549+MoojMidge@users.noreply.github.com>
Date: Mon, 18 Dec 2023 00:13:36 +1100
Subject: [PATCH 112/141] Remove old data on optimise SQL storage by size
- Also add logging for errors
- Bump storage table version
---
.../kodion/sql_store/data_cache.py | 2 -
.../kodion/sql_store/storage.py | 76 +++++++++++--------
2 files changed, 44 insertions(+), 34 deletions(-)
diff --git a/resources/lib/youtube_plugin/kodion/sql_store/data_cache.py b/resources/lib/youtube_plugin/kodion/sql_store/data_cache.py
index e3a5a538a..06539b514 100644
--- a/resources/lib/youtube_plugin/kodion/sql_store/data_cache.py
+++ b/resources/lib/youtube_plugin/kodion/sql_store/data_cache.py
@@ -10,8 +10,6 @@
from __future__ import absolute_import, division, unicode_literals
-from datetime import datetime
-
from .storage import Storage
diff --git a/resources/lib/youtube_plugin/kodion/sql_store/storage.py b/resources/lib/youtube_plugin/kodion/sql_store/storage.py
index c360bb219..23f4b347a 100644
--- a/resources/lib/youtube_plugin/kodion/sql_store/storage.py
+++ b/resources/lib/youtube_plugin/kodion/sql_store/storage.py
@@ -28,19 +28,20 @@ class Storage(object):
ONE_WEEK = 7 * ONE_DAY
ONE_MONTH = 4 * ONE_WEEK
- _table_name = 'storage_v2'
- _clear_sql = 'DELETE FROM %s' % _table_name
- _create_table_sql = 'CREATE TABLE IF NOT EXISTS %s (key TEXT PRIMARY KEY, time REAL, value BLOB)' % _table_name
- _drop_old_tables_sql = 'DELETE FROM sqlite_master WHERE type = "table" and name IS NOT "%s"' % _table_name
- _get_sql = 'SELECT * FROM %s WHERE key = ?' % _table_name
- _get_by_sql = 'SELECT * FROM %s WHERE key in ({0})' % _table_name
- _get_all_asc_sql = 'SELECT * FROM %s ORDER BY time ASC LIMIT {0}' % _table_name
- _get_all_desc_sql = 'SELECT * FROM %s ORDER BY time DESC LIMIT {0}' % _table_name
- _is_empty_sql = 'SELECT EXISTS(SELECT 1 FROM %s LIMIT 1)' % _table_name
- _optimize_item_sql = 'SELECT key FROM %s ORDER BY time DESC LIMIT -1 OFFSET {0}' % _table_name
- _remove_sql = 'DELETE FROM %s WHERE key = ?' % _table_name
- _remove_all_sql = 'DELETE FROM %s WHERE key in ({0})' % _table_name
- _set_sql = 'REPLACE INTO %s (key, time, value) VALUES(?, ?, ?)' % _table_name
+ _table_name = 'storage_v3'
+ _clear_sql = 'DELETE FROM {table}'.format(table=_table_name)
+ _create_table_sql = 'CREATE TABLE IF NOT EXISTS {table} (key TEXT PRIMARY KEY, time REAL, value BLOB, size INTEGER)'.format(table=_table_name)
+ _drop_old_tables_sql = 'DELETE FROM sqlite_master WHERE type = "table" and name IS NOT "{table}"'.format(table=_table_name)
+ _get_sql = 'SELECT * FROM {table} WHERE key = ?'.format(table=_table_name)
+ _get_by_sql = 'SELECT * FROM {table} WHERE key in ({{0}})'.format(table=_table_name)
+ _get_all_asc_sql = 'SELECT * FROM {table} ORDER BY time ASC LIMIT {{0}}'.format(table=_table_name)
+ _get_all_desc_sql = 'SELECT * FROM {table} ORDER BY time DESC LIMIT {{0}}'.format(table=_table_name)
+ _is_empty_sql = 'SELECT EXISTS(SELECT 1 FROM {table} LIMIT 1)'.format(table=_table_name)
+ _optimize_item_sql = 'SELECT key FROM {table} ORDER BY time DESC LIMIT -1 OFFSET {{0}}'.format(table=_table_name)
+ _prune_sql = 'DELETE FROM {table} WHERE ROWID IN (SELECT ROWID FROM {table} WHERE (SELECT SUM(size) FROM {table} AS _ WHERE time<={table}.time) <= {{0}})'.format(table=_table_name)
+ _remove_sql = 'DELETE FROM {table} WHERE key = ?'.format(table=_table_name)
+ _remove_all_sql = 'DELETE FROM {table} WHERE key in ({{0}})'.format(table=_table_name)
+ _set_sql = 'REPLACE INTO {table} (key, time, value, size) VALUES(?, ?, ?, ?)'.format(table=_table_name)
def __init__(self, filename, max_item_count=-1, max_file_size_kb=-1):
self._filename = filename
@@ -52,6 +53,7 @@ def __init__(self, filename, max_item_count=-1, max_file_size_kb=-1):
self._max_file_size_kb = max_file_size_kb
self._table_created = False
+ self._table_updated = False
self._needs_commit = False
def set_max_item_count(self, max_item_count):
@@ -76,14 +78,11 @@ def _open(self):
self._cursor = self._db.cursor()
return
- self._optimize_file_size()
-
path = os.path.dirname(self._filename)
if not os.path.exists(path):
os.makedirs(path)
db = sqlite3.connect(self._filename, check_same_thread=False,
- detect_types=sqlite3.PARSE_DECLTYPES,
timeout=1, isolation_level=None)
cursor = db.cursor()
# cursor.execute('PRAGMA journal_mode=MEMORY')
@@ -91,20 +90,26 @@ def _open(self):
cursor.execute('PRAGMA busy_timeout=20000')
cursor.execute('PRAGMA read_uncommitted=TRUE')
cursor.execute('PRAGMA temp_store=MEMORY')
+ cursor.execute('PRAGMA page_size=4096')
# cursor.execute('PRAGMA synchronous=OFF')
cursor.execute('PRAGMA synchronous=NORMAL')
- cursor.arraysize = 100
+ cursor.arraysize = 50
self._db = db
self._cursor = cursor
+
self._create_table()
self._drop_old_tables()
+ self._optimize_file_size()
def _drop_old_tables(self):
+ if self._table_updated:
+ return
self._execute(True, 'PRAGMA writable_schema=1')
self._execute(True, self._drop_old_tables_sql)
self._execute(True, 'PRAGMA writable_schema=0')
self._sync()
self._execute(False, 'VACUUM')
+ self._table_updated = True
def _execute(self, needs_commit, query, values=None, many=False):
if values is None:
@@ -124,18 +129,23 @@ def _execute(self, needs_commit, query, values=None, many=False):
return self._cursor.executemany(query, values)
return self._cursor.execute(query, values)
except TypeError:
+ log_error('SQLStorage._execute - |{0}|'.format(print_exc()))
return []
except:
+ log_error('SQLStorage._execute - |{0}|'.format(print_exc()))
time.sleep(0.1)
return []
def _close(self, full=False):
- if self._db and self._cursor:
+ if not self._db:
+ return
+
+ if self._cursor:
self._sync()
self._db.commit()
self._cursor.close()
self._cursor = None
- if full and self._db:
+ if full:
self._db.close()
self._db = None
@@ -144,20 +154,19 @@ def _optimize_file_size(self):
if self._max_file_size_kb <= 0:
return
- # do nothing - only if this folder exists
- path = os.path.dirname(self._filename)
- if not os.path.exists(path):
+ # do nothing - only if this db exists
+ if not os.path.exists(self._filename):
return
- if not os.path.exists(self._filename):
+ file_size_kb = (os.path.getsize(self._filename) // 1024)
+ if file_size_kb <= self._max_file_size_kb:
return
- try:
- file_size_kb = (os.path.getsize(self._filename) // 1024)
- if file_size_kb >= self._max_file_size_kb:
- os.remove(self._filename)
- except OSError:
- pass
+ prune_size = 1024 * int(file_size_kb - self._max_file_size_kb / 2)
+ query = self._prune_sql.format(prune_size)
+ self._execute(True, query)
+ self._sync()
+ self._execute(False, 'VACUUM')
def _create_table(self):
if self._table_created:
@@ -175,14 +184,14 @@ def _set(self, item_id, item):
now = since_epoch(datetime.now())
with self as db:
db._execute(True, db._set_sql,
- values=[str(item_id), now, db._encode(item)])
+ values=[str(item_id), now, *db._encode(item)])
self._optimize_item_count()
def _set_all(self, items):
now = since_epoch(datetime.now())
with self as db:
db._execute(True, db._set_sql, many=True,
- values=[(str(item_id), now, db._encode(item))
+ values=[(str(item_id), now, *db._encode(item))
for item_id, item in items.items()])
self._optimize_item_count()
@@ -226,9 +235,12 @@ def _decode(obj, process=None, item=None):
@staticmethod
def _encode(obj):
- return sqlite3.Binary(pickle.dumps(
+ blob = sqlite3.Binary(pickle.dumps(
obj, protocol=pickle.HIGHEST_PROTOCOL
))
+ size = getattr(blob, 'nbytes', None) or blob.itemsize * len(blob)
+ return blob, size
+
def _get(self, item_id, process=None, seconds=None):
with self as db:
From ae79b262aa5912a8fd147ec1c4804775b87cb551 Mon Sep 17 00:00:00 2001
From: MoojMidge <56883549+MoojMidge@users.noreply.github.com>
Date: Thu, 21 Dec 2023 02:27:20 +1100
Subject: [PATCH 113/141] Misc tidy ups
---
resources/lib/youtube_authentication.py | 4 +-
.../kodion/constants/const_content_types.py | 1 +
.../kodion/constants/const_paths.py | 1 +
.../kodion/constants/const_settings.py | 1 +
.../kodion/context/xbmc/xbmc_context.py | 4 +-
resources/lib/youtube_plugin/kodion/debug.py | 4 +-
.../youtube_plugin/kodion/items/__init__.py | 12 ++---
.../youtube_plugin/kodion/items/base_item.py | 20 ++++-----
.../kodion/network/http_server.py | 6 +--
.../youtube_plugin/kodion/network/requests.py | 2 +-
.../kodion/plugin/xbmc/xbmc_runner.py | 12 +++--
.../settings/xbmc/xbmc_plugin_settings.py | 44 +++++++++----------
.../kodion/sql_store/storage.py | 2 +-
.../kodion/ui/abstract_progress_dialog.py | 2 +-
.../youtube/client/login_client.py | 4 +-
.../youtube_plugin/youtube/client/youtube.py | 4 +-
.../youtube_plugin/youtube/helper/utils.py | 4 +-
.../lib/youtube_plugin/youtube/helper/v3.py | 4 +-
.../youtube/helper/video_info.py | 12 ++---
.../youtube_plugin/youtube/helper/yt_play.py | 8 ++--
.../lib/youtube_plugin/youtube/provider.py | 6 +--
21 files changed, 82 insertions(+), 75 deletions(-)
diff --git a/resources/lib/youtube_authentication.py b/resources/lib/youtube_authentication.py
index e29822074..fe463b867 100644
--- a/resources/lib/youtube_authentication.py
+++ b/resources/lib/youtube_authentication.py
@@ -103,8 +103,8 @@ def sign_in(addon_id):
try:
signed_in = youtube_authentication.sign_in(addon_id='plugin.video.example') # refreshes access tokens if already signed in
- except youtube_authentication.LoginException as e:
- error_message = e.get_message()
+ except youtube_authentication.LoginException as exc:
+ error_message = exc.get_message()
# handle error
signed_in = False
diff --git a/resources/lib/youtube_plugin/kodion/constants/const_content_types.py b/resources/lib/youtube_plugin/kodion/constants/const_content_types.py
index 5990ee1f6..6436882bb 100644
--- a/resources/lib/youtube_plugin/kodion/constants/const_content_types.py
+++ b/resources/lib/youtube_plugin/kodion/constants/const_content_types.py
@@ -10,6 +10,7 @@
from __future__ import absolute_import, division, unicode_literals
+
FILES = 'files'
SONGS = 'songs'
ARTISTS = 'artists'
diff --git a/resources/lib/youtube_plugin/kodion/constants/const_paths.py b/resources/lib/youtube_plugin/kodion/constants/const_paths.py
index 8903eb34d..aad8c4752 100644
--- a/resources/lib/youtube_plugin/kodion/constants/const_paths.py
+++ b/resources/lib/youtube_plugin/kodion/constants/const_paths.py
@@ -10,6 +10,7 @@
from __future__ import absolute_import, division, unicode_literals
+
SEARCH = 'kodion/search'
FAVORITES = 'kodion/favorites'
WATCH_LATER = 'kodion/watch_later'
diff --git a/resources/lib/youtube_plugin/kodion/constants/const_settings.py b/resources/lib/youtube_plugin/kodion/constants/const_settings.py
index 16ddff484..9f232632b 100644
--- a/resources/lib/youtube_plugin/kodion/constants/const_settings.py
+++ b/resources/lib/youtube_plugin/kodion/constants/const_settings.py
@@ -10,6 +10,7 @@
from __future__ import absolute_import, division, unicode_literals
+
THUMB_SIZE = 'kodion.thumbnail.size' # (int)
SHOW_FANART = 'kodion.fanart.show' # (bool)
SAFE_SEARCH = 'kodion.safe.search' # (int)
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 6ba3d588d..8472372a9 100644
--- a/resources/lib/youtube_plugin/kodion/context/xbmc/xbmc_context.py
+++ b/resources/lib/youtube_plugin/kodion/context/xbmc/xbmc_context.py
@@ -334,8 +334,8 @@ def get_language(self):
language = language.split('-')
language = '%s-%s' % (language[0].lower(), language[1].upper())
return language
- except Exception, ex:
- self.log_error('Failed to get system language (%s)', ex.__str__())
+ except Exception as exc:
+ self.log_error('Failed to get system language (%s)', exc.__str__())
return 'en-US'
'''
diff --git a/resources/lib/youtube_plugin/kodion/debug.py b/resources/lib/youtube_plugin/kodion/debug.py
index 4c7b9aa9b..896a7a3b1 100644
--- a/resources/lib/youtube_plugin/kodion/debug.py
+++ b/resources/lib/youtube_plugin/kodion/debug.py
@@ -63,7 +63,7 @@ def runtime(context, addon_version, elapsed, single_file=True):
class Profiler(object):
"""Class used to profile a block of code"""
- __slots__ = ('__weakref__', '_enabled', '_profiler', '_reuse', 'name', )
+ __slots__ = ('__weakref__', '_enabled', '_profiler', '_reuse', 'name',)
from cProfile import Profile as _Profile
from pstats import Stats as _Stats
@@ -120,7 +120,7 @@ def __enter__(self):
if not self._profiler:
self._create_profiler()
- def __exit__(self, exc_type=None, exc_value=None, traceback=None):
+ def __exit__(self, exc_type=None, exc_val=None, exc_tb=None):
if not self._enabled:
return
diff --git a/resources/lib/youtube_plugin/kodion/items/__init__.py b/resources/lib/youtube_plugin/kodion/items/__init__.py
index fff783f35..343c7acba 100644
--- a/resources/lib/youtube_plugin/kodion/items/__init__.py
+++ b/resources/lib/youtube_plugin/kodion/items/__init__.py
@@ -12,18 +12,18 @@
from .utils import to_json, from_json, to_jsons
-from .uri_item import UriItem
-from .base_item import BaseItem
from .audio_item import AudioItem
+from .base_item import BaseItem
from .directory_item import DirectoryItem
-from .watch_later_item import WatchLaterItem
from .favorites_item import FavoritesItem
-from .search_item import SearchItem
+from .image_item import ImageItem
from .new_search_item import NewSearchItem
-from .search_history_item import SearchHistoryItem
from .next_page_item import NextPageItem
+from .search_history_item import SearchHistoryItem
+from .search_item import SearchItem
+from .uri_item import UriItem
from .video_item import VideoItem
-from .image_item import ImageItem
+from .watch_later_item import WatchLaterItem
__all__ = ('AudioItem',
diff --git a/resources/lib/youtube_plugin/kodion/items/base_item.py b/resources/lib/youtube_plugin/kodion/items/base_item.py
index 2dee09a18..b648fc807 100644
--- a/resources/lib/youtube_plugin/kodion/items/base_item.py
+++ b/resources/lib/youtube_plugin/kodion/items/base_item.py
@@ -10,8 +10,8 @@
from __future__ import absolute_import, division, unicode_literals
-import hashlib
-import datetime
+from datetime import date, datetime
+from hashlib import md5
from ..compatibility import unescape
@@ -58,7 +58,7 @@ def get_id(self):
Returns a unique id of the item.
:return: unique id of the item.
"""
- md5_hash = hashlib.md5()
+ md5_hash = md5()
md5_hash.update(self._name.encode('utf-8'))
md5_hash.update(self._uri.encode('utf-8'))
return md5_hash.hexdigest()
@@ -106,7 +106,7 @@ def replace_context_menu(self):
return self._replace_context_menu
def set_date(self, year, month, day, hour=0, minute=0, second=0):
- self._date = datetime.datetime(year, month, day, hour, minute, second)
+ self._date = datetime(year, month, day, hour, minute, second)
def set_date_from_datetime(self, date_time):
self._date = date_time
@@ -121,12 +121,12 @@ def get_date(self, as_text=False, short=False):
return self._date
def set_dateadded(self, year, month, day, hour=0, minute=0, second=0):
- self._dateadded = datetime.datetime(year,
- month,
- day,
- hour,
- minute,
- second)
+ self._dateadded = datetime(year,
+ month,
+ day,
+ hour,
+ minute,
+ second)
def set_dateadded_from_datetime(self, date_time):
self._dateadded = date_time
diff --git a/resources/lib/youtube_plugin/kodion/network/http_server.py b/resources/lib/youtube_plugin/kodion/network/http_server.py
index 48836c372..18d4a041e 100644
--- a/resources/lib/youtube_plugin/kodion/network/http_server.py
+++ b/resources/lib/youtube_plugin/kodion/network/http_server.py
@@ -533,11 +533,11 @@ def get_http_server(address=None, port=None):
server = BaseHTTPServer.HTTPServer((address, port),
YouTubeProxyRequestHandler)
return server
- except socket_error as e:
+ except socket_error as exc:
log_debug('HTTPServer: Failed to start |{address}:{port}| |{response}|'
- .format(address=address, port=port, response=str(e)))
+ .format(address=address, port=port, response=str(exc)))
xbmcgui.Dialog().notification(_addon_name,
- str(e),
+ str(exc),
_addon_icon,
time=5000,
sound=False)
diff --git a/resources/lib/youtube_plugin/kodion/network/requests.py b/resources/lib/youtube_plugin/kodion/network/requests.py
index 34d4a8545..939a07b3c 100644
--- a/resources/lib/youtube_plugin/kodion/network/requests.py
+++ b/resources/lib/youtube_plugin/kodion/network/requests.py
@@ -49,7 +49,7 @@ def __init__(self, exc_type=RequestException):
def __enter__(self):
return self
- def __exit__(self, exc_type, exc_value, traceback):
+ def __exit__(self, exc_type=None, exc_val=None, exc_tb=None):
self._session.close()
def request(self, url, method='GET',
diff --git a/resources/lib/youtube_plugin/kodion/plugin/xbmc/xbmc_runner.py b/resources/lib/youtube_plugin/kodion/plugin/xbmc/xbmc_runner.py
index d23e8e48f..4b5cf9f19 100644
--- a/resources/lib/youtube_plugin/kodion/plugin/xbmc/xbmc_runner.py
+++ b/resources/lib/youtube_plugin/kodion/plugin/xbmc/xbmc_runner.py
@@ -10,6 +10,8 @@
from __future__ import absolute_import, division, unicode_literals
+from traceback import format_exc
+
from ..abstract_provider_runner import AbstractProviderRunner
from ...compatibility import xbmcgui, xbmcplugin
from ...exceptions import KodionException
@@ -46,10 +48,12 @@ def run(self, provider, context):
try:
results = provider.navigate(context)
- except KodionException as ex:
- if provider.handle_exception(context, ex):
- context.log_error(ex.__str__())
- xbmcgui.Dialog().ok("Exception in ContentProvider", ex.__str__())
+ except KodionException as exc:
+ if provider.handle_exception(context, exc):
+ context.log_error('XbmcRunner.run - {exc}:\n{details}'.format(
+ exc=exc, details=format_exc()
+ ))
+ xbmcgui.Dialog().ok("Error in ContentProvider", exc.__str__())
xbmcplugin.endOfDirectory(self.handle, succeeded=False)
return False
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 577d72bb2..682208539 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
@@ -73,12 +73,12 @@ def get_bool(self, setting, default=None, echo=None):
error = False
try:
value = bool(self._get_bool(self._type(), setting))
- except (AttributeError, TypeError) as ex:
- error = ex
+ except (AttributeError, TypeError) as exc:
+ error = exc
value = self.get_string(setting, echo=False)
value = AbstractSettings.VALUE_FROM_STR.get(value.lower(), default)
- except RuntimeError as ex:
- error = ex
+ except RuntimeError as exc:
+ error = exc
value = default
if self._echo and echo is not False:
@@ -95,8 +95,8 @@ def set_bool(self, setting, value, echo=None):
error = not self._set_bool(self._type(), setting, value)
if not error:
self._cache[setting] = value
- except RuntimeError as ex:
- error = ex
+ except RuntimeError as exc:
+ error = exc
if self._echo and echo is not False:
log_debug('Set |{setting}|: {value} (bool, {status})'.format(
@@ -115,16 +115,16 @@ def get_int(self, setting, default=-1, process=None, echo=None):
value = int(self._get_int(self._type(), setting))
if process:
value = process(value)
- except (AttributeError, TypeError, ValueError) as ex:
- error = ex
+ except (AttributeError, TypeError, ValueError) as exc:
+ error = exc
value = self.get_string(setting, echo=False)
try:
value = int(value)
- except (TypeError, ValueError) as ex:
- error = ex
+ except (TypeError, ValueError) as exc:
+ error = exc
value = default
- except RuntimeError as ex:
- error = ex
+ except RuntimeError as exc:
+ error = exc
value = default
if self._echo and echo is not False:
@@ -141,8 +141,8 @@ def set_int(self, setting, value, echo=None):
error = not self._set_int(self._type(), setting, value)
if not error:
self._cache[setting] = value
- except RuntimeError as ex:
- error = ex
+ except RuntimeError as exc:
+ error = exc
if self._echo and echo is not False:
log_debug('Set |{setting}|: {value} (int, {status})'.format(
@@ -159,8 +159,8 @@ def get_string(self, setting, default='', echo=None):
error = False
try:
value = self._get_str(self._type(), setting) or default
- except RuntimeError as ex:
- error = ex
+ except RuntimeError as exc:
+ error = exc
value = default
if self._echo and echo is not False:
@@ -177,8 +177,8 @@ def set_string(self, setting, value, echo=None):
error = not self._set_str(self._type(), setting, value)
if not error:
self._cache[setting] = value
- except RuntimeError as ex:
- error = ex
+ except RuntimeError as exc:
+ error = exc
if self._echo and echo is not False:
log_debug('Set |{setting}|: "{value}" (str, {status})'.format(
@@ -197,8 +197,8 @@ def get_string_list(self, setting, default=None, echo=None):
value = self._get_str_list(self._type(), setting)
if not value:
value = [] if default is None else default
- except RuntimeError as ex:
- error = ex
+ except RuntimeError as exc:
+ error = exc
value = default
if self._echo and echo is not False:
@@ -215,8 +215,8 @@ def set_string_list(self, setting, value, echo=None):
error = not self._set_str_list(self._type(), setting, value)
if not error:
self._cache[setting] = value
- except RuntimeError as ex:
- error = ex
+ except RuntimeError as exc:
+ error = exc
if self._echo and echo is not False:
log_debug('Set |{setting}|: "{value}" (str list, {status})'.format(
diff --git a/resources/lib/youtube_plugin/kodion/sql_store/storage.py b/resources/lib/youtube_plugin/kodion/sql_store/storage.py
index 23f4b347a..f8a0ced86 100644
--- a/resources/lib/youtube_plugin/kodion/sql_store/storage.py
+++ b/resources/lib/youtube_plugin/kodion/sql_store/storage.py
@@ -69,7 +69,7 @@ def __enter__(self):
self._open()
return self
- def __exit__(self, exc_type, exc_val, exc_tb):
+ def __exit__(self, exc_type=None, exc_val=None, exc_tb=None):
self._close()
def _open(self):
diff --git a/resources/lib/youtube_plugin/kodion/ui/abstract_progress_dialog.py b/resources/lib/youtube_plugin/kodion/ui/abstract_progress_dialog.py
index 3406a7ca4..dd6eb24f3 100644
--- a/resources/lib/youtube_plugin/kodion/ui/abstract_progress_dialog.py
+++ b/resources/lib/youtube_plugin/kodion/ui/abstract_progress_dialog.py
@@ -22,7 +22,7 @@ def __init__(self, dialog, heading, text, total=100):
def __enter__(self):
return self
- def __exit__(self, exc_type, exc_val, exc_tb):
+ def __exit__(self, exc_type=None, exc_val=None, exc_tb=None):
self.close()
def get_total(self):
diff --git a/resources/lib/youtube_plugin/youtube/client/login_client.py b/resources/lib/youtube_plugin/youtube/client/login_client.py
index 77b4ecd8d..a1f00d46d 100644
--- a/resources/lib/youtube_plugin/youtube/client/login_client.py
+++ b/resources/lib/youtube_plugin/youtube/client/login_client.py
@@ -90,8 +90,8 @@ def _response_hook(**kwargs):
raise YouTubeException('"error" in response JSON data',
json_data=json_data,
response=response)
- except ValueError as error:
- raise InvalidJSON(error, response=response)
+ except ValueError as exc:
+ raise InvalidJSON(exc, response=response)
response.raise_for_status()
return json_data
diff --git a/resources/lib/youtube_plugin/youtube/client/youtube.py b/resources/lib/youtube_plugin/youtube/client/youtube.py
index e484bcf54..0d9f65cce 100644
--- a/resources/lib/youtube_plugin/youtube/client/youtube.py
+++ b/resources/lib/youtube_plugin/youtube/client/youtube.py
@@ -1200,8 +1200,8 @@ def _response_hook(**kwargs):
raise YouTubeException('"error" in response JSON data',
json_data=json_data,
**kwargs)
- except ValueError as error:
- raise InvalidJSON(error, **kwargs)
+ except ValueError as exc:
+ raise InvalidJSON(exc, **kwargs)
response.raise_for_status()
return json_data
diff --git a/resources/lib/youtube_plugin/youtube/helper/utils.py b/resources/lib/youtube_plugin/youtube/helper/utils.py
index d2ccf5439..dc979a27e 100644
--- a/resources/lib/youtube_plugin/youtube/helper/utils.py
+++ b/resources/lib/youtube_plugin/youtube/helper/utils.py
@@ -269,7 +269,7 @@ def update_playlist_infos(provider, context, playlist_id_dict,
playlist_item.set_image(image)
channel_id = snippet['channelId']
- # if the path directs to a playlist of our own, we correct the channel id to 'mine'
+ # if the path directs to a playlist of our own, set channel id to 'mine'
if path == '/channel/mine/playlists/':
channel_id = 'mine'
channel_name = snippet.get('channelTitle', '')
@@ -611,7 +611,7 @@ def update_video_infos(provider, context, video_id_dict,
context_menu, context
)
- # got to [CHANNEL], only if we are not directly in the channel provide a jump to the channel
+ # got to [CHANNEL] only if we are not directly in the channel
if (channel_id and channel_name and
create_path('channel', channel_id) != path):
video_item.set_channel_id(channel_id)
diff --git a/resources/lib/youtube_plugin/youtube/helper/v3.py b/resources/lib/youtube_plugin/youtube/helper/v3.py
index 295c25efa..d92c2c2de 100644
--- a/resources/lib/youtube_plugin/youtube/helper/v3.py
+++ b/resources/lib/youtube_plugin/youtube/helper/v3.py
@@ -48,7 +48,6 @@ def _process_list_response(provider, context, json_data):
use_play_data = not incognito and settings.use_local_history()
for yt_item in yt_items:
-
is_youtube, kind = _parse_kind(yt_item)
if not is_youtube or not kind:
context.log_debug('v3 response: Item discarded, is_youtube=False')
@@ -278,7 +277,8 @@ def _process_list_response(provider, context, json_data):
else:
raise KodionException("Unknown kind '%s'" % kind)
- # this will also update the channel_id_dict with the correct channel id for each video.
+ # this will also update the channel_id_dict with the correct channel_id
+ # for each video.
channel_items_dict = {}
running = 0
diff --git a/resources/lib/youtube_plugin/youtube/helper/video_info.py b/resources/lib/youtube_plugin/youtube/helper/video_info.py
index 3fc2bea5e..c24d34546 100644
--- a/resources/lib/youtube_plugin/youtube/helper/video_info.py
+++ b/resources/lib/youtube_plugin/youtube/helper/video_info.py
@@ -10,10 +10,10 @@
from __future__ import absolute_import, division, unicode_literals
+import json
import random
import re
-import traceback
-from json import dumps as json_dumps, loads as json_loads
+from traceback import format_exc
from .ratebypass import ratebypass
from .signature.cipher import Cipher
@@ -706,7 +706,7 @@ def _get_player_config(page):
found = re.search(r'ytcfg\.set\s*\(\s*({.+?})\s*\)\s*;', page.text)
if found:
- return json_loads(found.group(1))
+ return json.loads(found.group(1))
return None
def _get_player_js(self):
@@ -930,9 +930,9 @@ def _process_signature_cipher(self, stream_map):
if not signature:
try:
signature = self._cipher.get_signature(encrypted_signature)
- except Exception as error:
+ except Exception as exc:
self._context.log_debug('{0}: {1}\n{2}'.format(
- error, encrypted_signature, traceback.format_exc()
+ exc, encrypted_signature, format_exc()
))
self._context.log_error(
'Failed to extract URL from signatureCipher'
@@ -1566,7 +1566,7 @@ def _process_stream_data(self, stream_data, default_lang_code='und'):
def _stream_sort(stream):
if not stream:
- return (1, )
+ return (1,)
return (
- stream['height'],
diff --git a/resources/lib/youtube_plugin/youtube/helper/yt_play.py b/resources/lib/youtube_plugin/youtube/helper/yt_play.py
index 8274f5b58..484d6857e 100644
--- a/resources/lib/youtube_plugin/youtube/helper/yt_play.py
+++ b/resources/lib/youtube_plugin/youtube/helper/yt_play.py
@@ -12,7 +12,7 @@
import json
import random
-import traceback
+from traceback import format_exc
from ..helper import utils, v3
from ..youtube_exceptions import YouTubeException
@@ -46,9 +46,9 @@ def play_video(provider, context):
try:
video_streams = client.get_video_streams(context, video_id)
- except YouTubeException as e:
- ui.show_notification(message=e.get_message())
- context.log_error(traceback.print_exc())
+ except YouTubeException as exc:
+ ui.show_notification(message=exc.get_message())
+ context.log_error(format_exc())
return False
if not video_streams:
diff --git a/resources/lib/youtube_plugin/youtube/provider.py b/resources/lib/youtube_plugin/youtube/provider.py
index 2c40c1d3f..0041370fe 100644
--- a/resources/lib/youtube_plugin/youtube/provider.py
+++ b/resources/lib/youtube_plugin/youtube/provider.py
@@ -217,11 +217,11 @@ def get_client(self, context):
access_manager.update_dev_access_token(dev_id, access_token, expires_in)
else:
access_manager.update_access_token(access_token, expires_in)
- except (InvalidGrant, LoginException) as ex:
- self.handle_exception(context, ex)
+ except (InvalidGrant, LoginException) as exc:
+ self.handle_exception(context, exc)
access_tokens = ['', '']
# reset access_token
- if isinstance(ex, InvalidGrant):
+ if isinstance(exc, InvalidGrant):
if dev_id:
access_manager.update_dev_access_token(dev_id, access_token='', refresh_token='')
else:
From da1b91a0220ccf44cc7fa06a438353dec91a88ba Mon Sep 17 00:00:00 2001
From: MoojMidge <56883549+MoojMidge@users.noreply.github.com>
Date: Thu, 21 Dec 2023 03:27:27 +1100
Subject: [PATCH 114/141] Further updates to SQL storage
- Versioning for each type of storage
- Table creation and update to occur once per class or if db is deleted
- Use connection context managers to auto-commit/rollback
- Transactions created manually where required
- Restore Python 2 compatibility
---
.../kodion/sql_store/data_cache.py | 14 +-
.../kodion/sql_store/favorite_list.py | 8 +-
.../kodion/sql_store/function_cache.py | 12 +-
.../kodion/sql_store/playback_history.py | 16 +-
.../kodion/sql_store/search_history.py | 8 +-
.../kodion/sql_store/storage.py | 427 +++++++++++-------
.../kodion/sql_store/watch_later_list.py | 8 +-
7 files changed, 310 insertions(+), 183 deletions(-)
diff --git a/resources/lib/youtube_plugin/kodion/sql_store/data_cache.py b/resources/lib/youtube_plugin/kodion/sql_store/data_cache.py
index 06539b514..e0271f939 100644
--- a/resources/lib/youtube_plugin/kodion/sql_store/data_cache.py
+++ b/resources/lib/youtube_plugin/kodion/sql_store/data_cache.py
@@ -14,6 +14,11 @@
class DataCache(Storage):
+ _table_name = 'storage_v2'
+ _table_created = False
+ _table_updated = False
+ _sql = {}
+
def __init__(self, filename, max_file_size_mb=5):
max_file_size_kb = max_file_size_mb * 1024
super(DataCache, self).__init__(filename,
@@ -34,10 +39,7 @@ def set_item(self, content_id, item):
self._set(content_id, item)
def set_items(self, items):
- self._set_all(items)
-
- def clear(self):
- self._clear()
+ self._set_many(items)
def remove(self, content_id):
self._remove(content_id)
@@ -45,5 +47,5 @@ def remove(self, content_id):
def update(self, content_id, item):
self._set(str(content_id), item)
- def _optimize_item_count(self):
- pass
+ def _optimize_item_count(self, limit=-1, defer=False):
+ return False
diff --git a/resources/lib/youtube_plugin/kodion/sql_store/favorite_list.py b/resources/lib/youtube_plugin/kodion/sql_store/favorite_list.py
index 119cd95dd..44e72d220 100644
--- a/resources/lib/youtube_plugin/kodion/sql_store/favorite_list.py
+++ b/resources/lib/youtube_plugin/kodion/sql_store/favorite_list.py
@@ -15,12 +15,14 @@
class FavoriteList(Storage):
+ _table_name = 'storage_v2'
+ _table_created = False
+ _table_updated = False
+ _sql = {}
+
def __init__(self, filename):
super(FavoriteList, self).__init__(filename)
- def clear(self):
- self._clear()
-
@staticmethod
def _sort_item(item):
return item.get_name().upper()
diff --git a/resources/lib/youtube_plugin/kodion/sql_store/function_cache.py b/resources/lib/youtube_plugin/kodion/sql_store/function_cache.py
index e244ba940..17e6a9682 100644
--- a/resources/lib/youtube_plugin/kodion/sql_store/function_cache.py
+++ b/resources/lib/youtube_plugin/kodion/sql_store/function_cache.py
@@ -17,6 +17,11 @@
class FunctionCache(Storage):
+ _table_name = 'storage_v2'
+ _table_created = False
+ _table_updated = False
+ _sql = {}
+
def __init__(self, filename, max_file_size_mb=5):
max_file_size_kb = max_file_size_mb * 1024
super(FunctionCache, self).__init__(filename,
@@ -24,9 +29,6 @@ def __init__(self, filename, max_file_size_mb=5):
self._enabled = True
- def clear(self):
- self._clear()
-
def enabled(self):
"""
Enables the caching
@@ -90,7 +92,7 @@ def get(self, func, seconds, *args, **keywords):
self._set(cache_id, data)
return data
- def _optimize_item_count(self):
+ def _optimize_item_count(self, limit=-1, defer=False):
# override method Storage._optimize_item_count
# for function cache do not optimize by item count, use database size.
- pass
+ return False
diff --git a/resources/lib/youtube_plugin/kodion/sql_store/playback_history.py b/resources/lib/youtube_plugin/kodion/sql_store/playback_history.py
index a0eae31e4..56874ba97 100644
--- a/resources/lib/youtube_plugin/kodion/sql_store/playback_history.py
+++ b/resources/lib/youtube_plugin/kodion/sql_store/playback_history.py
@@ -13,6 +13,11 @@
class PlaybackHistory(Storage):
+ _table_name = 'storage_v2'
+ _table_created = False
+ _table_updated = False
+ _sql = {}
+
def __init__(self, filename):
super(PlaybackHistory, self).__init__(filename)
@@ -34,17 +39,14 @@ def get_item(self, key):
result = self._get(key, process=self._add_last_played)
return result
- def clear(self):
- self._clear()
-
def remove(self, video_id):
self._remove(video_id)
def update(self, video_id, play_data):
self._set(video_id, play_data)
- def _optimize_item_count(self):
- pass
+ def _optimize_item_count(self, limit=-1, defer=False):
+ return False
- def _optimize_file_size(self):
- pass
+ def _optimize_file_size(self, limit=-1, defer=False):
+ return False
diff --git a/resources/lib/youtube_plugin/kodion/sql_store/search_history.py b/resources/lib/youtube_plugin/kodion/sql_store/search_history.py
index a0f7046ea..b53110b32 100644
--- a/resources/lib/youtube_plugin/kodion/sql_store/search_history.py
+++ b/resources/lib/youtube_plugin/kodion/sql_store/search_history.py
@@ -16,6 +16,11 @@
class SearchHistory(Storage):
+ _table_name = 'storage_v2'
+ _table_created = False
+ _table_updated = False
+ _sql = {}
+
def __init__(self, filename, max_item_count=10):
super(SearchHistory, self).__init__(filename,
max_item_count=max_item_count)
@@ -29,9 +34,6 @@ def get_items(self):
values_only=True)
return result
- def clear(self):
- self._clear()
-
@staticmethod
def _make_id(search_text):
md5_hash = md5()
diff --git a/resources/lib/youtube_plugin/kodion/sql_store/storage.py b/resources/lib/youtube_plugin/kodion/sql_store/storage.py
index f8a0ced86..0d990b709 100644
--- a/resources/lib/youtube_plugin/kodion/sql_store/storage.py
+++ b/resources/lib/youtube_plugin/kodion/sql_store/storage.py
@@ -15,7 +15,7 @@
import sqlite3
import time
from datetime import datetime
-from traceback import print_exc
+from traceback import format_exc
from ..logger import log_error
from ..utils.datetime_parser import since_epoch
@@ -28,20 +28,107 @@ class Storage(object):
ONE_WEEK = 7 * ONE_DAY
ONE_MONTH = 4 * ONE_WEEK
- _table_name = 'storage_v3'
- _clear_sql = 'DELETE FROM {table}'.format(table=_table_name)
- _create_table_sql = 'CREATE TABLE IF NOT EXISTS {table} (key TEXT PRIMARY KEY, time REAL, value BLOB, size INTEGER)'.format(table=_table_name)
- _drop_old_tables_sql = 'DELETE FROM sqlite_master WHERE type = "table" and name IS NOT "{table}"'.format(table=_table_name)
- _get_sql = 'SELECT * FROM {table} WHERE key = ?'.format(table=_table_name)
- _get_by_sql = 'SELECT * FROM {table} WHERE key in ({{0}})'.format(table=_table_name)
- _get_all_asc_sql = 'SELECT * FROM {table} ORDER BY time ASC LIMIT {{0}}'.format(table=_table_name)
- _get_all_desc_sql = 'SELECT * FROM {table} ORDER BY time DESC LIMIT {{0}}'.format(table=_table_name)
- _is_empty_sql = 'SELECT EXISTS(SELECT 1 FROM {table} LIMIT 1)'.format(table=_table_name)
- _optimize_item_sql = 'SELECT key FROM {table} ORDER BY time DESC LIMIT -1 OFFSET {{0}}'.format(table=_table_name)
- _prune_sql = 'DELETE FROM {table} WHERE ROWID IN (SELECT ROWID FROM {table} WHERE (SELECT SUM(size) FROM {table} AS _ WHERE time<={table}.time) <= {{0}})'.format(table=_table_name)
- _remove_sql = 'DELETE FROM {table} WHERE key = ?'.format(table=_table_name)
- _remove_all_sql = 'DELETE FROM {table} WHERE key in ({{0}})'.format(table=_table_name)
- _set_sql = 'REPLACE INTO {table} (key, time, value, size) VALUES(?, ?, ?, ?)'.format(table=_table_name)
+ _table_name = 'storage_v2'
+ _table_created = False
+ _table_updated = False
+
+ _sql = {
+ 'clear': (
+ 'DELETE'
+ ' FROM {table};'
+ ),
+ 'create_table': (
+ 'CREATE TABLE'
+ ' IF NOT EXISTS {table} ('
+ ' key TEXT PRIMARY KEY,'
+ ' timestamp REAL,'
+ ' value BLOB,'
+ ' size INTEGER'
+ ' );'
+ ),
+ 'drop_old_table': (
+ 'DELETE'
+ ' FROM sqlite_master'
+ ' WHERE type = "table"'
+ ' and name IS NOT "{table}";'
+ ),
+ 'get': (
+ 'SELECT *'
+ ' FROM {table}'
+ ' WHERE key = ?;'
+ ),
+ 'get_by_key': (
+ 'SELECT *'
+ ' FROM {table}'
+ ' WHERE key in ({{0}});'
+ ),
+ 'get_many': (
+ 'SELECT *'
+ ' FROM {table}'
+ ' ORDER BY timestamp'
+ ' LIMIT {{0}};'
+ ),
+ 'get_many_desc': (
+ 'SELECT *'
+ ' FROM {table}'
+ ' ORDER BY timestamp DESC'
+ ' LIMIT {{0}};'
+ ),
+ 'has_old_table': (
+ 'SELECT EXISTS ('
+ ' SELECT 1'
+ ' FROM sqlite_master'
+ ' WHERE type = "table"'
+ ' and name IS NOT "{table}"'
+ ');'
+ ),
+ 'is_empty': (
+ 'SELECT EXISTS ('
+ ' SELECT 1'
+ ' FROM {table}'
+ ');'
+ ),
+ 'prune_by_count': (
+ 'DELETE'
+ ' FROM {table}'
+ ' WHERE rowid IN ('
+ ' SELECT rowid'
+ ' FROM {table}'
+ ' ORDER BY timestamp DESC'
+ ' LIMIT {{0}}'
+ ' OFFSET {{1}}'
+ ' );'
+ ),
+ 'prune_by_size': (
+ 'DELETE'
+ ' FROM {table}'
+ ' WHERE rowid IN ('
+ ' SELECT rowid'
+ ' FROM {table}'
+ ' WHERE ('
+ ' SELECT SUM(size)'
+ ' FROM {table} AS _'
+ ' WHERE timestamp<={table}.timestamp'
+ ' ) <= {{0}}'
+ ' );'
+ ),
+ 'remove': (
+ 'DELETE'
+ ' FROM {table}'
+ ' WHERE key = ?;'
+ ),
+ 'remove_by_key': (
+ 'DELETE'
+ ' FROM {table}'
+ ' WHERE key in ({{0}});'
+ ),
+ 'set': (
+ 'REPLACE'
+ ' INTO {table}'
+ ' (key, timestamp, value, size)'
+ ' VALUES(?, ?, ?, ?);'
+ ),
+ }
def __init__(self, filename, max_item_count=-1, max_file_size_kb=-1):
self._filename = filename
@@ -52,9 +139,12 @@ def __init__(self, filename, max_item_count=-1, max_file_size_kb=-1):
self._max_item_count = max_item_count
self._max_file_size_kb = max_file_size_kb
- self._table_created = False
- self._table_updated = False
- self._needs_commit = False
+ if not self._sql:
+ statements = {
+ name: sql.format(table=self._table_name)
+ for name, sql in Storage._sql.items()
+ }
+ self.__class__._sql.update(statements)
def set_max_item_count(self, max_item_count):
self._max_item_count = max_item_count
@@ -63,61 +153,88 @@ def set_max_file_size_kb(self, max_file_size_kb):
self._max_file_size_kb = max_file_size_kb
def __del__(self):
- self._close(True)
+ self._close()
def __enter__(self):
- self._open()
- return self
+ if not self._db or not self._cursor:
+ self._open()
+ return self._db, self._cursor
def __exit__(self, exc_type=None, exc_val=None, exc_tb=None):
self._close()
def _open(self):
- if self._db:
- if not self._cursor:
- self._cursor = self._db.cursor()
- return
-
- path = os.path.dirname(self._filename)
- if not os.path.exists(path):
- os.makedirs(path)
+ if not os.path.exists(self._filename):
+ os.makedirs(os.path.dirname(self._filename), exist_ok=True)
+ self.__class__._table_created = False
+ self.__class__._table_updated = True
+
+ try:
+ db = sqlite3.connect(self._filename,
+ check_same_thread=False,
+ timeout=1,
+ isolation_level=None)
+ except sqlite3.OperationalError as exc:
+ log_error('SQLStorage._execute - {exc}:\n{details}'.format(
+ exc=exc, details=format_exc()
+ ))
+ return False
- db = sqlite3.connect(self._filename, check_same_thread=False,
- timeout=1, isolation_level=None)
cursor = db.cursor()
- # cursor.execute('PRAGMA journal_mode=MEMORY')
- cursor.execute('PRAGMA journal_mode=WAL')
- cursor.execute('PRAGMA busy_timeout=20000')
- cursor.execute('PRAGMA read_uncommitted=TRUE')
- cursor.execute('PRAGMA temp_store=MEMORY')
- cursor.execute('PRAGMA page_size=4096')
- # cursor.execute('PRAGMA synchronous=OFF')
- cursor.execute('PRAGMA synchronous=NORMAL')
- cursor.arraysize = 50
+ cursor.arraysize = 100
+
+ sql_script = [
+ 'PRAGMA journal_mode = WAL;',
+ 'PRAGMA busy_timeout = 20000;',
+ 'PRAGMA read_uncommitted = TRUE;',
+ 'PRAGMA temp_store = MEMORY;',
+ 'PRAGMA page_size = 4096;',
+ 'PRAGMA synchronous = NORMAL;',
+ 'PRAGMA mmap_size = 10485760;',
+ # 'PRAGMA locking_mode = EXCLUSIVE;'
+ 'PRAGMA cache_size = 500;'
+ ]
+ statements = []
+
+ if not self._table_created:
+ statements.append(self._sql['create_table'])
+
+ if not self._table_updated:
+ for result in cursor.execute(self._sql['has_old_table']):
+ if result[0] == 1:
+ statements.extend((
+ 'PRAGMA writable_schema = 1;',
+ self._sql['drop_old_table'],
+ 'PRAGMA writable_schema = 0;',
+ ))
+ break
+
+ if statements:
+ transaction_begin = len(sql_script) + 1
+ sql_script.extend(('BEGIN;', 'COMMIT;', 'VACUUM;'))
+ sql_script[transaction_begin:transaction_begin] = statements
+ cursor.executescript('\n'.join(sql_script))
+
+ self.__class__._table_created = True
+ self.__class__._table_updated = True
self._db = db
self._cursor = cursor
- self._create_table()
- self._drop_old_tables()
- self._optimize_file_size()
+ def _close(self):
+ if self._cursor:
+ self._execute(self._cursor, 'PRAGMA optimize')
+ self._cursor.close()
+ self._cursor = None
+ if self._db:
+ # Not needed if using self._db as a context manager
+ # self._db.commit()
+ self._db.close()
+ self._db = None
- def _drop_old_tables(self):
- if self._table_updated:
- return
- self._execute(True, 'PRAGMA writable_schema=1')
- self._execute(True, self._drop_old_tables_sql)
- self._execute(True, 'PRAGMA writable_schema=0')
- self._sync()
- self._execute(False, 'VACUUM')
- self._table_updated = True
-
- def _execute(self, needs_commit, query, values=None, many=False):
+ @staticmethod
+ def _execute(cursor, query, values=None, many=False):
if values is None:
values = ()
- if not self._needs_commit and needs_commit:
- self._needs_commit = True
- self._cursor.execute('BEGIN')
-
"""
Tests revealed that sqlite has problems to release the database in time
This happens no so often, but just to be sure, we try at least 3 times
@@ -126,99 +243,95 @@ def _execute(self, needs_commit, query, values=None, many=False):
for _ in range(3):
try:
if many:
- return self._cursor.executemany(query, values)
- return self._cursor.execute(query, values)
- except TypeError:
- log_error('SQLStorage._execute - |{0}|'.format(print_exc()))
- return []
- except:
- log_error('SQLStorage._execute - |{0}|'.format(print_exc()))
+ return cursor.executemany(query, values)
+ return cursor.execute(query, values)
+ except sqlite3.OperationalError as exc:
+ log_error('SQLStorage._execute - {exc}:\n{details}'.format(
+ exc=exc, details=format_exc()
+ ))
time.sleep(0.1)
+ except sqlite3.Error as exc:
+ log_error('SQLStorage._execute - {exc}:\n{details}'.format(
+ exc=exc, details=format_exc()
+ ))
+ return []
return []
- def _close(self, full=False):
- if not self._db:
- return
-
- if self._cursor:
- self._sync()
- self._db.commit()
- self._cursor.close()
- self._cursor = None
- if full:
- self._db.close()
- self._db = None
-
- def _optimize_file_size(self):
- # do nothing - only we have given a size
+ def _optimize_file_size(self, defer=False):
+ # do nothing - optimize only if max size limit has been set
if self._max_file_size_kb <= 0:
- return
-
- # do nothing - only if this db exists
- if not os.path.exists(self._filename):
- return
+ return False
- file_size_kb = (os.path.getsize(self._filename) // 1024)
- if file_size_kb <= self._max_file_size_kb:
- return
+ try:
+ file_size_kb = (os.path.getsize(self._filename) // 1024)
+ if file_size_kb <= self._max_file_size_kb:
+ return False
+ except OSError:
+ return False
prune_size = 1024 * int(file_size_kb - self._max_file_size_kb / 2)
- query = self._prune_sql.format(prune_size)
- self._execute(True, query)
- self._sync()
- self._execute(False, 'VACUUM')
-
- def _create_table(self):
- if self._table_created:
- return
- self._execute(True, self._create_table_sql)
- self._table_created = True
-
- def _sync(self):
- if not self._needs_commit:
- return None
- self._needs_commit = False
- return self._execute(False, 'COMMIT')
+ query = self._sql['prune_by_size'].format(prune_size)
+ if defer:
+ return query
+ with self as (db, cursor), db:
+ self._execute(cursor, query)
+ self._execute(cursor, 'VACUUM')
+ return True
+
+ def _optimize_item_count(self, limit=-1, defer=False):
+ # do nothing - optimize only if max item limit has been set
+ if self._max_item_count < 0:
+ return False
- def _set(self, item_id, item):
- now = since_epoch(datetime.now())
- with self as db:
- db._execute(True, db._set_sql,
- values=[str(item_id), now, *db._encode(item)])
- self._optimize_item_count()
+ # clear db if max item count has been set to 0
+ if not self._max_item_count:
+ if not self._is_empty():
+ return self.clear(defer)
+ return False
+
+ query = self._sql['prune_by_count'].format(
+ limit, self._max_item_count
+ )
+ if defer:
+ return query
+ with self as (db, cursor), db:
+ self._execute(cursor, query)
+ self._execute(cursor, 'VACUUM')
+ return True
- def _set_all(self, items):
+ def _set(self, item_id, item):
+ values = self._encode(item_id, item)
+ optimize_query = self._optimize_item_count(1, defer=True)
+ with self as (db, cursor), db:
+ if optimize_query:
+ self._execute(cursor, 'BEGIN')
+ self._execute(cursor, optimize_query)
+ self._execute(cursor, self._sql['set'], values=values)
+
+ def _set_many(self, items):
now = since_epoch(datetime.now())
- with self as db:
- db._execute(True, db._set_sql, many=True,
- values=[(str(item_id), now, *db._encode(item))
- for item_id, item in items.items()])
- self._optimize_item_count()
+ values = [self._encode(*item, timestamp=now)
+ for item in items.items()]
+ optimize_query = self._optimize_item_count(len(items), defer=True)
+ with self as (db, cursor), db:
+ self._execute(cursor, 'BEGIN')
+ if optimize_query:
+ self._execute(cursor, optimize_query)
+ self._execute(cursor, self._sql['set'], many=True, values=values)
+ self._optimize_file_size()
- def _optimize_item_count(self):
- if not self._max_item_count:
- if not self._is_empty():
- self._clear()
- return
- if self._max_item_count < 0:
- return
- query = self._optimize_item_sql.format(self._max_item_count)
- with self as db:
- item_ids = db._execute(False, query)
- item_ids = [item_id[0] for item_id in item_ids]
- if item_ids:
- db._remove_all(item_ids)
-
- def _clear(self):
- with self as db:
- db._execute(True, db._clear_sql)
- db._create_table()
- db._sync()
- db._execute(False, 'VACUUM')
+ def clear(self, defer=False):
+ query = self._sql['clear']
+ if defer:
+ return query
+ with self as (db, cursor), db:
+ self._execute(cursor, query)
+ self._execute(cursor, 'VACUUM')
+ return True
def _is_empty(self):
- with self as db:
- result = db._execute(False, db._is_empty_sql)
+ with self as (db, cursor), db:
+ result = self._execute(cursor, self._sql['is_empty'])
for item in result:
is_empty = item[0] == 0
break
@@ -234,17 +347,17 @@ def _decode(obj, process=None, item=None):
return decoded_obj
@staticmethod
- def _encode(obj):
+ def _encode(key, obj, timestamp=None):
+ timestamp = timestamp or since_epoch(datetime.now())
blob = sqlite3.Binary(pickle.dumps(
obj, protocol=pickle.HIGHEST_PROTOCOL
))
size = getattr(blob, 'nbytes', None) or blob.itemsize * len(blob)
- return blob, size
-
+ return str(key), timestamp, blob, size
def _get(self, item_id, process=None, seconds=None):
- with self as db:
- result = db._execute(False, db._get_sql, [str(item_id)])
+ with self as (db, cursor), db:
+ result = self._execute(cursor, self._sql['get'], [str(item_id)])
item = result.fetchone() if result else None
if not item:
return None
@@ -258,45 +371,47 @@ def _get_by_ids(self, item_ids=None, oldest_first=True, limit=-1,
as_dict=False, values_only=False):
if not item_ids:
if oldest_first:
- query = self._get_all_asc_sql
+ query = self._sql['get_many']
else:
- query = self._get_all_desc_sql
+ query = self._sql['get_many_desc']
query = query.format(limit)
else:
num_ids = len(item_ids)
- query = self._get_by_sql.format(('?,' * (num_ids - 1)) + '?')
+ query = self._sql['get_by_key'].format(('?,' * (num_ids - 1)) + '?')
item_ids = tuple(item_ids)
- with self as db:
- result = db._execute(False, query, item_ids)
+ with self as (db, cursor), db:
+ result = self._execute(cursor, query, item_ids)
cut_off = since_epoch(datetime.now()) - seconds if seconds else 0
if as_dict:
result = {
- item[0]: db._decode(item[2], process, item)
+ item[0]: self._decode(item[2], process, item)
for item in result if not cut_off or item[1] >= cut_off
}
elif values_only:
result = [
- db._decode(item[2], process, item)
+ self._decode(item[2], process, item)
for item in result if not cut_off or item[1] >= cut_off
]
else:
result = [
(item[0],
self._convert_timestamp(item[1]),
- db._decode(item[2], process, item))
+ self._decode(item[2], process, item))
for item in result if not cut_off or item[1] >= cut_off
]
return result
def _remove(self, item_id):
- with self as db:
- db._execute(True, db._remove_sql, [item_id])
+ with self as (db, cursor), db:
+ self._execute(cursor, self._sql['remove'], [item_id])
- def _remove_all(self, item_ids):
+ def _remove_many(self, item_ids):
num_ids = len(item_ids)
- query = self._remove_all_sql.format(('?,' * (num_ids - 1)) + '?')
- self._execute(True, query, tuple(item_ids))
+ query = self._sql['remove_by_key'].format(('?,' * (num_ids - 1)) + '?')
+ with self as (db, cursor), db:
+ self._execute(cursor, query, tuple(item_ids))
+ self._execute(cursor, 'VACUUM')
@classmethod
def _convert_timestamp(cls, val):
diff --git a/resources/lib/youtube_plugin/kodion/sql_store/watch_later_list.py b/resources/lib/youtube_plugin/kodion/sql_store/watch_later_list.py
index 70a1b7f9e..1618a5570 100644
--- a/resources/lib/youtube_plugin/kodion/sql_store/watch_later_list.py
+++ b/resources/lib/youtube_plugin/kodion/sql_store/watch_later_list.py
@@ -17,12 +17,14 @@
class WatchLaterList(Storage):
+ _table_name = 'storage_v2'
+ _table_created = False
+ _table_updated = False
+ _sql = {}
+
def __init__(self, filename):
super(WatchLaterList, self).__init__(filename)
- def clear(self):
- self._clear()
-
def get_items(self):
result = self._get_by_ids(process=from_json, values_only=True)
return result
From 8b1a57626ab611c9d52f17d6a0311d257981645e Mon Sep 17 00:00:00 2001
From: MoojMidge <56883549+MoojMidge@users.noreply.github.com>
Date: Thu, 21 Dec 2023 03:57:31 +1100
Subject: [PATCH 115/141] Update ResourceManager and _process_list_response
- Defer cache store until all data has been fetched/updated
- Improve deferred thread creation sequence
---
.../youtube/helper/resource_manager.py | 49 +++---
.../lib/youtube_plugin/youtube/helper/v3.py | 146 +++++++++++-------
2 files changed, 122 insertions(+), 73 deletions(-)
diff --git a/resources/lib/youtube_plugin/youtube/helper/resource_manager.py b/resources/lib/youtube_plugin/youtube/helper/resource_manager.py
index a685217ae..ffcbb7cf9 100644
--- a/resources/lib/youtube_plugin/youtube/helper/resource_manager.py
+++ b/resources/lib/youtube_plugin/youtube/helper/resource_manager.py
@@ -20,6 +20,7 @@ def __init__(self, context, client):
self._show_fanart = context.get_settings().get_bool(
'youtube.channel.fanart.show', True
)
+ self.new_data = {}
@staticmethod
def _list_batch(input_list, n=50):
@@ -32,7 +33,7 @@ def clear(self):
self._func_cache.clear()
self._data_cache.clear()
- def get_channels(self, ids):
+ def get_channels(self, ids, defer_cache=False):
updated = []
for channel_id in ids:
if not channel_id:
@@ -80,9 +81,7 @@ def get_channels(self, ids):
if yt_item
}
result.update(new_data)
- self._data_cache.set_items(new_data)
- self._context.log_debug('Cached data for channels:\n|{ids}|'
- .format(ids=list(new_data)))
+ self.cache_data(new_data, defer=defer_cache)
# Re-sort result to match order of requested IDs
# Will only work in Python v3.7+
@@ -95,11 +94,11 @@ def get_channels(self, ids):
return result
- def get_fanarts(self, channel_ids):
+ def get_fanarts(self, channel_ids, defer_cache=False):
if not self._show_fanart:
return {}
- result = self.get_channels(channel_ids)
+ result = self.get_channels(channel_ids, defer_cache=defer_cache)
banners = ['bannerTvMediumImageUrl', 'bannerTvLowImageUrl',
'bannerTvImageUrl', 'bannerExternalUrl']
# transform
@@ -117,7 +116,7 @@ def get_fanarts(self, channel_ids):
return result
- def get_playlists(self, ids):
+ def get_playlists(self, ids, defer_cache=False):
ids = tuple(ids)
result = self._data_cache.get_items(ids, self._data_cache.ONE_MONTH)
to_update = [id_ for id_ in ids if id_ not in result]
@@ -144,9 +143,7 @@ def get_playlists(self, ids):
if yt_item
}
result.update(new_data)
- self._data_cache.set_items(new_data)
- self._context.log_debug('Cached data for playlists:\n|{ids}|'
- .format(ids=list(new_data)))
+ self.cache_data(new_data, defer=defer_cache)
# Re-sort result to match order of requested IDs
# Will only work in Python v3.7+
@@ -159,7 +156,7 @@ def get_playlists(self, ids):
return result
- def get_playlist_items(self, ids=None, batch_id=None):
+ def get_playlist_items(self, ids=None, batch_id=None, defer_cache=False):
if not ids and not batch_id:
return None
@@ -214,10 +211,8 @@ def get_playlist_items(self, ids=None, batch_id=None):
to_update = list(new_data)
self._context.log_debug('Got items for playlists:\n|{ids}|'
.format(ids=to_update))
- self._data_cache.set_items(new_data)
result.update(new_data)
- self._context.log_debug('Cached items for playlists:\n|{ids}|'
- .format(ids=to_update))
+ self.cache_data(new_data, defer=defer_cache)
# Re-sort result to match order of requested IDs
# Will only work in Python v3.7+
@@ -230,8 +225,8 @@ def get_playlist_items(self, ids=None, batch_id=None):
return result
- def get_related_playlists(self, channel_id):
- result = self.get_channels([channel_id])
+ def get_related_playlists(self, channel_id, defer_cache=False):
+ result = self.get_channels([channel_id], defer_cache=defer_cache)
# transform
item = None
@@ -247,7 +242,11 @@ def get_related_playlists(self, channel_id):
return item.get('contentDetails', {}).get('relatedPlaylists', {})
- def get_videos(self, ids, live_details=False, suppress_errors=False):
+ def get_videos(self,
+ ids,
+ live_details=False,
+ suppress_errors=False,
+ defer_cache=False):
ids = tuple(ids)
result = self._data_cache.get_items(ids, self._data_cache.ONE_MONTH)
to_update = [id_ for id_ in ids if id_ not in result]
@@ -277,9 +276,7 @@ def get_videos(self, ids, live_details=False, suppress_errors=False):
for yt_item in batch.get('items', [])
})
result.update(new_data)
- self._data_cache.set_items(new_data)
- self._context.log_debug('Cached data for videos:\n|{ids}|'
- .format(ids=list(new_data)))
+ self.cache_data(new_data, defer=defer_cache)
# Re-sort result to match order of requested IDs
# Will only work in Python v3.7+
@@ -297,3 +294,15 @@ def get_videos(self, ids, live_details=False, suppress_errors=False):
result[video_id]['play_data'] = play_data
return result
+
+ def cache_data(self, data=None, defer=False):
+ if defer:
+ if data:
+ self.new_data.update(data)
+ return
+
+ data = data or self.new_data
+ if data:
+ self._data_cache.set_items(data)
+ self._context.log_debug('Cached data for items:\n|{ids}|'
+ .format(ids=list(data)))
diff --git a/resources/lib/youtube_plugin/youtube/helper/v3.py b/resources/lib/youtube_plugin/youtube/helper/v3.py
index d92c2c2de..447db2d4b 100644
--- a/resources/lib/youtube_plugin/youtube/helper/v3.py
+++ b/resources/lib/youtube_plugin/youtube/helper/v3.py
@@ -281,102 +281,142 @@ def _process_list_response(provider, context, json_data):
# for each video.
channel_items_dict = {}
- running = 0
resource_manager = provider.get_resource_manager(context)
- resources = [
- {
+ resources = {
+ 1: {
'fetcher': resource_manager.get_videos,
- 'args': (video_id_dict.keys(), ),
- 'kwargs': {'live_details': True, 'suppress_errors': True},
+ 'args': (video_id_dict,),
+ 'kwargs': {
+ 'live_details': True,
+ 'suppress_errors': True,
+ 'defer_cache': True,
+ },
'thread': None,
'updater': update_video_infos,
- 'upd_args': (provider, context, video_id_dict, playlist_item_id_dict, channel_items_dict),
- 'upd_kwargs': {'data': None, 'live_details': True, 'use_play_data': use_play_data},
+ 'upd_args': (
+ provider,
+ context,
+ video_id_dict,
+ playlist_item_id_dict,
+ channel_items_dict,
+ ),
+ 'upd_kwargs': {
+ 'data': None,
+ 'live_details': True,
+ 'use_play_data': use_play_data
+ },
'complete': False,
'defer': False,
},
- {
+ 2: {
'fetcher': resource_manager.get_playlists,
- 'args': (playlist_id_dict.keys(), ),
- 'kwargs': {},
+ 'args': (playlist_id_dict,),
+ 'kwargs': {'defer_cache': True},
'thread': None,
'updater': update_playlist_infos,
- 'upd_args': (provider, context, playlist_id_dict, channel_items_dict),
+ 'upd_args': (
+ provider,
+ context,
+ playlist_id_dict,
+ channel_items_dict,
+ ),
'upd_kwargs': {'data': None},
'complete': False,
'defer': False,
},
- {
+ 3: {
'fetcher': resource_manager.get_channels,
- 'args': (channel_id_dict.keys(), ),
- 'kwargs': {},
+ 'args': (channel_id_dict,),
+ 'kwargs': {'defer_cache': True},
'thread': None,
'updater': update_channel_infos,
- 'upd_args': (provider, context, channel_id_dict, subscription_id_dict, channel_items_dict),
+ 'upd_args': (
+ provider,
+ context,
+ channel_id_dict,
+ subscription_id_dict,
+ channel_items_dict,
+ ),
'upd_kwargs': {'data': None},
'complete': False,
'defer': False,
},
- {
+ 4: {
'fetcher': resource_manager.get_fanarts,
- 'args': (channel_items_dict.keys(), ),
- 'kwargs': {},
+ 'args': (channel_items_dict,),
+ 'kwargs': {'defer_cache': True},
'thread': None,
'updater': update_fanarts,
- 'upd_args': (provider, context, channel_items_dict),
+ 'upd_args': (
+ provider,
+ context,
+ channel_items_dict,
+ ),
'upd_kwargs': {'data': None},
'complete': False,
'defer': True,
},
- ]
+ 5: {
+ 'fetcher': resource_manager.cache_data,
+ 'args': (),
+ 'kwargs': {},
+ 'thread': None,
+ 'updater': None,
+ 'upd_args': (),
+ 'upd_kwargs': {},
+ 'complete': False,
+ 'defer': 4,
+ },
+ }
def _fetch(resource):
data = resource['fetcher'](
*resource['args'], **resource['kwargs']
)
- if not data:
+ if not data or not resource['updater']:
return
resource['upd_kwargs']['data'] = data
resource['updater'](*resource['upd_args'], **resource['upd_kwargs'])
- for resource in resources:
- if resource['defer']:
- running += 1
+ remaining = len(resources)
+ deferred = sum(1 for resource in resources.values() if resource['defer'])
+ iterator = iter(resources.values())
+ while remaining:
+ try:
+ resource = next(iterator)
+ except StopIteration:
+ iterator = iter(resources.values())
+ resource = next(iterator)
+
+ if resource['complete']:
continue
- if not resource['args'][0]:
+ defer = resource['defer']
+ if defer:
+ if remaining > deferred:
+ continue
+ if defer in resources and not resources[defer]['complete']:
+ continue
+ resource['defer'] = False
+
+ args = resource['args']
+ if args and not args[0]:
resource['complete'] = True
+ remaining -= 1
continue
- running += 1
- # _fetch(resource)
- thread = Thread(target=_fetch, args=(resource, ))
- thread.daemon = True
- thread.start()
- resource['thread'] = thread
-
- while running > 0:
- for resource in resources:
- if resource['complete']:
- continue
-
- thread = resource['thread']
- if thread:
- thread.join(30)
- if not thread.is_alive():
- resource['thread'] = None
- resource['complete'] = True
- running -= 1
- elif resource['defer']:
- resource['defer'] = False
- # _fetch(resource)
- thread = Thread(target=_fetch, args=(resource, ))
- thread.daemon = True
- thread.start()
- resource['thread'] = thread
- else:
- running -= 1
+ thread = resource['thread']
+ if thread:
+ thread.join(1)
+ if not thread.is_alive():
+ resource['thread'] = None
resource['complete'] = True
+ remaining -= 1
+ else:
+ thread = Thread(target=_fetch, args=(resource,))
+ thread.daemon = True
+ thread.start()
+ resource['thread'] = thread
return result
From f8812410c22d4d3a6a36c7098311bf2181b2ee1f Mon Sep 17 00:00:00 2001
From: MoojMidge <56883549+MoojMidge@users.noreply.github.com>
Date: Thu, 21 Dec 2023 04:19:20 +1100
Subject: [PATCH 116/141] Create Session as class variable in BaseRequestsClass
- Create once, close pools when not needed
---
.../lib/youtube_plugin/kodion/network/requests.py | 12 +++++++-----
1 file changed, 7 insertions(+), 5 deletions(-)
diff --git a/resources/lib/youtube_plugin/kodion/network/requests.py b/resources/lib/youtube_plugin/kodion/network/requests.py
index 939a07b3c..ced557ab2 100644
--- a/resources/lib/youtube_plugin/kodion/network/requests.py
+++ b/resources/lib/youtube_plugin/kodion/network/requests.py
@@ -25,7 +25,7 @@
class BaseRequestsClass(object):
- http_adapter = HTTPAdapter(
+ _http_adapter = HTTPAdapter(
pool_maxsize=10,
pool_block=True,
max_retries=Retry(
@@ -36,15 +36,17 @@ class BaseRequestsClass(object):
)
)
+ _session = Session()
+ _session.mount('https://', _http_adapter)
+ atexit.register(_session.close)
+
def __init__(self, exc_type=RequestException):
self._verify = _settings.verify_ssl()
self._timeout = _settings.get_timeout()
self._default_exc = exc_type
- self._session = Session()
- self._session.verify = self._verify
- self._session.mount('https://', self.http_adapter)
- atexit.register(self._session.close)
+ def __del__(self):
+ self._session.close()
def __enter__(self):
return self
From 658892745c3022829cc8701cecd2a20aded8adec Mon Sep 17 00:00:00 2001
From: MoojMidge <56883549+MoojMidge@users.noreply.github.com>
Date: Thu, 21 Dec 2023 04:21:10 +1100
Subject: [PATCH 117/141] Set _is_action in DirectoryItem.__init__
---
resources/lib/youtube_plugin/kodion/items/directory_item.py | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/resources/lib/youtube_plugin/kodion/items/directory_item.py b/resources/lib/youtube_plugin/kodion/items/directory_item.py
index 2738c0b03..8b26ae4e5 100644
--- a/resources/lib/youtube_plugin/kodion/items/directory_item.py
+++ b/resources/lib/youtube_plugin/kodion/items/directory_item.py
@@ -14,10 +14,10 @@
class DirectoryItem(BaseItem):
- def __init__(self, name, uri, image='', fanart=''):
+ def __init__(self, name, uri, image='', fanart='', action=False):
super(DirectoryItem, self).__init__(name, uri, image, fanart)
self._plot = self.get_name()
- self._is_action = False
+ self._is_action = action
self._channel_subscription_id = None
self._channel_id = None
From 421ec800ffe766af848bc9adc858940b8a7ef927 Mon Sep 17 00:00:00 2001
From: MoojMidge <56883549+MoojMidge@users.noreply.github.com>
Date: Thu, 21 Dec 2023 05:56:11 +1100
Subject: [PATCH 118/141] Add local Watch Later and History lists
- Make items.utils.to_json a method of BaseItem
- Also fix to allow handling of date/datetime objects
- Will only work in Python 3.7+
- TODO: fix date parsing for older versions of Python
- TODO: update context menu items
- TODO: fix serialisation prior to updating items
---
.../resource.language.en_au/strings.po | 8 +
.../resource.language.en_gb/strings.po | 8 +
.../resource.language.en_nz/strings.po | 8 +
.../resource.language.en_us/strings.po | 8 +
.../kodion/abstract_provider.py | 145 ++++++---
.../kodion/constants/const_paths.py | 1 +
.../kodion/context/abstract_context.py | 3 +-
.../kodion/context/xbmc/xbmc_context.py | 6 +-
.../youtube_plugin/kodion/items/__init__.py | 7 +-
.../youtube_plugin/kodion/items/base_item.py | 15 +
.../lib/youtube_plugin/kodion/items/utils.py | 59 ++--
.../kodion/sql_store/favorite_list.py | 11 +-
.../kodion/sql_store/watch_later_list.py | 15 +-
.../lib/youtube_plugin/youtube/provider.py | 276 ++++++++++++------
14 files changed, 373 insertions(+), 197 deletions(-)
diff --git a/resources/language/resource.language.en_au/strings.po b/resources/language/resource.language.en_au/strings.po
index de411ad0c..3002b35d3 100644
--- a/resources/language/resource.language.en_au/strings.po
+++ b/resources/language/resource.language.en_au/strings.po
@@ -1372,3 +1372,11 @@ msgstr ""
msgctxt "#30768"
msgid "Disable high framerate video at maximum video quality"
msgstr ""
+
+msgctxt "#30769"
+msgid "Clear Watch Later list"
+msgstr ""
+
+msgctxt "#30770"
+msgid "Are you sure you want to clear your Watch Later list?"
+msgstr ""
diff --git a/resources/language/resource.language.en_gb/strings.po b/resources/language/resource.language.en_gb/strings.po
index 831ef1a2b..fb9fd6986 100644
--- a/resources/language/resource.language.en_gb/strings.po
+++ b/resources/language/resource.language.en_gb/strings.po
@@ -1372,3 +1372,11 @@ msgstr ""
msgctxt "#30768"
msgid "Disable high framerate video at maximum video quality"
msgstr ""
+
+msgctxt "#30769"
+msgid "Clear Watch Later list"
+msgstr ""
+
+msgctxt "#30770"
+msgid "Are you sure you want to clear your Watch Later list?"
+msgstr ""
diff --git a/resources/language/resource.language.en_nz/strings.po b/resources/language/resource.language.en_nz/strings.po
index 15c435e76..34c126c5b 100644
--- a/resources/language/resource.language.en_nz/strings.po
+++ b/resources/language/resource.language.en_nz/strings.po
@@ -1368,3 +1368,11 @@ msgstr ""
msgctxt "#30768"
msgid "Disable high framerate video at maximum video quality"
msgstr ""
+
+msgctxt "#30769"
+msgid "Clear Watch Later list"
+msgstr ""
+
+msgctxt "#30770"
+msgid "Are you sure you want to clear your Watch Later list?"
+msgstr ""
diff --git a/resources/language/resource.language.en_us/strings.po b/resources/language/resource.language.en_us/strings.po
index e33ce18a0..82767d56b 100644
--- a/resources/language/resource.language.en_us/strings.po
+++ b/resources/language/resource.language.en_us/strings.po
@@ -1373,3 +1373,11 @@ msgstr ""
msgctxt "#30768"
msgid "Disable high framerate video at maximum video quality"
msgstr ""
+
+msgctxt "#30769"
+msgid "Clear Watch Later list"
+msgstr ""
+
+msgctxt "#30770"
+msgid "Are you sure you want to clear your Watch Later list?"
+msgstr ""
diff --git a/resources/lib/youtube_plugin/kodion/abstract_provider.py b/resources/lib/youtube_plugin/kodion/abstract_provider.py
index 4f543e10d..34b7c6322 100644
--- a/resources/lib/youtube_plugin/kodion/abstract_provider.py
+++ b/resources/lib/youtube_plugin/kodion/abstract_provider.py
@@ -10,7 +10,6 @@
from __future__ import absolute_import, division, unicode_literals
-import json
import re
from . import constants
@@ -20,8 +19,6 @@
DirectoryItem,
NewSearchItem,
SearchHistoryItem,
- from_json,
- to_jsons,
)
from .utils import to_unicode
@@ -38,12 +35,33 @@ def __init__(self):
# register some default paths
self.register_path(r'^/$', '_internal_root')
- self.register_path(r''.join(['^/', constants.paths.WATCH_LATER, '/(?Padd|remove|list)/?$']),
- '_internal_watch_later')
- self.register_path(r''.join(['^/', constants.paths.FAVORITES, '/(?Padd|remove|list)/?$']), '_internal_favorite')
- self.register_path(r''.join(['^/', constants.paths.SEARCH, '/(?Pinput|query|list|remove|clear|rename)/?$']),
- '_internal_search')
- self.register_path(r'(?P.*\/)extrafanart\/([\?#].+)?$', '_internal_on_extra_fanart')
+
+ self.register_path(r''.join([
+ '^/',
+ constants.paths.WATCH_LATER,
+ '/(?Padd|clear|list|remove)/?$'
+ ]), '_internal_watch_later')
+
+ self.register_path(r''.join([
+ '^/',
+ constants.paths.FAVORITES,
+ '/(?Padd|clear|list|remove)/?$'
+ ]), '_internal_favorite')
+
+ self.register_path(r''.join([
+ '^/',
+ constants.paths.SEARCH,
+ '/(?Pinput|query|list|remove|clear|rename)/?$'
+ ]), '_internal_search')
+
+ self.register_path(r''.join([
+ '^/',
+ constants.paths.HISTORY,
+ '/$'
+ ]), 'on_playback_history')
+
+ self.register_path(r'(?P.*\/)extrafanart\/([\?#].+)?$',
+ '_internal_on_extra_fanart')
"""
Test each method of this class for the appended attribute '_re_match' by the
@@ -122,70 +140,107 @@ def _internal_on_extra_fanart(self, context, re_match):
new_context = context.clone(new_path=path)
return self.on_extra_fanart(new_context, re_match)
+ def on_playback_history(self, context, re_match):
+ raise NotImplementedError()
+
def on_search(self, search_text, context, re_match):
raise NotImplementedError()
def on_root(self, context, re_match):
raise NotImplementedError()
- def on_watch_later(self, context, re_match):
- pass
-
def _internal_root(self, context, re_match):
return self.on_root(context, re_match)
@staticmethod
def _internal_favorite(context, re_match):
params = context.get_params()
-
command = re_match.group('command')
- if command == 'add':
- fav_item = from_json(params['item'])
- context.get_favorite_list().add(fav_item)
- return None
- if command == 'remove':
- fav_item = from_json(params['item'])
- context.get_favorite_list().remove(fav_item)
- context.get_ui().refresh_container()
- return None
+ if not command:
+ return False
+
if command == 'list':
- directory_items = context.get_favorite_list().get_items()
+ items = context.get_favorite_list().get_items()
- for directory_item in directory_items:
- context_menu = [(context.localize('watch_later.remove'),
- 'RunPlugin(%s)' % context.create_uri([constants.paths.FAVORITES, 'remove'],
- params={'item': to_jsons(directory_item)}))]
- directory_item.set_context_menu(context_menu)
+ for item in items:
+ context_menu = [(
+ context.localize('favorites.remove'),
+ 'RunPlugin(%s)' % context.create_uri(
+ [constants.paths.FAVORITES, 'remove'],
+ params={'item_id': item.get_id()}
+ )
+ )]
+ item.set_context_menu(context_menu)
- return directory_items
- return None
+ return items
- def _internal_watch_later(self, context, re_match):
- self.on_watch_later(context, re_match)
+ video_id = params.get('video_id')
+ if not video_id:
+ return False
- params = context.get_params()
-
- command = re_match.group('command')
if command == 'add':
- item = from_json(params['item'])
- context.get_watch_later_list().add(item)
- return None
+ item = params.get('item')
+ if item:
+ context.get_favorite_list().add(video_id, item)
+ return True
+
if command == 'remove':
- item = from_json(params['item'])
- context.get_watch_later_list().remove(item)
+ context.get_favorite_list().remove(video_id)
context.get_ui().refresh_container()
- return None
+ return True
+
+ return False
+
+ def _internal_watch_later(self, context, re_match):
+ params = context.get_params()
+ command = re_match.group('command')
+ if not command:
+ return False
+
if command == 'list':
video_items = context.get_watch_later_list().get_items()
for video_item in video_items:
- context_menu = [(context.localize('watch_later.remove'),
- 'RunPlugin(%s)' % context.create_uri([constants.paths.WATCH_LATER, 'remove'],
- params={'item': to_jsons(video_item)}))]
+ context_menu = [(
+ context.localize('watch_later.remove'),
+ 'RunPlugin(%s)' % context.create_uri(
+ [constants.paths.WATCH_LATER, 'remove'],
+ params={'item_id': video_item.get_id()}
+ )
+ ), (
+ context.localize('watch_later.clear'),
+ 'RunPlugin(%s)' % context.create_uri(
+ [constants.paths.WATCH_LATER, 'clear']
+ )
+ )]
video_item.set_context_menu(context_menu)
return video_items
- return None
+
+ if (command == 'clear' and context.get_ui().on_yes_no_input(
+ context.get_name(),
+ context.localize('watch_later.clear.confirm')
+ )):
+ context.get_watch_later_list().clear()
+ context.get_ui().refresh_container()
+ return True
+
+ video_id = params.get('video_id')
+ if not video_id:
+ return False
+
+ if command == 'add':
+ item = params.get('item')
+ if item:
+ context.get_watch_later_list().add(video_id, item)
+ return True
+
+ if command == 'remove':
+ context.get_watch_later_list().remove(video_id)
+ context.get_ui().refresh_container()
+ return True
+
+ return False
@property
def data_cache(self):
diff --git a/resources/lib/youtube_plugin/kodion/constants/const_paths.py b/resources/lib/youtube_plugin/kodion/constants/const_paths.py
index aad8c4752..b0ba36f27 100644
--- a/resources/lib/youtube_plugin/kodion/constants/const_paths.py
+++ b/resources/lib/youtube_plugin/kodion/constants/const_paths.py
@@ -14,3 +14,4 @@
SEARCH = 'kodion/search'
FAVORITES = 'kodion/favorites'
WATCH_LATER = 'kodion/watch_later'
+HISTORY = 'kodion/playback_history'
diff --git a/resources/lib/youtube_plugin/kodion/context/abstract_context.py b/resources/lib/youtube_plugin/kodion/context/abstract_context.py
index 576384437..1f3851bc5 100644
--- a/resources/lib/youtube_plugin/kodion/context/abstract_context.py
+++ b/resources/lib/youtube_plugin/kodion/context/abstract_context.py
@@ -64,7 +64,7 @@ class AbstractContext(object):
}
_STRING_PARAMS = {
'api_key',
- 'action', # deprecated
+ 'action',
'addon_id',
'channel_id',
'channel_name',
@@ -72,6 +72,7 @@ class AbstractContext(object):
'client_secret',
'event_type',
'item',
+ 'item_id',
'next_page_token',
'page_token',
'parent_id',
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 8472372a9..027ee84ab 100644
--- a/resources/lib/youtube_plugin/kodion/context/xbmc/xbmc_context.py
+++ b/resources/lib/youtube_plugin/kodion/context/xbmc/xbmc_context.py
@@ -48,8 +48,6 @@ class XbmcContext(AbstractContext):
'cache.function': 30557,
'cancel': 30615,
'channels': 30500,
- 'clear_history': 30609,
- 'clear_history_confirmation': 30610,
'client.id.incorrect': 30649,
'client.ip': 30700,
'client.ip.failed': 30701,
@@ -88,6 +86,8 @@ class XbmcContext(AbstractContext):
'go_to_channel': 30502,
'highlights': 30104,
'history': 30509,
+ 'history.clear': 30609,
+ 'history.clear.confirm': 30610,
'history.list.remove': 30572,
'history.list.remove.confirm': 30573,
'history.list.set': 30571,
@@ -230,6 +230,8 @@ class XbmcContext(AbstractContext):
'watch_later': 30107,
'watch_later.add': 30107,
'watch_later.added_to': 30713,
+ 'watch_later.clear': 30769,
+ 'watch_later.clear.confirm': 30770,
'watch_later.list.remove': 30568,
'watch_later.list.remove.confirm': 30569,
'watch_later.list.set': 30567,
diff --git a/resources/lib/youtube_plugin/kodion/items/__init__.py b/resources/lib/youtube_plugin/kodion/items/__init__.py
index 343c7acba..5cba8f9e2 100644
--- a/resources/lib/youtube_plugin/kodion/items/__init__.py
+++ b/resources/lib/youtube_plugin/kodion/items/__init__.py
@@ -10,8 +10,6 @@
from __future__ import absolute_import, division, unicode_literals
-from .utils import to_json, from_json, to_jsons
-
from .audio_item import AudioItem
from .base_item import BaseItem
from .directory_item import DirectoryItem
@@ -22,6 +20,7 @@
from .search_history_item import SearchHistoryItem
from .search_item import SearchItem
from .uri_item import UriItem
+from .utils import from_json
from .video_item import VideoItem
from .watch_later_item import WatchLaterItem
@@ -38,6 +37,4 @@
'UriItem',
'VideoItem',
'WatchLaterItem',
- 'from_json',
- 'to_json',
- 'to_jsons')
+ 'from_json',)
diff --git a/resources/lib/youtube_plugin/kodion/items/base_item.py b/resources/lib/youtube_plugin/kodion/items/base_item.py
index b648fc807..9e71bb88a 100644
--- a/resources/lib/youtube_plugin/kodion/items/base_item.py
+++ b/resources/lib/youtube_plugin/kodion/items/base_item.py
@@ -10,6 +10,7 @@
from __future__ import absolute_import, division, unicode_literals
+import json
from datetime import date, datetime
from hashlib import md5
@@ -53,6 +54,20 @@ def __str__(self):
obj_str = "------------------------------\n'%s'\nURI: %s\nImage: %s\n------------------------------" % (name, uri, image)
return obj_str
+ def to_dict(self):
+ return {'type': self.__class__.__name__, 'data': self.__dict__}
+
+ def dumps(self):
+ def _encoder(obj):
+ if isinstance(obj, (date, datetime)):
+ return {
+ '__class__': obj.__class__.__name__,
+ '__isoformat__': obj.isoformat(),
+ }
+ return json.JSONEncoder().default(obj)
+
+ return json.dumps(self.to_dict(), ensure_ascii=False, default=_encoder)
+
def get_id(self):
"""
Returns a unique id of the item.
diff --git a/resources/lib/youtube_plugin/kodion/items/utils.py b/resources/lib/youtube_plugin/kodion/items/utils.py
index a5a5971a1..7c985b61b 100644
--- a/resources/lib/youtube_plugin/kodion/items/utils.py
+++ b/resources/lib/youtube_plugin/kodion/items/utils.py
@@ -11,6 +11,7 @@
from __future__ import absolute_import, division, unicode_literals
import json
+from datetime import date, datetime
from .audio_item import AudioItem
from .directory_item import DirectoryItem
@@ -26,51 +27,33 @@
}
+def _decoder(obj):
+ date_in_isoformat = obj.get('__isoformat__')
+ if not date_in_isoformat:
+ return obj
+
+ if obj['__class__'] == 'date':
+ return date.fromisoformat(date_in_isoformat)
+ return datetime.fromisoformat(date_in_isoformat)
+
+
def from_json(json_data, *_args):
"""
- Creates a instance of the given json dump or dict.
+ Creates an instance of the given json dump or dict.
:param json_data:
:return:
"""
-
- def _from_json(_json_data):
- item_type = _json_data.get('type')
- if not item_type or item_type not in _ITEM_TYPES:
- return _json_data
-
- item = _ITEM_TYPES[item_type]()
-
- data = _json_data.get('data', {})
- for key, value in data.items():
- if hasattr(item, key):
- setattr(item, key, value)
-
- return item
-
if isinstance(json_data, str):
- json_data = json.loads(json_data)
- return _from_json(json_data)
-
-
-def to_jsons(base_item):
- return json.dumps(to_json(base_item))
-
-
-def to_json(base_item):
- """
- Convert the given @base_item to json
- :param base_item:
- :return: json string
- """
+ json_data = json.loads(json_data, object_hook=_decoder)
- def _to_json(obj):
- if isinstance(obj, dict):
- return obj.__dict__
+ item_type = json_data.get('type')
+ if not item_type or item_type not in _ITEM_TYPES:
+ return json_data
- for name, item_type in _ITEM_TYPES.items():
- if isinstance(obj, item_type):
- return {'type': name, 'data': obj.__dict__}
+ item = _ITEM_TYPES[item_type](name='', uri='')
- return obj.__dict__
+ for key, value in json_data.get('data', {}).items():
+ if hasattr(item, key):
+ setattr(item, key, value)
- return _to_json(base_item)
+ return item
diff --git a/resources/lib/youtube_plugin/kodion/sql_store/favorite_list.py b/resources/lib/youtube_plugin/kodion/sql_store/favorite_list.py
index 44e72d220..fc84cbae0 100644
--- a/resources/lib/youtube_plugin/kodion/sql_store/favorite_list.py
+++ b/resources/lib/youtube_plugin/kodion/sql_store/favorite_list.py
@@ -11,7 +11,7 @@
from __future__ import absolute_import, division, unicode_literals
from .storage import Storage
-from ..items import from_json, to_json
+from ..items import from_json
class FavoriteList(Storage):
@@ -31,9 +31,8 @@ def get_items(self):
result = self._get_by_ids(process=from_json, values_only=True)
return sorted(result, key=self._sort_item, reverse=False)
- def add(self, base_item):
- item_json_data = to_json(base_item)
- self._set(base_item.get_id(), item_json_data)
+ def add(self, item_id, item):
+ self._set(item_id, item)
- def remove(self, base_item):
- self._remove(base_item.get_id())
+ def remove(self, item_id):
+ self._remove(item_id)
diff --git a/resources/lib/youtube_plugin/kodion/sql_store/watch_later_list.py b/resources/lib/youtube_plugin/kodion/sql_store/watch_later_list.py
index 1618a5570..39274f029 100644
--- a/resources/lib/youtube_plugin/kodion/sql_store/watch_later_list.py
+++ b/resources/lib/youtube_plugin/kodion/sql_store/watch_later_list.py
@@ -10,10 +10,8 @@
from __future__ import absolute_import, division, unicode_literals
-from datetime import datetime
-
from .storage import Storage
-from ..items import to_json, from_json
+from ..items import from_json
class WatchLaterList(Storage):
@@ -29,11 +27,8 @@ def get_items(self):
result = self._get_by_ids(process=from_json, values_only=True)
return result
- def add(self, base_item):
- base_item.set_date_from_datetime(datetime.now())
-
- item_json_data = to_json(base_item)
- self._set(base_item.get_id(), item_json_data)
+ def add(self, video_id, item):
+ self._set(video_id, item)
- def remove(self, base_item):
- self._remove(base_item.get_id())
+ def remove(self, video_id):
+ self._remove(video_id)
diff --git a/resources/lib/youtube_plugin/youtube/provider.py b/resources/lib/youtube_plugin/youtube/provider.py
index 0041370fe..a405a2ca5 100644
--- a/resources/lib/youtube_plugin/youtube/provider.py
+++ b/resources/lib/youtube_plugin/youtube/provider.py
@@ -1140,15 +1140,74 @@ def show_client_ip(self, context, re_match):
context.get_ui().show_notification(context.localize('httpd.not.running'))
# noinspection PyUnusedLocal
- @RegisterProviderPath('^/playback_history/$')
def on_playback_history(self, context, re_match):
params = context.get_params()
- video_id = params.get('video_id')
action = params.get('action')
- if not video_id or not action:
- return True
+ if not action:
+ return False
+
playback_history = context.get_playback_history()
- play_data = playback_history.get_items([video_id]).get(video_id)
+
+ if action == 'list':
+ play_data = playback_history.get_items()
+ if not play_data:
+ return True
+ json_data = self.get_client(context).get_videos(play_data)
+ if not json_data:
+ return True
+ items = v3.response_to_items(self, context, json_data)
+
+ for item in items:
+ context_menu = [(
+ context.localize('remove'),
+ 'RunPlugin({0})'.format(context.create_uri(
+ [constants.paths.HISTORY],
+ params={'action': 'remove',
+ 'video_id': item.video_id}
+ ))
+ ), (
+ context.localize('mark.unwatched'),
+ 'RunPlugin({0})'.format(context.create_uri(
+ [constants.paths.HISTORY],
+ params={'action': 'mark_unwatched',
+ 'video_id': item.video_id}
+ ))
+ ), (
+ context.localize('mark.watched'),
+ 'RunPlugin({0})'.format(context.create_uri(
+ [constants.paths.HISTORY],
+ params={'action': 'mark_watched',
+ 'video_id': item.video_id}
+ ))
+ ), (
+ context.localize('history.clear'),
+ 'RunPlugin({0})'.format(context.create_uri(
+ [constants.paths.HISTORY],
+ params={'action': 'clear'}
+ ))
+ )]
+ item.set_context_menu(context_menu)
+
+ return items
+
+ if (action == 'clear' and context.get_ui().on_yes_no_input(
+ context.get_name(),
+ context.localize('history.clear.confirm')
+ )):
+ playback_history.clear()
+ context.get_ui().refresh_container()
+ return True
+
+ video_id = params.get('video_id')
+ if not video_id:
+ return False
+
+ if action == 'remove':
+ playback_history.remove(video_id)
+ context.get_ui().refresh_container()
+ return True
+
+ play_data = playback_history.get_item(video_id)
if not play_data:
play_data = {
'play_count': 0,
@@ -1190,6 +1249,7 @@ def on_root(self, context, re_match):
ui = context.get_ui()
_ = self.get_client(context) # required for self.is_logged_in()
+ logged_in = self.is_logged_in()
self.set_content_type(context, constants.content_type.FILES)
@@ -1277,95 +1337,131 @@ def on_root(self, context, re_match):
my_location_item.set_fanart(self.get_fanart(context))
result.append(my_location_item)
- # subscriptions
- if self.is_logged_in():
- # my channel
- if settings.get_bool('youtube.folder.my_channel.show', True):
- my_channel_item = DirectoryItem(localize('my_channel'),
- create_uri(['channel', 'mine']),
- image=create_path('media', 'channel.png'))
- my_channel_item.set_fanart(self.get_fanart(context))
- result.append(my_channel_item)
-
- # watch later
- watch_later_playlist_id = access_manager.get_watch_later_id()
- if settings.get_bool('youtube.folder.watch_later.show', True) and watch_later_playlist_id:
- watch_later_item = DirectoryItem(localize('watch_later'),
- create_uri(['channel', 'mine', 'playlist', watch_later_playlist_id]),
- create_path('media', 'watch_later.png'))
- watch_later_item.set_fanart(self.get_fanart(context))
+ # my channel
+ if logged_in and settings.get_bool('youtube.folder.my_channel.show', True):
+ my_channel_item = DirectoryItem(
+ localize('my_channel'),
+ create_uri(['channel', 'mine']),
+ image=create_path('media', 'channel.png'),
+ fanart=self.get_fanart(context)
+ )
+ result.append(my_channel_item)
+
+ # watch later
+ if settings.get_bool('youtube.folder.watch_later.show', True):
+ playlist_id = logged_in and access_manager.get_watch_later_id()
+ if playlist_id and playlist_id != 'HL':
+ watch_later_item = DirectoryItem(
+ localize('watch_later'),
+ create_uri(['channel', 'mine', 'playlist', playlist_id]),
+ image=create_path('media', 'watch_later.png'),
+ fanart=self.get_fanart(context)
+ )
context_menu = []
- yt_context_menu.append_play_all_from_playlist(context_menu, context, watch_later_playlist_id)
+ yt_context_menu.append_play_all_from_playlist(context_menu,
+ context,
+ playlist_id)
watch_later_item.set_context_menu(context_menu)
result.append(watch_later_item)
+ else:
+ watch_history_item = DirectoryItem(
+ localize('watch_later'),
+ create_uri([constants.paths.WATCH_LATER, 'list']),
+ image=create_path('media', 'watch_later.png'),
+ fanart=self.get_fanart(context)
+ )
+ result.append(watch_history_item)
+
+ # liked videos
+ if logged_in and settings.get_bool('youtube.folder.liked_videos.show', True):
+ resource_manager = self.get_resource_manager(context)
+ playlists = resource_manager.get_related_playlists(channel_id='mine')
+ if 'likes' in playlists:
+ liked_videos_item = DirectoryItem(
+ localize('video.liked'),
+ create_uri(['channel', 'mine', 'playlist', playlists['likes']]),
+ image=create_path('media', 'likes.png'),
+ fanart=self.get_fanart(context)
+ )
+ context_menu = []
+ yt_context_menu.append_play_all_from_playlist(context_menu, context, playlists['likes'])
+ liked_videos_item.set_context_menu(context_menu)
+ result.append(liked_videos_item)
+
+ # disliked videos
+ if logged_in and settings.get_bool('youtube.folder.disliked_videos.show', True):
+ disliked_videos_item = DirectoryItem(
+ localize('video.disliked'),
+ create_uri(['special', 'disliked_videos']),
+ image=create_path('media', 'dislikes.png'),
+ fanart=self.get_fanart(context)
+ )
+ result.append(disliked_videos_item)
+
+ # history
+ if settings.get_bool('youtube.folder.history.show', False):
+ playlist_id = logged_in and access_manager.get_watch_history_id()
+ if playlist_id and playlist_id != 'HL':
+ watch_history_item = DirectoryItem(
+ localize('history'),
+ create_uri(['channel', 'mine', 'playlist', playlist_id]),
+ image=create_path('media', 'history.png'),
+ fanart=self.get_fanart(context)
+ )
+ context_menu = []
+ yt_context_menu.append_play_all_from_playlist(context_menu,
+ context,
+ playlist_id)
+ watch_history_item.set_context_menu(context_menu)
+ result.append(watch_history_item)
+ elif settings.use_local_history():
+ watch_history_item = DirectoryItem(
+ localize('history'),
+ create_uri([constants.paths.HISTORY], params={'action': 'list'}),
+ image=create_path('media', 'history.png'),
+ fanart=self.get_fanart(context)
+ )
+ result.append(watch_history_item)
+
+ # (my) playlists
+ if logged_in and settings.get_bool('youtube.folder.playlists.show', True):
+ playlists_item = DirectoryItem(
+ localize('playlists'),
+ create_uri(['channel', 'mine', 'playlists']),
+ image=create_path('media', 'playlist.png'),
+ fanart=self.get_fanart(context)
+ )
+ result.append(playlists_item)
+
+ # saved playlists
+ if logged_in and settings.get_bool('youtube.folder.saved.playlists.show', True):
+ playlists_item = DirectoryItem(
+ localize('saved.playlists'),
+ create_uri(['special', 'saved_playlists']),
+ image=create_path('media', 'playlist.png'),
+ fanart=self.get_fanart(context)
+ )
+ result.append(playlists_item)
- # liked videos
- if settings.get_bool('youtube.folder.liked_videos.show', True):
- resource_manager = self.get_resource_manager(context)
- playlists = resource_manager.get_related_playlists(channel_id='mine')
- if 'likes' in playlists:
- liked_videos_item = DirectoryItem(localize('video.liked'),
- create_uri(['channel', 'mine', 'playlist', playlists['likes']]),
- create_path('media', 'likes.png'))
- liked_videos_item.set_fanart(self.get_fanart(context))
- context_menu = []
- yt_context_menu.append_play_all_from_playlist(context_menu, context, playlists['likes'])
- liked_videos_item.set_context_menu(context_menu)
- result.append(liked_videos_item)
-
- # disliked videos
- if settings.get_bool('youtube.folder.disliked_videos.show', True):
- disliked_videos_item = DirectoryItem(localize('video.disliked'),
- create_uri(['special', 'disliked_videos']),
- create_path('media', 'dislikes.png'))
- disliked_videos_item.set_fanart(self.get_fanart(context))
- result.append(disliked_videos_item)
-
- # history
- if settings.get_bool('youtube.folder.history.show', False):
- watch_history_playlist_id = access_manager.get_watch_history_id()
- if watch_history_playlist_id != 'HL':
- watch_history_item = DirectoryItem(localize('history'),
- create_uri(['channel', 'mine', 'playlist', watch_history_playlist_id]),
- create_path('media', 'history.png'))
- watch_history_item.set_fanart(self.get_fanart(context))
- context_menu = []
- yt_context_menu.append_play_all_from_playlist(context_menu, context, watch_history_playlist_id)
- watch_history_item.set_context_menu(context_menu)
-
- result.append(watch_history_item)
-
- # (my) playlists
- if settings.get_bool('youtube.folder.playlists.show', True):
- playlists_item = DirectoryItem(localize('playlists'),
- create_uri(['channel', 'mine', 'playlists']),
- create_path('media', 'playlist.png'))
- playlists_item.set_fanart(self.get_fanart(context))
- result.append(playlists_item)
-
- # saved playlists
- if settings.get_bool('youtube.folder.saved.playlists.show', True):
- playlists_item = DirectoryItem(localize('saved.playlists'),
- create_uri(['special', 'saved_playlists']),
- create_path('media', 'playlist.png'))
- playlists_item.set_fanart(self.get_fanart(context))
- result.append(playlists_item)
-
- # subscriptions
- if settings.get_bool('youtube.folder.subscriptions.show', True):
- subscriptions_item = DirectoryItem(localize('subscriptions'),
- create_uri(['subscriptions', 'list']),
- image=create_path('media', 'channels.png'))
- subscriptions_item.set_fanart(self.get_fanart(context))
- result.append(subscriptions_item)
-
- # browse channels
- if settings.get_bool('youtube.folder.browse_channels.show', True):
- browse_channels_item = DirectoryItem(localize('browse_channels'),
- create_uri(['special', 'browse_channels']),
- image=create_path('media', 'browse_channels.png'))
- browse_channels_item.set_fanart(self.get_fanart(context))
- result.append(browse_channels_item)
+ # subscriptions
+ if logged_in and settings.get_bool('youtube.folder.subscriptions.show', True):
+ subscriptions_item = DirectoryItem(
+ localize('subscriptions'),
+ create_uri(['subscriptions', 'list']),
+ image=create_path('media', 'channels.png'),
+ fanart=self.get_fanart(context)
+ )
+ result.append(subscriptions_item)
+
+ # browse channels
+ if logged_in and settings.get_bool('youtube.folder.browse_channels.show', True):
+ browse_channels_item = DirectoryItem(
+ localize('browse_channels'),
+ create_uri(['special', 'browse_channels']),
+ image=create_path('media', 'browse_channels.png'),
+ fanart=self.get_fanart(context)
+ )
+ result.append(browse_channels_item)
# completed live events
if settings.get_bool('youtube.folder.completed.live.show', True):
From d19b11c66acac471b64c42769442aa12eaf747bf Mon Sep 17 00:00:00 2001
From: MoojMidge <56883549+MoojMidge@users.noreply.github.com>
Date: Sun, 24 Dec 2023 09:06:49 +1100
Subject: [PATCH 119/141] Initial consolidation of context menu methods
- Context menu items are evaluated at the time that they are created
- This creates problems when the underlying item data changes
- The context menu item can then contain out of date data
- e.g. updated channel fanart or video play data
- TODO: register menu items, but don't call them until ListItem is added to UI
---
.../kodion/abstract_provider.py | 31 +-
.../kodion/context/xbmc/xbmc_context.py | 7 +-
.../youtube_plugin/kodion/items/__init__.py | 4 +-
.../youtube_plugin/kodion/items/menu_items.py | 491 ++++++++++++++++++
.../kodion/items/search_history_item.py | 12 +-
.../youtube_plugin/youtube/helper/utils.py | 266 +++++-----
.../lib/youtube_plugin/youtube/helper/v3.py | 10 +-
.../youtube/helper/yt_context_menu.py | 216 --------
.../lib/youtube_plugin/youtube/provider.py | 79 ++-
9 files changed, 695 insertions(+), 421 deletions(-)
create mode 100644 resources/lib/youtube_plugin/kodion/items/menu_items.py
delete mode 100644 resources/lib/youtube_plugin/youtube/helper/yt_context_menu.py
diff --git a/resources/lib/youtube_plugin/kodion/abstract_provider.py b/resources/lib/youtube_plugin/kodion/abstract_provider.py
index 34b7c6322..57c5fdffa 100644
--- a/resources/lib/youtube_plugin/kodion/abstract_provider.py
+++ b/resources/lib/youtube_plugin/kodion/abstract_provider.py
@@ -19,6 +19,7 @@
DirectoryItem,
NewSearchItem,
SearchHistoryItem,
+ menu_items
)
from .utils import to_unicode
@@ -163,13 +164,11 @@ def _internal_favorite(context, re_match):
items = context.get_favorite_list().get_items()
for item in items:
- context_menu = [(
- context.localize('favorites.remove'),
- 'RunPlugin(%s)' % context.create_uri(
- [constants.paths.FAVORITES, 'remove'],
- params={'item_id': item.get_id()}
- )
- )]
+ context_menu = [
+ menu_items.favorites_remove(
+ context, item.video_id
+ ),
+ ]
item.set_context_menu(context_menu)
return items
@@ -201,18 +200,14 @@ def _internal_watch_later(self, context, re_match):
video_items = context.get_watch_later_list().get_items()
for video_item in video_items:
- context_menu = [(
- context.localize('watch_later.remove'),
- 'RunPlugin(%s)' % context.create_uri(
- [constants.paths.WATCH_LATER, 'remove'],
- params={'item_id': video_item.get_id()}
- )
- ), (
- context.localize('watch_later.clear'),
- 'RunPlugin(%s)' % context.create_uri(
- [constants.paths.WATCH_LATER, 'clear']
+ context_menu = [
+ menu_items.watch_later_local_remove(
+ context, video_item.video_id
+ ),
+ menu_items.watch_later_local_clear(
+ context
)
- )]
+ ]
video_item.set_context_menu(context_menu)
return video_items
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 027ee84ab..20b69bf80 100644
--- a/resources/lib/youtube_plugin/kodion/context/xbmc/xbmc_context.py
+++ b/resources/lib/youtube_plugin/kodion/context/xbmc/xbmc_context.py
@@ -92,6 +92,10 @@ class XbmcContext(AbstractContext):
'history.list.remove.confirm': 30573,
'history.list.set': 30571,
'history.list.set.confirm': 30574,
+ 'history.mark.unwatched': 30669,
+ 'history.mark.watched': 30670,
+ 'history.remove': 30108,
+ 'history.reset.resume_point': 30674,
'httpd.not.running': 30699,
'inputstreamhelper.is_installed': 30625,
'isa.enable.confirm': 30579,
@@ -102,8 +106,6 @@ class XbmcContext(AbstractContext):
'live': 30539,
'live.completed': 30647,
'live.upcoming': 30646,
- 'mark.unwatched': 30669,
- 'mark.watched': 30670,
'must_be_signed_in': 30616,
'my_channel': 30507,
'my_location': 30654,
@@ -142,7 +144,6 @@ class XbmcContext(AbstractContext):
'renamed': 30667,
'requires.krypton': 30624,
'reset.access_manager.confirm': 30581,
- 'reset.resume_point': 30674,
'retry': 30612,
'saved.playlists': 30611,
'search': 30102,
diff --git a/resources/lib/youtube_plugin/kodion/items/__init__.py b/resources/lib/youtube_plugin/kodion/items/__init__.py
index 5cba8f9e2..948f32655 100644
--- a/resources/lib/youtube_plugin/kodion/items/__init__.py
+++ b/resources/lib/youtube_plugin/kodion/items/__init__.py
@@ -10,6 +10,7 @@
from __future__ import absolute_import, division, unicode_literals
+from . import menu_items
from .audio_item import AudioItem
from .base_item import BaseItem
from .directory_item import DirectoryItem
@@ -37,4 +38,5 @@
'UriItem',
'VideoItem',
'WatchLaterItem',
- 'from_json',)
+ 'from_json',
+ 'menu_items',)
diff --git a/resources/lib/youtube_plugin/kodion/items/menu_items.py b/resources/lib/youtube_plugin/kodion/items/menu_items.py
new file mode 100644
index 000000000..ba51775bd
--- /dev/null
+++ b/resources/lib/youtube_plugin/kodion/items/menu_items.py
@@ -0,0 +1,491 @@
+# -*- coding: utf-8 -*-
+"""
+
+ Copyright (C) 2014-2016 bromix (plugin.video.youtube)
+ Copyright (C) 2016-2018 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 ..constants import paths
+
+
+def more_for_video(context, video_id, logged_in=False, refresh_container=False):
+ return (
+ context.localize('video.more'),
+ 'RunPlugin({0})'.format(context.create_uri(
+ ['video', 'more'],
+ {
+ 'video_id': video_id,
+ 'logged_in': logged_in,
+ 'refresh_container': refresh_container,
+ }
+ ))
+ )
+
+
+def content_from_description(context, video_id):
+ return (
+ context.localize('video.description.links'),
+ 'RunPlugin({0})'.format(context.create_uri(
+ ['special', 'description_links'],
+ {
+ 'video_id': video_id,
+ }
+ ))
+ )
+
+
+def play_with(context):
+ return (
+ context.localize('video.play.with'),
+ 'Action(SwitchPlayer)'
+ )
+
+
+def queue_video(context):
+ return (
+ context.localize('video.queue'),
+ 'Action(Queue)'
+ )
+
+
+def play_all_from_playlist(context, playlist_id, video_id=''):
+ if video_id:
+ return (
+ context.localize('playlist.play.from_here'),
+ 'RunPlugin({0})'.format(context.create_uri(
+ ['play'],
+ {
+ 'playlist_id': playlist_id,
+ 'video_id': video_id,
+ 'play': True,
+ }
+ ))
+ )
+ return (
+ context.localize('playlist.play.all'),
+ 'RunPlugin({0})'.format(context.create_uri(
+ ['play'],
+ {
+ 'playlist_id': playlist_id,
+ 'play': True,
+ }
+ ))
+ )
+
+
+def add_video_to_playlist(context, video_id):
+ return (
+ context.localize('video.add_to_playlist'),
+ 'RunPlugin({0})'.format(context.create_uri(
+ ['playlist', 'select', 'playlist'],
+ {
+ 'video_id': video_id,
+ }
+ ))
+ )
+
+
+def remove_video_from_playlist(context, playlist_id, video_id, video_name):
+ return (
+ context.localize('remove'),
+ 'RunPlugin({0})'.format(context.create_uri(
+ ['playlist', 'remove', 'video'],
+ {
+ 'playlist_id': playlist_id,
+ 'video_id': video_id,
+ 'video_name': video_name,
+ }
+ ))
+ )
+
+
+def rename_playlist(context, playlist_id, playlist_name):
+ return (
+ context.localize('rename'),
+ 'RunPlugin({0})'.format(context.create_uri(
+ ['playlist', 'rename', 'playlist'],
+ {
+ 'playlist_id': playlist_id,
+ 'playlist_name': playlist_name
+ }
+ ))
+ )
+
+
+def delete_playlist(context, playlist_id, playlist_name):
+ return (
+ context.localize('delete'),
+ 'RunPlugin({0})'.format(context.create_uri(
+ ['playlist', 'remove', 'playlist'],
+ {
+ 'playlist_id': playlist_id,
+ 'playlist_name': playlist_name
+ }
+ ))
+ )
+
+
+def remove_as_watchlater(context, playlist_id, playlist_name):
+ return (
+ context.localize('watch_later.list.remove'),
+ 'RunPlugin({0})'.format(context.create_uri(
+ ['playlist', 'remove', 'watchlater'],
+ {
+ 'playlist_id': playlist_id,
+ 'playlist_name': playlist_name
+ }
+ ))
+ )
+
+
+def set_as_watchlater(context, playlist_id, playlist_name):
+ return (
+ context.localize('watch_later.list.set'),
+ 'RunPlugin({0})'.format(context.create_uri(
+ ['playlist', 'set', 'watchlater'],
+ {
+ 'playlist_id': playlist_id,
+ 'playlist_name': playlist_name
+ }
+ ))
+ )
+
+
+def remove_as_history(context, playlist_id, playlist_name):
+ return (
+ context.localize('history.list.remove'),
+ 'RunPlugin({0})'.format(context.create_uri(
+ ['playlist', 'remove', 'history'],
+ {
+ 'playlist_id': playlist_id,
+ 'playlist_name': playlist_name
+ }
+ ))
+ )
+
+
+def set_as_history(context, playlist_id, playlist_name):
+ return (
+ context.localize('history.list.set'),
+ 'RunPlugin({0})'.format(context.create_uri(
+ ['playlist', 'set', 'history'],
+ {
+ 'playlist_id': playlist_id,
+ 'playlist_name': playlist_name
+ }
+ ))
+ )
+
+
+def remove_my_subscriptions_filter(context, channel_name):
+ return (
+ context.localize('my_subscriptions.filter.remove'),
+ 'RunPlugin({0})'.format(context.create_uri(
+ ['my_subscriptions', 'filter'],
+ {
+ 'channel_name': channel_name,
+ 'action': 'remove'
+ }
+ ))
+ )
+
+
+def add_my_subscriptions_filter(context, channel_name):
+ return (
+ context.localize('my_subscriptions.filter.add'),
+ 'RunPlugin({0})'.format(context.create_uri(
+ ['my_subscriptions', 'filter'],
+ {
+ 'channel_name': channel_name,
+ 'action': 'add',
+ }
+ ))
+ )
+
+
+def rate_video(context, video_id, refresh_container=False):
+ return (
+ context.localize('video.rate'),
+ 'RunPlugin({0})'.format(context.create_uri(
+ ['video', 'rate'],
+ {
+ 'video_id': video_id,
+ 'refresh_container': refresh_container,
+ }
+ ))
+ )
+
+
+def watch_later_add(context, playlist_id, video_id):
+ return (
+ context.localize('watch_later.add'),
+ 'RunPlugin({0})'.format(context.create_uri(
+ ['playlist', 'add', 'video'],
+ {
+ 'playlist_id': playlist_id,
+ 'video_id': video_id,
+ }
+ ))
+ )
+
+
+def watch_later_local_add(context, item):
+ return (
+ context.localize('watch_later.add'),
+ 'RunPlugin({0})'.format(context.create_uri(
+ [paths.WATCH_LATER, 'add'],
+ {
+ 'video_id': item.video_id,
+ 'item': item.dumps(),
+ }
+ ))
+ )
+
+
+def watch_later_local_remove(context, video_id):
+ return (
+ context.localize('watch_later.remove'),
+ 'RunPlugin({0})'.format(context.create_uri(
+ [paths.WATCH_LATER, 'remove'],
+ {
+ 'video_id': video_id,
+ }
+ ))
+ )
+
+
+def watch_later_local_clear(context):
+ return (
+ context.localize('watch_later.clear'),
+ 'RunPlugin({0})'.format(context.create_uri(
+ [paths.WATCH_LATER, 'clear']
+ ))
+ )
+
+
+def go_to_channel(context, channel_id, channel_name):
+ return (
+ context.localize('go_to_channel') % context.get_ui().bold(channel_name),
+ 'Container.Update({0})'.format(context.create_uri(
+ ['channel', channel_id]
+ ))
+ )
+
+
+def related_videos(context, video_id):
+ return (
+ context.localize('related_videos'),
+ 'Container.Update({0})'.format(context.create_uri(
+ ['special', 'related_videos'],
+ {
+ 'video_id': video_id,
+ }
+ ))
+ )
+
+
+def refresh(context):
+ return (
+ context.localize('refresh'),
+ 'Container.Refresh'
+ )
+
+
+def subscribe_to_channel(context, channel_id, channel_name=''):
+ if not channel_name:
+ return (
+ context.localize('subscribe'),
+ 'RunPlugin({0})'.format(context.create_uri(
+ ['subscriptions', 'add'],
+ {
+ 'subscription_id': channel_id,
+ }
+ ))
+ )
+ return (
+ context.localize('subscribe_to') % context.get_ui().bold(channel_name),
+ 'RunPlugin({0})'.format(context.create_uri(
+ ['subscriptions', 'add'],
+ {
+ 'subscription_id': channel_id,
+ }
+ ))
+ )
+
+
+def unsubscribe_from_channel(context, channel_id):
+ return (
+ context.localize('unsubscribe'),
+ 'RunPlugin({0})'.format(context.create_uri(
+ ['subscriptions', 'remove'],
+ {
+ 'subscription_id': channel_id,
+ }
+ ))
+ )
+
+
+def play_with_subtitles(context, video_id):
+ return (
+ context.localize('video.play.with_subtitles'),
+ 'RunPlugin({0})'.format(context.create_uri(
+ ['play'],
+ {
+ 'video_id': video_id,
+ 'prompt_for_subtitles': True,
+ }
+ ))
+ )
+
+
+def play_audio_only(context, video_id):
+ return (
+ context.localize('video.play.audio_only'),
+ 'RunPlugin({0})'.format(context.create_uri(
+ ['play'],
+ {
+ 'video_id': video_id,
+ 'audio_only': True,
+ }
+ ))
+ )
+
+
+def play_ask_for_quality(context, video_id):
+ return (
+ context.localize('video.play.ask_for_quality'),
+ 'RunPlugin({0})'.format(context.create_uri(
+ ['play'],
+ {
+ 'video_id': video_id,
+ 'ask_for_quality': True,
+ }
+ ))
+ )
+
+
+def history_remove(context, video_id):
+ return (
+ context.localize('history.remove'),
+ 'RunPlugin({0})'.format(context.create_uri(
+ [paths.HISTORY],
+ {
+ 'action': 'remove',
+ 'video_id': video_id
+ }
+ ))
+ )
+
+
+def history_clear(context):
+ return (
+ context.localize('history.clear'),
+ 'RunPlugin({0})'.format(context.create_uri(
+ [paths.HISTORY],
+ {
+ 'action': 'clear'
+ }
+ ))
+ )
+
+
+def history_mark_watched(context, video_id):
+ return (
+ context.localize('history.mark.watched'),
+ 'RunPlugin({0})'.format(context.create_uri(
+ [paths.HISTORY],
+ {
+ 'video_id': video_id,
+ 'action': 'mark_watched',
+ }
+ ))
+ )
+
+
+def history_mark_unwatched(context, video_id):
+ return (
+ context.localize('history.mark.unwatched'),
+ 'RunPlugin({0})'.format(context.create_uri(
+ [paths.HISTORY],
+ {
+ 'video_id': video_id,
+ 'action': 'mark_unwatched',
+ }
+ ))
+ )
+
+
+def history_reset_resume(context, video_id):
+ return (
+ context.localize('history.reset.resume_point'),
+ 'RunPlugin({0})'.format(context.create_uri(
+ [paths.HISTORY],
+ {
+ 'video_id': video_id,
+ 'action': 'reset_resume',
+ }
+ ))
+ )
+
+
+def favorites_add(context, item):
+ return (
+ context.localize('favorites.add'),
+ 'RunPlugin({0})'.format(context.create_uri(
+ [paths.FAVORITES, 'add'],
+ {
+ 'video_id': item.video_id,
+ 'item': item.dumps(),
+ }
+ ))
+ )
+
+
+def favorites_remove(context, video_id):
+ return (
+ context.localize('favorites.remove'),
+ 'RunPlugin({0})'.format(context.create_uri(
+ [paths.FAVORITES, 'remove'],
+ {
+ 'vide_id': video_id,
+ }
+ ))
+ )
+
+
+def search_remove(context, query):
+ return (
+ context.localize('search.remove'),
+ 'RunPlugin({0})'.format(context.create_uri(
+ [paths.SEARCH, 'remove'],
+ {
+ 'q': query,
+ }
+ ))
+ )
+
+
+def search_rename(context, query):
+ return (
+ context.localize('search.rename'),
+ 'RunPlugin({0})'.format(context.create_uri(
+ [paths.SEARCH, 'rename'],
+ {
+ 'q': query,
+ }
+ ))
+ )
+
+
+def search_clear(context):
+ return (
+ context.localize('search.clear'),
+ 'RunPlugin({0})'.format(context.create_uri(
+ [paths.SEARCH, 'clear']
+ ))
+ )
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 e53d020aa..b2aeab883 100644
--- a/resources/lib/youtube_plugin/kodion/items/search_history_item.py
+++ b/resources/lib/youtube_plugin/kodion/items/search_history_item.py
@@ -10,6 +10,7 @@
from __future__ import absolute_import, division, unicode_literals
+from . import menu_items
from .directory_item import DirectoryItem
from ..constants.const_paths import SEARCH
@@ -29,10 +30,9 @@ def __init__(self, context, query, image=None, fanart=None, location=False):
else:
self.set_fanart(context.get_fanart())
- context_menu = [(context.localize('search.remove'),
- 'RunPlugin(%s)' % context.create_uri([SEARCH, 'remove'], params={'q': query})),
- (context.localize('search.rename'),
- 'RunPlugin(%s)' % context.create_uri([SEARCH, 'rename'], params={'q': query})),
- (context.localize('search.clear'),
- 'RunPlugin(%s)' % context.create_uri([SEARCH, 'clear']))]
+ context_menu = [
+ menu_items.search_remove(context, query),
+ menu_items.search_rename(context, query),
+ menu_items.search_clear(context),
+ ]
self.set_context_menu(context_menu)
diff --git a/resources/lib/youtube_plugin/youtube/helper/utils.py b/resources/lib/youtube_plugin/youtube/helper/utils.py
index dc979a27e..311d3719b 100644
--- a/resources/lib/youtube_plugin/youtube/helper/utils.py
+++ b/resources/lib/youtube_plugin/youtube/helper/utils.py
@@ -14,8 +14,7 @@
import time
from math import log10
-from ..helper import yt_context_menu
-from ...kodion.items import DirectoryItem
+from ...kodion.items import DirectoryItem, menu_items
from ...kodion.utils import (
create_path,
datetime_parser,
@@ -36,8 +35,6 @@
'viewCount': 'lightblue',
}
-__RE_HISTORY_MATCH = re.compile(r'^/special/watch_history_tv/$')
-
__RE_PLAYLIST_MATCH = re.compile(
r'^(/channel/(?P[^/]+))/playlist/(?P[^/]+)/$'
)
@@ -164,18 +161,25 @@ def update_channel_infos(provider, context, channel_id_dict,
if subscription_id_dict is None:
subscription_id_dict = {}
- filter_list = []
+ settings = context.get_settings()
logged_in = provider.is_logged_in()
path = context.get_path()
+
+ filter_list = None
if path == '/subscriptions/list/':
- filter_string = context.get_settings().get_string(
- 'youtube.filter.my_subscriptions_filtered.list', ''
- )
- filter_string = filter_string.replace(', ', ',')
- filter_list = filter_string.split(',')
- filter_list = [x.lower() for x in filter_list]
+ in_subscription_list = True
+ if settings.get_bool('youtube.folder.my_subscriptions_filtered.show',
+ False):
+ filter_string = settings.get_string(
+ 'youtube.filter.my_subscriptions_filtered.list', ''
+ )
+ filter_string = filter_string.replace(', ', ',')
+ filter_list = filter_string.split(',')
+ filter_list = [x.lower() for x in filter_list]
+ else:
+ in_subscription_list = False
- thumb_size = context.get_settings().use_thumbnail_size
+ thumb_size = settings.use_thumbnail_size
banners = [
'bannerTvMediumImageUrl',
'bannerTvLowImageUrl',
@@ -197,30 +201,39 @@ def update_channel_infos(provider, context, channel_id_dict,
# - update context menu
context_menu = []
+
# -- unsubscribe from channel
subscription_id = subscription_id_dict.get(channel_id, '')
if subscription_id:
channel_item.set_channel_subscription_id(subscription_id)
- yt_context_menu.append_unsubscribe_from_channel(
- context_menu, context, subscription_id
+ context_menu.append(
+ menu_items.unsubscribe_from_channel(
+ context, subscription_id
+ )
)
+
# -- subscribe to the channel
- if logged_in and path != '/subscriptions/list/':
- yt_context_menu.append_subscribe_to_channel(
- context_menu, context, channel_id
+ if logged_in and not in_subscription_list:
+ context_menu.append(
+ menu_items.subscribe_to_channel(
+ context, channel_id
+ )
)
- if path == '/subscriptions/list/':
+ # add/remove from filter list
+ if in_subscription_list and filter_list is not None:
channel = title.lower().replace(',', '')
- if channel in filter_list:
- yt_context_menu.append_remove_my_subscriptions_filter(
- context_menu, context, title
- )
- else:
- yt_context_menu.append_add_my_subscriptions_filter(
- context_menu, context, title
+ context_menu.append(
+ menu_items.remove_my_subscriptions_filter(
+ context, title
+ ) if channel in filter_list else
+ menu_items.add_my_subscriptions_filter(
+ context, title
)
- channel_item.set_context_menu(context_menu)
+ )
+
+ if context_menu:
+ channel_item.set_context_menu(context_menu)
fanart_images = yt_item.get('brandingSettings', {}).get('image', {})
for banner in banners:
@@ -273,49 +286,49 @@ def update_playlist_infos(provider, context, playlist_id_dict,
if path == '/channel/mine/playlists/':
channel_id = 'mine'
channel_name = snippet.get('channelTitle', '')
- context_menu = []
+
# play all videos of the playlist
- yt_context_menu.append_play_all_from_playlist(
- context_menu, context, playlist_id
- )
+ context_menu = [
+ menu_items.play_all_from_playlist(
+ context, playlist_id
+ )
+ ]
if logged_in:
if channel_id != 'mine':
# subscribe to the channel via the playlist item
- yt_context_menu.append_subscribe_to_channel(
- context_menu, context, channel_id, channel_name
+ context_menu.append(
+ menu_items.subscribe_to_channel(
+ context, channel_id, channel_name
+ )
)
else:
- # remove my playlist
- yt_context_menu.append_delete_playlist(
- context_menu, context, playlist_id, title
- )
-
- # rename playlist
- yt_context_menu.append_rename_playlist(
- context_menu, context, playlist_id, title
- )
-
- # remove as my custom watch later playlist
- if playlist_id == custom_watch_later_id:
- yt_context_menu.append_remove_as_watchlater(
- context_menu, context, playlist_id, title
- )
- # set as my custom watch later playlist
- else:
- yt_context_menu.append_set_as_watchlater(
- context_menu, context, playlist_id, title
- )
- # remove as custom history playlist
- if playlist_id == custom_history_id:
- yt_context_menu.append_remove_as_history(
- context_menu, context, playlist_id, title
- )
- # set as custom history playlist
- else:
- yt_context_menu.append_set_as_history(
- context_menu, context, playlist_id, title
- )
+ context_menu.extend((
+ # remove my playlist
+ menu_items.delete_playlist(
+ context, playlist_id, title
+ ),
+ # rename playlist
+ menu_items.rename_playlist(
+ context, playlist_id, title
+ ),
+ # remove as my custom watch later playlist
+ menu_items.remove_as_watchlater(
+ context, playlist_id, title
+ ) if playlist_id == custom_watch_later_id else
+ # set as my custom watch later playlist
+ menu_items.set_as_watchlater(
+ context, playlist_id, title
+ ),
+ # remove as custom history playlist
+ menu_items.remove_as_history(
+ context, playlist_id, title
+ ) if playlist_id == custom_history_id else
+ # set as custom history playlist
+ menu_items.set_as_history(
+ context, playlist_id, title
+ ),
+ ))
if context_menu:
playlist_item.set_context_menu(context_menu)
@@ -538,25 +551,22 @@ def update_video_infos(provider, context, video_id_dict,
image = ''.join([image, '?ct=', thumb_stamp])
video_item.set_image(image)
- # set fanart
- video_item.set_fanart(provider.get_fanart(context))
-
# update channel mapping
channel_id = snippet.get('channelId', '')
+ video_item.set_subscription_id(channel_id)
if channel_id and channel_items_dict is not None:
if channel_id not in channel_items_dict:
channel_items_dict[channel_id] = []
channel_items_dict[channel_id].append(video_item)
- context_menu = []
+ context_menu = [
+ # Refresh
+ menu_items.refresh(context),
+ # Queue Video
+ menu_items.queue_video(context),
+ ]
replace_context_menu = False
- # Refresh
- yt_context_menu.append_refresh(context_menu, context)
-
- # Queue Video
- yt_context_menu.append_queue_video(context_menu, context)
-
"""
Play all videos of the playlist.
@@ -570,22 +580,26 @@ def update_video_infos(provider, context, video_id_dict,
playlist_id = playlist_match.group('playlist_id')
playlist_channel_id = playlist_match.group('channel_id')
- yt_context_menu.append_play_all_from_playlist(
- context_menu, context, playlist_id, video_id
- )
- yt_context_menu.append_play_all_from_playlist(
- context_menu, context, playlist_id
- )
+ context_menu.extend((
+ menu_items.play_all_from_playlist(
+ context, playlist_id, video_id
+ ),
+ menu_items.play_all_from_playlist(
+ context, playlist_id
+ )
+ ))
# 'play with...' (external player)
if alternate_player:
- yt_context_menu.append_play_with(context_menu, context)
+ context_menu.append(menu_items.play_with(context))
if logged_in:
# add 'Watch Later' only if we are not in my 'Watch Later' list
- if wl_playlist_id and wl_playlist_id != playlist_id:
- yt_context_menu.append_watch_later(
- context_menu, context, wl_playlist_id, video_id
+ if wl_playlist_id and playlist_id and wl_playlist_id != playlist_id:
+ context_menu.append(
+ menu_items.watch_later_add(
+ context, wl_playlist_id, video_id
+ )
)
# provide 'remove' for videos in my playlists
@@ -596,72 +610,74 @@ def update_video_infos(provider, context, video_id_dict,
playlist_item_id = playlist_item_id_dict[video_id]
video_item.set_playlist_id(playlist_id)
video_item.set_playlist_item_id(playlist_item_id)
- context_menu.append((
- context.localize('remove'),
- 'RunPlugin(%s)' % context.create_uri(
- ['playlist', 'remove', 'video'],
- {'playlist_id': playlist_id,
- 'video_id': playlist_item_id,
- 'video_name': video_item.get_name()}
+ context_menu.append(
+ menu_items.remove_video_from_playlist(
+ context, playlist_id, video_id, video_item.get_name()
)
- ))
-
- if __RE_HISTORY_MATCH.match(path):
- yt_context_menu.append_clear_watch_history(
- context_menu, context
)
+ else:
+ context_menu.append(
+ menu_items.watch_later_local_add(
+ context, video_item
+ )
+ )
# got to [CHANNEL] only if we are not directly in the channel
if (channel_id and channel_name and
create_path('channel', channel_id) != path):
video_item.set_channel_id(channel_id)
- yt_context_menu.append_go_to_channel(
- context_menu, context, channel_id, channel_name
+ context_menu.append(
+ menu_items.go_to_channel(
+ context, channel_id, channel_name
+ )
)
if logged_in:
# subscribe to the channel of the video
- video_item.set_subscription_id(channel_id)
- yt_context_menu.append_subscribe_to_channel(
- context_menu, context, channel_id, channel_name
+ context_menu.append(
+ menu_items.subscribe_to_channel(
+ context, channel_id, channel_name
+ )
)
if not video_item.live and play_data:
- if not play_data.get('play_count'):
- yt_context_menu.append_mark_watched(
- context_menu, context, video_id
- )
- else:
- yt_context_menu.append_mark_unwatched(
- context_menu, context, video_id
+ context_menu.append(
+ menu_items.history_mark_unwatched(
+ context, video_id
+ ) if 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):
- yt_context_menu.append_reset_resume_point(
- context_menu, context, video_id
+ context_menu.append(
+ menu_items.history_reset_resume(
+ context, video_id
+ )
)
# more...
refresh_container = (path.startswith('/channel/mine/playlist/LL')
or path == '/special/disliked_videos/')
- yt_context_menu.append_more_for_video(
- context_menu, context, video_id,
- is_logged_in=logged_in,
- refresh_container=refresh_container
- )
-
- if not video_item.live:
- yt_context_menu.append_play_with_subtitles(
- context_menu, context, video_id
- )
- yt_context_menu.append_play_audio_only(
- context_menu, context, video_id
- )
-
- yt_context_menu.append_play_ask_for_quality(
- context_menu, context, video_id
- )
+ context_menu.extend((
+ menu_items.more_for_video(
+ context,
+ video_id,
+ logged_in=logged_in,
+ refresh_container=refresh_container,
+ ),
+ menu_items.play_with_subtitles(
+ context, video_id
+ ),
+ menu_items.play_audio_only(
+ context, video_id
+ ),
+ menu_items.play_ask_for_quality(
+ context, video_id
+ ),
+ ))
if context_menu:
video_item.set_context_menu(
diff --git a/resources/lib/youtube_plugin/youtube/helper/v3.py b/resources/lib/youtube_plugin/youtube/helper/v3.py
index 447db2d4b..345a85ca7 100644
--- a/resources/lib/youtube_plugin/youtube/helper/v3.py
+++ b/resources/lib/youtube_plugin/youtube/helper/v3.py
@@ -21,9 +21,8 @@
update_playlist_infos,
update_video_infos,
)
-from ..helper import yt_context_menu
from ...kodion import KodionException
-from ...kodion.items import DirectoryItem, NextPageItem, VideoItem
+from ...kodion.items import DirectoryItem, NextPageItem, VideoItem, menu_items
def _process_list_response(provider, context, json_data):
@@ -87,8 +86,11 @@ def _process_list_response(provider, context, json_data):
# if logged in => provide subscribing to the channel
if provider.is_logged_in():
- context_menu = []
- yt_context_menu.append_subscribe_to_channel(context_menu, context, channel_id)
+ context_menu = [
+ menu_items.subscribe_to_channel(
+ context, channel_id
+ ),
+ ]
channel_item.set_context_menu(context_menu)
result.append(channel_item)
channel_id_dict[channel_id] = channel_item
diff --git a/resources/lib/youtube_plugin/youtube/helper/yt_context_menu.py b/resources/lib/youtube_plugin/youtube/helper/yt_context_menu.py
deleted file mode 100644
index 3159ebc49..000000000
--- a/resources/lib/youtube_plugin/youtube/helper/yt_context_menu.py
+++ /dev/null
@@ -1,216 +0,0 @@
-# -*- coding: utf-8 -*-
-"""
-
- Copyright (C) 2014-2016 bromix (plugin.video.youtube)
- Copyright (C) 2016-2018 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 kodion
-
-
-def append_more_for_video(context_menu, context, video_id, is_logged_in=False, refresh_container=False):
- _is_logged_in = '0'
- if is_logged_in:
- _is_logged_in = '1'
-
- _refresh_container = '0'
- if refresh_container:
- _refresh_container = '1'
-
- context_menu.append((context.localize('video.more'),
- 'RunPlugin(%s)' % context.create_uri(['video', 'more'],
- {'video_id': video_id,
- 'logged_in': _is_logged_in,
- 'refresh_container': _refresh_container})))
-
-
-def append_content_from_description(context_menu, context, video_id):
- context_menu.append((context.localize('video.description.links'),
- 'Container.Update(%s)' % context.create_uri(['special', 'description_links'],
- {'video_id': video_id})))
-
-
-def append_play_with(context_menu, context):
- context_menu.append((context.localize('video.play.with'), 'Action(SwitchPlayer)'))
-
-
-def append_queue_video(context_menu, context):
- context_menu.append((context.localize('video.queue'), 'Action(Queue)'))
-
-
-def append_play_all_from_playlist(context_menu, context, playlist_id, video_id=''):
- if video_id:
- context_menu.append((context.localize('playlist.play.from_here'),
- 'RunPlugin(%s)' % context.create_uri(['play'],
- {'playlist_id': playlist_id,
- 'video_id': video_id,
- 'play': '1'})))
- else:
- context_menu.append((context.localize('playlist.play.all'),
- 'RunPlugin(%s)' % context.create_uri(['play'],
- {'playlist_id': playlist_id,
- 'play': '1'})))
-
-
-def append_add_video_to_playlist(context_menu, context, video_id):
- context_menu.append((context.localize('video.add_to_playlist'),
- 'RunPlugin(%s)' % context.create_uri(['playlist', 'select', 'playlist'],
- {'video_id': video_id})))
-
-
-def append_rename_playlist(context_menu, context, playlist_id, playlist_name):
- context_menu.append((context.localize('rename'),
- 'RunPlugin(%s)' % context.create_uri(['playlist', 'rename', 'playlist'],
- {'playlist_id': playlist_id,
- 'playlist_name': playlist_name})))
-
-
-def append_delete_playlist(context_menu, context, playlist_id, playlist_name):
- context_menu.append((context.localize('delete'),
- 'RunPlugin(%s)' % context.create_uri(['playlist', 'remove', 'playlist'],
- {'playlist_id': playlist_id,
- 'playlist_name': playlist_name})))
-
-
-def append_remove_as_watchlater(context_menu, context, playlist_id, playlist_name):
- context_menu.append((context.localize('watch_later.list.remove'),
- 'RunPlugin(%s)' % context.create_uri(['playlist', 'remove', 'watchlater'],
- {'playlist_id': playlist_id,
- 'playlist_name': playlist_name})))
-
-
-def append_set_as_watchlater(context_menu, context, playlist_id, playlist_name):
- context_menu.append((context.localize('watch_later.list.set'),
- 'RunPlugin(%s)' % context.create_uri(['playlist', 'set', 'watchlater'],
- {'playlist_id': playlist_id,
- 'playlist_name': playlist_name})))
-
-
-def append_remove_as_history(context_menu, context, playlist_id, playlist_name):
- context_menu.append((context.localize('history.list.remove'),
- 'RunPlugin(%s)' % context.create_uri(['playlist', 'remove', 'history'],
- {'playlist_id': playlist_id,
- 'playlist_name': playlist_name})))
-
-
-def append_set_as_history(context_menu, context, playlist_id, playlist_name):
- context_menu.append((context.localize('history.list.set'),
- 'RunPlugin(%s)' % context.create_uri(['playlist', 'set', 'history'],
- {'playlist_id': playlist_id,
- 'playlist_name': playlist_name})))
-
-
-def append_remove_my_subscriptions_filter(context_menu, context, channel_name):
- if context.get_settings().get_bool('youtube.folder.my_subscriptions_filtered.show', False):
- context_menu.append((context.localize('my_subscriptions.filter.remove'),
- 'RunPlugin(%s)' % context.create_uri(['my_subscriptions', 'filter'],
- {'channel_name': channel_name,
- 'action': 'remove'})))
-
-
-def append_add_my_subscriptions_filter(context_menu, context, channel_name):
- if context.get_settings().get_bool('youtube.folder.my_subscriptions_filtered.show', False):
- context_menu.append((context.localize('my_subscriptions.filter.add'),
- 'RunPlugin(%s)' % context.create_uri(['my_subscriptions', 'filter'],
- {'channel_name': channel_name,
- 'action': 'add'})))
-
-
-def append_rate_video(context_menu, context, video_id, refresh_container=False):
- refresh_container = '1' if refresh_container else '0'
- context_menu.append((context.localize('video.rate'),
- 'RunPlugin(%s)' % context.create_uri(['video', 'rate'],
- {'video_id': video_id,
- 'refresh_container': refresh_container})))
-
-
-def append_watch_later(context_menu, context, playlist_id, video_id):
- playlist_path = kodion.utils.create_path('channel', 'mine', 'playlist', playlist_id)
- if playlist_id and playlist_path != context.get_path():
- context_menu.append((context.localize('watch_later'),
- 'RunPlugin(%s)' % context.create_uri(['playlist', 'add', 'video'],
- {'playlist_id': playlist_id, 'video_id': video_id})))
-
-
-def append_go_to_channel(context_menu, context, channel_id, channel_name):
- text = context.localize('go_to_channel') % context.get_ui().bold(channel_name)
- context_menu.append((text, 'Container.Update(%s)' % context.create_uri(['channel', channel_id])))
-
-
-def append_related_videos(context_menu, context, video_id):
- context_menu.append((context.localize('related_videos'),
- 'Container.Update(%s)' % context.create_uri(['special', 'related_videos'],
- {'video_id': video_id})))
-
-
-def append_clear_watch_history(context_menu, context):
- context_menu.append((context.localize('clear_history'),
- 'Container.Update(%s)' % context.create_uri(['history', 'clear'])))
-
-
-def append_refresh(context_menu, context):
- context_menu.append((context.localize('refresh'), 'Container.Refresh'))
-
-
-def append_subscribe_to_channel(context_menu, context, channel_id, channel_name=''):
- if channel_name:
- text = context.localize('subscribe_to') % context.get_ui().bold(channel_name)
- context_menu.append(
- (text, 'RunPlugin(%s)' % context.create_uri(['subscriptions', 'add'], {'subscription_id': channel_id})))
- else:
- context_menu.append((context.localize('subscribe'),
- 'RunPlugin(%s)' % context.create_uri(['subscriptions', 'add'],
- {'subscription_id': channel_id})))
-
-
-def append_unsubscribe_from_channel(context_menu, context, channel_id):
- context_menu.append((context.localize('unsubscribe'),
- 'RunPlugin(%s)' % context.create_uri(['subscriptions', 'remove'],
- {'subscription_id': channel_id})))
-
-
-def append_mark_watched(context_menu, context, video_id):
- context_menu.append((context.localize('mark.watched'),
- 'RunPlugin(%s)' % context.create_uri(['playback_history'],
- {'video_id': video_id,
- 'action': 'mark_watched'})))
-
-
-def append_mark_unwatched(context_menu, context, video_id):
- context_menu.append((context.localize('mark.unwatched'),
- 'RunPlugin(%s)' % context.create_uri(['playback_history'],
- {'video_id': video_id,
- 'action': 'mark_unwatched'})))
-
-
-def append_reset_resume_point(context_menu, context, video_id):
- context_menu.append((context.localize('reset.resume_point'),
- 'RunPlugin(%s)' % context.create_uri(['playback_history'],
- {'video_id': video_id,
- 'action': 'reset_resume'})))
-
-
-def append_play_with_subtitles(context_menu, context, video_id):
- context_menu.append((context.localize('video.play.with_subtitles'),
- 'RunPlugin(%s)' % context.create_uri(['play'],
- {'video_id': video_id,
- 'prompt_for_subtitles': '1'})))
-
-
-def append_play_audio_only(context_menu, context, video_id):
- context_menu.append((context.localize('video.play.audio_only'),
- 'RunPlugin(%s)' % context.create_uri(['play'],
- {'video_id': video_id,
- 'audio_only': '1'})))
-
-
-def append_play_ask_for_quality(context_menu, context, video_id):
- context_menu.append((context.localize('video.play.ask_for_quality'),
- 'RunPlugin(%s)' % context.create_uri(['play'],
- {'video_id': video_id,
- 'ask_for_quality': '1'})))
diff --git a/resources/lib/youtube_plugin/youtube/provider.py b/resources/lib/youtube_plugin/youtube/provider.py
index a405a2ca5..d406c3739 100644
--- a/resources/lib/youtube_plugin/youtube/provider.py
+++ b/resources/lib/youtube_plugin/youtube/provider.py
@@ -23,7 +23,6 @@
UrlResolver,
UrlToItemConverter,
v3,
- yt_context_menu,
yt_login,
yt_old_actions,
yt_play,
@@ -36,7 +35,7 @@
from .youtube_exceptions import InvalidGrant, LoginException
from ..kodion import (AbstractProvider, RegisterProviderPath, constants)
from ..kodion.compatibility import xbmcaddon, xbmcvfs
-from ..kodion.items import DirectoryItem, NewSearchItem, SearchItem
+from ..kodion.items import DirectoryItem, NewSearchItem, SearchItem, menu_items
from ..kodion.network import get_client_ip_address, is_httpd_live
from ..kodion.utils import find_video_id, strip_html_from_text
@@ -653,14 +652,6 @@ def _on_yt_specials(self, context, re_match):
category = re_match.group('category')
return yt_specials.process(category, self, context)
- # noinspection PyUnusedLocal
- @RegisterProviderPath('^/history/clear/$')
- def _on_yt_clear_history(self, context, re_match):
- if context.get_ui().on_yes_no_input(context.get_name(), context.localize('clear_history_confirmation')):
- json_data = self.get_client(context).clear_watch_history()
- if 'error' not in json_data:
- context.get_ui().show_notification(context.localize('succeeded'))
-
@RegisterProviderPath('^/users/(?P[^/]+)/$')
def _on_users(self, context, re_match):
action = re_match.group('action')
@@ -1158,34 +1149,21 @@ def on_playback_history(self, context, re_match):
items = v3.response_to_items(self, context, json_data)
for item in items:
- context_menu = [(
- context.localize('remove'),
- 'RunPlugin({0})'.format(context.create_uri(
- [constants.paths.HISTORY],
- params={'action': 'remove',
- 'video_id': item.video_id}
- ))
- ), (
- context.localize('mark.unwatched'),
- 'RunPlugin({0})'.format(context.create_uri(
- [constants.paths.HISTORY],
- params={'action': 'mark_unwatched',
- 'video_id': item.video_id}
- ))
- ), (
- context.localize('mark.watched'),
- 'RunPlugin({0})'.format(context.create_uri(
- [constants.paths.HISTORY],
- params={'action': 'mark_watched',
- 'video_id': item.video_id}
- ))
- ), (
- context.localize('history.clear'),
- 'RunPlugin({0})'.format(context.create_uri(
- [constants.paths.HISTORY],
- params={'action': 'clear'}
- ))
- )]
+ video_id = item.video_id
+ context_menu = [
+ menu_items.history_remove(
+ context, video_id
+ ),
+ menu_items.history_mark_unwatched(
+ context, video_id
+ ) if play_data[video_id]['play_count'] else
+ menu_items.history_mark_watched(
+ context, video_id
+ ),
+ menu_items.history_clear(
+ context
+ ),
+ ]
item.set_context_menu(context_menu)
return items
@@ -1357,10 +1335,11 @@ def on_root(self, context, re_match):
image=create_path('media', 'watch_later.png'),
fanart=self.get_fanart(context)
)
- context_menu = []
- yt_context_menu.append_play_all_from_playlist(context_menu,
- context,
- playlist_id)
+ context_menu = [
+ menu_items.play_all_from_playlist(
+ context, playlist_id
+ )
+ ]
watch_later_item.set_context_menu(context_menu)
result.append(watch_later_item)
else:
@@ -1383,8 +1362,11 @@ def on_root(self, context, re_match):
image=create_path('media', 'likes.png'),
fanart=self.get_fanart(context)
)
- context_menu = []
- yt_context_menu.append_play_all_from_playlist(context_menu, context, playlists['likes'])
+ context_menu = [
+ menu_items.play_all_from_playlist(
+ context, playlists['likes']
+ )
+ ]
liked_videos_item.set_context_menu(context_menu)
result.append(liked_videos_item)
@@ -1408,10 +1390,11 @@ def on_root(self, context, re_match):
image=create_path('media', 'history.png'),
fanart=self.get_fanart(context)
)
- context_menu = []
- yt_context_menu.append_play_all_from_playlist(context_menu,
- context,
- playlist_id)
+ context_menu = [
+ menu_items.play_all_from_playlist(
+ context, playlist_id
+ )
+ ]
watch_history_item.set_context_menu(context_menu)
result.append(watch_history_item)
elif settings.use_local_history():
From 45ce733a59f84e5825488e1bd2e9c4a02953cfc7 Mon Sep 17 00:00:00 2001
From: MoojMidge <56883549+MoojMidge@users.noreply.github.com>
Date: Sun, 24 Dec 2023 09:14:05 +1100
Subject: [PATCH 120/141] Provide stack trace in error log messages
---
.../lib/youtube_plugin/kodion/network/requests.py | 5 ++---
.../kodion/plugin/xbmc/xbmc_runner.py | 4 ++--
.../lib/youtube_plugin/kodion/sql_store/storage.py | 8 ++++----
.../lib/youtube_plugin/youtube/helper/video_info.py | 13 +++++++------
.../lib/youtube_plugin/youtube/helper/yt_play.py | 6 ++++--
5 files changed, 19 insertions(+), 17 deletions(-)
diff --git a/resources/lib/youtube_plugin/kodion/network/requests.py b/resources/lib/youtube_plugin/kodion/network/requests.py
index ced557ab2..a8bc4a7b9 100644
--- a/resources/lib/youtube_plugin/kodion/network/requests.py
+++ b/resources/lib/youtube_plugin/kodion/network/requests.py
@@ -10,7 +10,7 @@
from __future__ import absolute_import, division, unicode_literals
import atexit
-from traceback import format_exc, format_stack
+from traceback import format_stack
from requests import Session
from requests.adapters import HTTPAdapter, Retry
@@ -101,7 +101,6 @@ def request(self, url, method='GET',
except (RequestException, self._default_exc) as exc:
response_text = exc.response and exc.response.text
stack_trace = format_stack()
- exc_tb = format_exc()
error_details = {'exc': exc}
if error_hook:
@@ -147,7 +146,7 @@ def request(self, url, method='GET',
)
log_error('\n'.join([part for part in [
- error_title, error_info, response_text, stack_trace, exc_tb
+ error_title, error_info, response_text, stack_trace
] if part]))
if raise_exc:
diff --git a/resources/lib/youtube_plugin/kodion/plugin/xbmc/xbmc_runner.py b/resources/lib/youtube_plugin/kodion/plugin/xbmc/xbmc_runner.py
index 4b5cf9f19..3d5282dc9 100644
--- a/resources/lib/youtube_plugin/kodion/plugin/xbmc/xbmc_runner.py
+++ b/resources/lib/youtube_plugin/kodion/plugin/xbmc/xbmc_runner.py
@@ -10,7 +10,7 @@
from __future__ import absolute_import, division, unicode_literals
-from traceback import format_exc
+from traceback import format_stack
from ..abstract_provider_runner import AbstractProviderRunner
from ...compatibility import xbmcgui, xbmcplugin
@@ -51,7 +51,7 @@ def run(self, provider, context):
except KodionException as exc:
if provider.handle_exception(context, exc):
context.log_error('XbmcRunner.run - {exc}:\n{details}'.format(
- exc=exc, details=format_exc()
+ exc=exc, details=''.join(format_stack())
))
xbmcgui.Dialog().ok("Error in ContentProvider", exc.__str__())
xbmcplugin.endOfDirectory(self.handle, succeeded=False)
diff --git a/resources/lib/youtube_plugin/kodion/sql_store/storage.py b/resources/lib/youtube_plugin/kodion/sql_store/storage.py
index 0d990b709..46c7c2ba9 100644
--- a/resources/lib/youtube_plugin/kodion/sql_store/storage.py
+++ b/resources/lib/youtube_plugin/kodion/sql_store/storage.py
@@ -15,7 +15,7 @@
import sqlite3
import time
from datetime import datetime
-from traceback import format_exc
+from traceback import format_stack
from ..logger import log_error
from ..utils.datetime_parser import since_epoch
@@ -176,7 +176,7 @@ def _open(self):
isolation_level=None)
except sqlite3.OperationalError as exc:
log_error('SQLStorage._execute - {exc}:\n{details}'.format(
- exc=exc, details=format_exc()
+ exc=exc, details=''.join(format_stack())
))
return False
@@ -247,12 +247,12 @@ def _execute(cursor, query, values=None, many=False):
return cursor.execute(query, values)
except sqlite3.OperationalError as exc:
log_error('SQLStorage._execute - {exc}:\n{details}'.format(
- exc=exc, details=format_exc()
+ exc=exc, details=''.join(format_stack())
))
time.sleep(0.1)
except sqlite3.Error as exc:
log_error('SQLStorage._execute - {exc}:\n{details}'.format(
- exc=exc, details=format_exc()
+ exc=exc, details=''.join(format_stack())
))
return []
return []
diff --git a/resources/lib/youtube_plugin/youtube/helper/video_info.py b/resources/lib/youtube_plugin/youtube/helper/video_info.py
index c24d34546..b494e20e0 100644
--- a/resources/lib/youtube_plugin/youtube/helper/video_info.py
+++ b/resources/lib/youtube_plugin/youtube/helper/video_info.py
@@ -13,7 +13,7 @@
import json
import random
import re
-from traceback import format_exc
+from traceback import format_stack
from .ratebypass import ratebypass
from .signature.cipher import Cipher
@@ -931,12 +931,13 @@ def _process_signature_cipher(self, stream_map):
try:
signature = self._cipher.get_signature(encrypted_signature)
except Exception as exc:
- self._context.log_debug('{0}: {1}\n{2}'.format(
- exc, encrypted_signature, format_exc()
+ self._context.log_error('VideoInfo._process_signature_cipher - '
+ 'failed to extract URL from |{sig}|\n'
+ '{exc}:\n{details}'.format(
+ sig=encrypted_signature,
+ exc=exc,
+ details=''.join(format_stack())
))
- self._context.log_error(
- 'Failed to extract URL from signatureCipher'
- )
return None
self._data_cache.set_item(encrypted_signature, {'sig': signature})
diff --git a/resources/lib/youtube_plugin/youtube/helper/yt_play.py b/resources/lib/youtube_plugin/youtube/helper/yt_play.py
index 484d6857e..16963ce3f 100644
--- a/resources/lib/youtube_plugin/youtube/helper/yt_play.py
+++ b/resources/lib/youtube_plugin/youtube/helper/yt_play.py
@@ -12,7 +12,7 @@
import json
import random
-from traceback import format_exc
+from traceback import format_stack
from ..helper import utils, v3
from ..youtube_exceptions import YouTubeException
@@ -47,8 +47,10 @@ def play_video(provider, context):
try:
video_streams = client.get_video_streams(context, video_id)
except YouTubeException as exc:
+ context.log_error('yt_play.play_video - {exc}:\n{details}'.format(
+ exc=exc, details=''.join(format_stack())
+ ))
ui.show_notification(message=exc.get_message())
- context.log_error(format_exc())
return False
if not video_streams:
From 61e0a859321ff1a43415370e99cc9e4fc0a8892a Mon Sep 17 00:00:00 2001
From: MoojMidge <56883549+MoojMidge@users.noreply.github.com>
Date: Sun, 24 Dec 2023 09:19:18 +1100
Subject: [PATCH 121/141] Allow for proper inheritance of request exceptions
- Also tidy up imports after 860defd
---
.../youtube_plugin/kodion/network/requests.py | 11 ++++++---
.../youtube/client/login_client.py | 23 +++++++++----------
.../youtube/client/request_client.py | 9 +++++++-
.../youtube/helper/yt_playlist.py | 1 -
.../youtube_plugin/youtube/helper/yt_video.py | 1 -
5 files changed, 27 insertions(+), 18 deletions(-)
diff --git a/resources/lib/youtube_plugin/kodion/network/requests.py b/resources/lib/youtube_plugin/kodion/network/requests.py
index a8bc4a7b9..c5585b664 100644
--- a/resources/lib/youtube_plugin/kodion/network/requests.py
+++ b/resources/lib/youtube_plugin/kodion/network/requests.py
@@ -40,10 +40,15 @@ class BaseRequestsClass(object):
_session.mount('https://', _http_adapter)
atexit.register(_session.close)
- def __init__(self, exc_type=RequestException):
+ def __init__(self, exc_type=None):
self._verify = _settings.verify_ssl()
self._timeout = _settings.get_timeout()
- self._default_exc = exc_type
+ if isinstance(exc_type, tuple):
+ self._default_exc = (RequestException,) + exc_type
+ elif exc_type:
+ self._default_exc = (RequestException, exc_type)
+ else:
+ self._default_exc = RequestException
def __del__(self):
self._session.close()
@@ -98,7 +103,7 @@ def request(self, url, method='GET',
else:
response.raise_for_status()
- except (RequestException, self._default_exc) as exc:
+ except self._default_exc as exc:
response_text = exc.response and exc.response.text
stack_trace = format_stack()
error_details = {'exc': exc}
diff --git a/resources/lib/youtube_plugin/youtube/client/login_client.py b/resources/lib/youtube_plugin/youtube/client/login_client.py
index a1f00d46d..f6a70d0ea 100644
--- a/resources/lib/youtube_plugin/youtube/client/login_client.py
+++ b/resources/lib/youtube_plugin/youtube/client/login_client.py
@@ -23,7 +23,6 @@
InvalidGrant,
InvalidJSON,
LoginException,
- YouTubeException,
)
from ...kodion.compatibility import parse_qsl
from ...kodion.logger import log_debug
@@ -87,9 +86,9 @@ def _response_hook(**kwargs):
try:
json_data = response.json()
if 'error' in json_data:
- raise YouTubeException('"error" in response JSON data',
- json_data=json_data,
- response=response)
+ raise LoginException('"error" in response JSON data',
+ json_data=json_data,
+ response=response)
except ValueError as exc:
raise InvalidJSON(exc, response=response)
response.raise_for_status()
@@ -139,8 +138,8 @@ def revoke(self, refresh_token):
method='POST',
data=post_data,
headers=headers,
- response_hook=self._response_hook,
- error_hook=self._error_hook,
+ response_hook=LoginClient._response_hook,
+ error_hook=LoginClient._error_hook,
error_title='Logout Failed',
error_info='Revoke failed: {exc}',
raise_exc=True)
@@ -179,8 +178,8 @@ def refresh_token(self, refresh_token, client_id='', client_secret=''):
method='POST',
data=post_data,
headers=headers,
- response_hook=self._response_hook,
- error_hook=self._error_hook,
+ response_hook=LoginClient._response_hook,
+ error_hook=LoginClient._error_hook,
error_title='Login Failed',
error_info=('Refresh token failed'
' {client}: {{exc}}'
@@ -227,8 +226,8 @@ def request_access_token(self, code, client_id='', client_secret=''):
method='POST',
data=post_data,
headers=headers,
- response_hook=self._response_hook,
- error_hook=self._error_hook,
+ response_hook=LoginClient._response_hook,
+ error_hook=LoginClient._error_hook,
error_title='Login Failed: Unknown response',
error_info=('Access token request failed'
' {client}: {{exc}}'
@@ -263,8 +262,8 @@ def request_device_and_user_code(self, client_id=''):
method='POST',
data=post_data,
headers=headers,
- response_hook=self._response_hook,
- error_hook=self._error_hook,
+ response_hook=LoginClient._response_hook,
+ error_hook=LoginClient._error_hook,
error_title='Login Failed: Unknown response',
error_info=('Device/user code request failed'
' {client}: {{exc}}'
diff --git a/resources/lib/youtube_plugin/youtube/client/request_client.py b/resources/lib/youtube_plugin/youtube/client/request_client.py
index 045e35006..3fd740386 100644
--- a/resources/lib/youtube_plugin/youtube/client/request_client.py
+++ b/resources/lib/youtube_plugin/youtube/client/request_client.py
@@ -258,8 +258,15 @@ class YouTubeRequestClient(BaseRequestsClass):
},
}
- def __init__(self, exc_type=YouTubeException):
+ def __init__(self, exc_type=None):
+ if isinstance(exc_type, tuple):
+ exc_type = (YouTubeException,) + exc_type
+ elif exc_type:
+ exc_type = (YouTubeException, exc_type)
+ else:
+ exc_type = YouTubeException
super(YouTubeRequestClient, self).__init__(exc_type=exc_type)
+
self._access_token = None
self.video_id = None
diff --git a/resources/lib/youtube_plugin/youtube/helper/yt_playlist.py b/resources/lib/youtube_plugin/youtube/helper/yt_playlist.py
index b5fbacca6..936d859d8 100644
--- a/resources/lib/youtube_plugin/youtube/helper/yt_playlist.py
+++ b/resources/lib/youtube_plugin/youtube/helper/yt_playlist.py
@@ -10,7 +10,6 @@
from __future__ import absolute_import, division, unicode_literals
-from ..helper import v3
from ...kodion import KodionException
from ...kodion.utils import find_video_id
diff --git a/resources/lib/youtube_plugin/youtube/helper/yt_video.py b/resources/lib/youtube_plugin/youtube/helper/yt_video.py
index 9ce0f8523..aa6086e92 100644
--- a/resources/lib/youtube_plugin/youtube/helper/yt_video.py
+++ b/resources/lib/youtube_plugin/youtube/helper/yt_video.py
@@ -10,7 +10,6 @@
from __future__ import absolute_import, division, unicode_literals
-from ..helper import v3
from ...kodion import KodionException
from ...kodion.utils import find_video_id
From 6805638e448862e02f85b45bc0277d857e87963f Mon Sep 17 00:00:00 2001
From: MoojMidge <56883549+MoojMidge@users.noreply.github.com>
Date: Sun, 24 Dec 2023 09:42:25 +1100
Subject: [PATCH 122/141] Create new Context without default plugin_id value
---
resources/lib/youtube_authentication.py | 18 ++++++++----------
.../kodion/constants/__init__.py | 10 +++++++++-
.../kodion/context/xbmc/xbmc_context.py | 14 +++++++++-----
resources/lib/youtube_plugin/kodion/runner.py | 2 +-
resources/lib/youtube_plugin/kodion/service.py | 2 +-
.../youtube/client/__config__.py | 2 +-
.../youtube_plugin/youtube/client/youtube.py | 2 +-
resources/lib/youtube_registration.py | 2 +-
resources/lib/youtube_requests.py | 4 ++--
resources/lib/youtube_resolver.py | 4 ++--
10 files changed, 35 insertions(+), 25 deletions(-)
diff --git a/resources/lib/youtube_authentication.py b/resources/lib/youtube_authentication.py
index fe463b867..6c7f0739c 100644
--- a/resources/lib/youtube_authentication.py
+++ b/resources/lib/youtube_authentication.py
@@ -10,6 +10,7 @@
from __future__ import absolute_import, division, unicode_literals
from youtube_plugin.youtube.provider import Provider
+from youtube_plugin.kodion.constants import ADDON_ID
from youtube_plugin.kodion.context import Context
from youtube_plugin.youtube.helper import yt_login
@@ -27,8 +28,7 @@ def __add_new_developer(addon_id):
:param addon_id: id of the add-on being added
:return:
"""
- params = {'addon_id': addon_id}
- context = Context(params=params, plugin_id='plugin.video.youtube')
+ context = Context(params={'addon_id': addon_id})
access_manager = context.get_access_manager()
developers = access_manager.get_developers()
@@ -45,14 +45,13 @@ def __auth(addon_id, mode=SIGN_IN):
:param mode: SIGN_IN or SIGN_OUT
:return: addon provider, context and client
"""
- if not addon_id or addon_id == 'plugin.video.youtube':
- context = Context(plugin_id='plugin.video.youtube')
+ if not addon_id or addon_id == ADDON_ID:
+ context = Context()
context.log_error('Developer authentication: |%s| Invalid addon_id' % addon_id)
return
__add_new_developer(addon_id)
- params = {'addon_id': addon_id}
provider = Provider()
- context = Context(params=params, plugin_id='plugin.video.youtube')
+ context = Context(params={'addon_id': addon_id})
_ = provider.get_client(context=context) # NOQA
logged_in = provider.is_logged_in()
@@ -156,12 +155,11 @@ def reset_access_tokens(addon_id):
:param addon_id: id of the add-on having it's access tokens reset
:return:
"""
- if not addon_id or addon_id == 'plugin.video.youtube':
- context = Context(plugin_id='plugin.video.youtube')
+ if not addon_id or addon_id == ADDON_ID:
+ context = Context()
context.log_error('Developer reset access tokens: |%s| Invalid addon_id' % addon_id)
return
- params = {'addon_id': addon_id}
- context = Context(params=params, plugin_id='plugin.video.youtube')
+ context = Context(params={'addon_id': addon_id})
access_manager = context.get_access_manager()
access_manager.update_dev_access_token(addon_id, access_token='', refresh_token='')
diff --git a/resources/lib/youtube_plugin/kodion/constants/__init__.py b/resources/lib/youtube_plugin/kodion/constants/__init__.py
index 91e90d688..9a297be63 100644
--- a/resources/lib/youtube_plugin/kodion/constants/__init__.py
+++ b/resources/lib/youtube_plugin/kodion/constants/__init__.py
@@ -16,4 +16,12 @@
from . import const_paths as paths
-__all__ = ['setting', 'sort_method', 'content_type', 'paths']
+ADDON_ID = 'plugin.video.youtube'
+
+__all__ = (
+ 'ADDON_ID',
+ 'content_type',
+ 'paths',
+ 'setting',
+ 'sort_method',
+)
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 20b69bf80..d84843a6d 100644
--- a/resources/lib/youtube_plugin/kodion/context/xbmc/xbmc_context.py
+++ b/resources/lib/youtube_plugin/kodion/context/xbmc/xbmc_context.py
@@ -26,6 +26,7 @@
xbmcplugin,
xbmcvfs,
)
+from ...constants import ADDON_ID
from ...player.xbmc.xbmc_player import XbmcPlayer
from ...player.xbmc.xbmc_playlist import XbmcPlaylist
from ...settings.xbmc.xbmc_plugin_settings import XbmcPluginSettings
@@ -255,7 +256,7 @@ def __init__(self, path='/', params=None, plugin_name='', plugin_id='', override
if plugin_id:
self._addon = xbmcaddon.Addon(id=plugin_id)
else:
- self._addon = xbmcaddon.Addon(id='plugin.video.youtube')
+ self._addon = xbmcaddon.Addon(id=ADDON_ID)
"""
I don't know what xbmc/kodi is doing with a simple uri, but we have to extract the information from the
@@ -286,7 +287,7 @@ def __init__(self, path='/', params=None, plugin_name='', plugin_id='', override
self._video_player = None
self._audio_player = None
self._plugin_handle = int(sys.argv[1]) if num_args > 1 else -1
- self._plugin_id = plugin_id or self._addon.getAddonInfo('id')
+ self._plugin_id = plugin_id or ADDON_ID
self._plugin_name = plugin_name or self._addon.getAddonInfo('name')
self._version = self._addon.getAddonInfo('version')
self._native_path = xbmcvfs.translatePath(self._addon.getAddonInfo('path'))
@@ -431,8 +432,11 @@ def clone(self, new_path=None, new_params=None):
if not new_params:
new_params = self.get_params()
- new_context = XbmcContext(path=new_path, params=new_params, plugin_name=self._plugin_name,
- plugin_id=self._plugin_id, override=False)
+ new_context = XbmcContext(path=new_path,
+ params=new_params,
+ plugin_name=self._plugin_name,
+ plugin_id=self._plugin_id,
+ override=False)
new_context._function_cache = self._function_cache
new_context._search_history = self._search_history
new_context._favorite_list = self._favorite_list
@@ -488,7 +492,7 @@ def send_notification(self, method, data):
data = json.dumps(data)
self.log_debug('send_notification: |%s| -> |%s|' % (method, data))
data = '\\"[\\"%s\\"]\\"' % quote(data)
- self.execute('NotifyAll(plugin.video.youtube,%s,%s)' % (method, data))
+ self.execute('NotifyAll({0},{1},{2})'.format(ADDON_ID, method, data))
def use_inputstream_adaptive(self):
if self._settings.use_isa():
diff --git a/resources/lib/youtube_plugin/kodion/runner.py b/resources/lib/youtube_plugin/kodion/runner.py
index d03d44d69..458fe9e97 100644
--- a/resources/lib/youtube_plugin/kodion/runner.py
+++ b/resources/lib/youtube_plugin/kodion/runner.py
@@ -30,7 +30,7 @@ def run(provider, context=None):
start_time = timeit.default_timer()
if not context:
- context = Context(plugin_id='plugin.video.youtube')
+ context = Context()
context.log_debug('Starting Kodion framework by bromix...')
python_version = 'Unknown version of Python'
diff --git a/resources/lib/youtube_plugin/kodion/service.py b/resources/lib/youtube_plugin/kodion/service.py
index e50d59d71..609ab63d6 100644
--- a/resources/lib/youtube_plugin/kodion/service.py
+++ b/resources/lib/youtube_plugin/kodion/service.py
@@ -51,7 +51,7 @@ def run():
ping_timestamp = None
first_run = True
- context = Context(plugin_id='plugin.video.youtube')
+ context = Context()
context.log_debug('YouTube service initialization...')
diff --git a/resources/lib/youtube_plugin/youtube/client/__config__.py b/resources/lib/youtube_plugin/youtube/client/__config__.py
index fbe3eaf3f..545becf4e 100644
--- a/resources/lib/youtube_plugin/youtube/client/__config__.py
+++ b/resources/lib/youtube_plugin/youtube/client/__config__.py
@@ -192,7 +192,7 @@ def _strip_api_keys(self, api_key, client_id, client_secret):
return return_key, return_id, return_secret
-_api_check = APICheck(Context(plugin_id='plugin.video.youtube'))
+_api_check = APICheck(Context())
keys_changed = _api_check.changed
current_user = _api_check.get_current_user()
diff --git a/resources/lib/youtube_plugin/youtube/client/youtube.py b/resources/lib/youtube_plugin/youtube/client/youtube.py
index 0d9f65cce..272840e84 100644
--- a/resources/lib/youtube_plugin/youtube/client/youtube.py
+++ b/resources/lib/youtube_plugin/youtube/client/youtube.py
@@ -22,7 +22,7 @@
from ...kodion.utils import datetime_parser, strip_html_from_text, to_unicode
-_context = Context(plugin_id='plugin.video.youtube')
+_context = Context()
class YouTube(LoginClient):
diff --git a/resources/lib/youtube_registration.py b/resources/lib/youtube_registration.py
index 548be86bb..a5585082d 100644
--- a/resources/lib/youtube_registration.py
+++ b/resources/lib/youtube_registration.py
@@ -42,7 +42,7 @@ def register_api_keys(addon_id, api_key, client_id, client_secret):
:param client_secret: YouTube Data v3 Client secret
"""
- context = Context(plugin_id='plugin.video.youtube')
+ context = Context()
if not addon_id or addon_id == 'plugin.video.youtube':
context.log_error('Register API Keys: |%s| Invalid addon_id' % addon_id)
diff --git a/resources/lib/youtube_requests.py b/resources/lib/youtube_requests.py
index 81b532f88..822a04c38 100644
--- a/resources/lib/youtube_requests.py
+++ b/resources/lib/youtube_requests.py
@@ -22,9 +22,9 @@ def __get_core_components(addon_id=None):
"""
provider = Provider()
if addon_id is not None:
- context = Context(params={'addon_id': addon_id}, plugin_id='plugin.video.youtube')
+ context = Context(params={'addon_id': addon_id})
else:
- context = Context(plugin_id='plugin.video.youtube')
+ context = Context()
client = provider.get_client(context=context)
return provider, context, client
diff --git a/resources/lib/youtube_resolver.py b/resources/lib/youtube_resolver.py
index 53c771a3e..82429150f 100644
--- a/resources/lib/youtube_resolver.py
+++ b/resources/lib/youtube_resolver.py
@@ -18,9 +18,9 @@
def _get_core_components(addon_id=None):
provider = Provider()
if addon_id is not None:
- context = Context(params={'addon_id': addon_id}, plugin_id='plugin.video.youtube')
+ context = Context(params={'addon_id': addon_id})
else:
- context = Context(plugin_id='plugin.video.youtube')
+ context = Context()
client = provider.get_client(context=context)
return provider, context, client
From 613d51e0490d52d2ff870dbb6857db4a6dbda780 Mon Sep 17 00:00:00 2001
From: MoojMidge <56883549+MoojMidge@users.noreply.github.com>
Date: Sun, 24 Dec 2023 14:33:20 +1100
Subject: [PATCH 123/141] Youtube class now requires Context as a parameter
- Reuse existing, rather than creating a new Context at the module level
- Remove log method callbacks
---
.../youtube/client/login_client.py | 11 ---
.../youtube_plugin/youtube/client/youtube.py | 94 +++++++++++--------
.../lib/youtube_plugin/youtube/provider.py | 5 +-
3 files changed, 55 insertions(+), 55 deletions(-)
diff --git a/resources/lib/youtube_plugin/youtube/client/login_client.py b/resources/lib/youtube_plugin/youtube/client/login_client.py
index f6a70d0ea..76964ea66 100644
--- a/resources/lib/youtube_plugin/youtube/client/login_client.py
+++ b/resources/lib/youtube_plugin/youtube/client/login_client.py
@@ -76,8 +76,6 @@ def __init__(self, config=None, language='en-US', region='',
self._access_token = access_token
self._access_token_tv = access_token_tv
- self._log_error_callback = None
-
super(LoginClient, self).__init__(exc_type=LoginException)
@staticmethod
@@ -106,15 +104,6 @@ def _error_hook(**kwargs):
return None, None, None, json_data, False, InvalidGrant(json_data)
return None, None, None, json_data, False, LoginException(json_data)
- def set_log_error(self, callback):
- self._log_error_callback = callback
-
- def log_error(self, text):
- if self._log_error_callback:
- self._log_error_callback(text)
- else:
- print(text)
-
def verify(self):
return self._verify
diff --git a/resources/lib/youtube_plugin/youtube/client/youtube.py b/resources/lib/youtube_plugin/youtube/client/youtube.py
index 272840e84..5b6d7df45 100644
--- a/resources/lib/youtube_plugin/youtube/client/youtube.py
+++ b/resources/lib/youtube_plugin/youtube/client/youtube.py
@@ -18,15 +18,12 @@
from .login_client import LoginClient
from ..helper.video_info import VideoInfo
from ..youtube_exceptions import InvalidJSON, YouTubeException
-from ...kodion import Context
from ...kodion.utils import datetime_parser, strip_html_from_text, to_unicode
-_context = Context()
-
-
class YouTube(LoginClient):
- def __init__(self, **kwargs):
+ def __init__(self, context, **kwargs):
+ self._context = context
if not kwargs.get('config'):
kwargs['config'] = {}
if 'items_per_page' in kwargs:
@@ -381,11 +378,11 @@ def _get_recommendations_for_home(self):
'items': []
}
- watch_history_id = _context.get_access_manager().get_watch_history_id()
- if not watch_history_id or watch_history_id == 'HL':
+ history_id = self._context.get_access_manager().get_watch_history_id()
+ if not history_id or history_id == 'HL':
return payload
- cache = _context.get_data_cache()
+ cache = self._context.get_data_cache()
# Do we have a cached result?
cache_home_key = 'get-activities-home'
@@ -401,7 +398,7 @@ def _get_recommendations_for_home(self):
# Fetch history and recommended items. Use threads for faster execution.
def helper(video_id, responses):
- _context.log_debug(
+ self._context.log_debug(
'Method get_activities: doing expensive API fetch for related'
'items for video %s' % video_id
)
@@ -412,7 +409,7 @@ def helper(video_id, responses):
item['plugin_fetched_for'] = video_id
responses.extend(di['items'])
- history = self.get_playlist_items(watch_history_id, max_results=50)
+ history = self.get_playlist_items(history_id, max_results=50)
if not history.get('items'):
return payload
@@ -712,10 +709,11 @@ def get_live_events(self,
'maxResults': str(self._max_results)}
if location:
- location = _context.get_settings().get_location()
+ settings = self._context.get_settings()
+ location = settings.get_location()
if location:
params['location'] = location
- params['locationRadius'] = _context.get_settings().get_location_radius()
+ params['locationRadius'] = settings.get_location_radius()
if page_token:
params['pageToken'] = page_token
@@ -887,10 +885,11 @@ def search(self,
break
if params['type'] == 'video' and location:
- location = _context.get_settings().get_location()
+ settings = self._context.get_settings()
+ location = settings.get_location()
if location:
params['location'] = location
- params['locationRadius'] = _context.get_settings().get_location_radius()
+ params['locationRadius'] = settings.get_location_radius()
return self.perform_v3_request(method='GET',
path='search',
@@ -918,7 +917,7 @@ def _perform(_page_token, _offset, _result):
'items': []
}
- cache = _context.get_data_cache()
+ cache = self._context.get_data_cache()
# if new uploads is cached
cache_items_key = 'my-subscriptions-items'
@@ -1189,11 +1188,10 @@ def _perform(_playlist_idx, _page_token, _offset, _result):
return result
- @staticmethod
- def _response_hook(**kwargs):
+ def _response_hook(self, **kwargs):
response = kwargs['response']
- _context.log_debug('[data] v3 response: |{0.status_code}|\n'
- '\theaders: |{0.headers}|'.format(response))
+ self._context.log_debug('[data] v3 response: |{0.status_code}|\n'
+ '\theaders: |{0.headers}|'.format(response))
try:
json_data = response.json()
if 'error' in json_data:
@@ -1205,8 +1203,7 @@ def _response_hook(**kwargs):
response.raise_for_status()
return json_data
- @staticmethod
- def _error_hook(**kwargs):
+ def _error_hook(self, **kwargs):
exc = kwargs['exc']
json_data = getattr(exc, 'json_data', None)
data = getattr(exc, 'pass_data', False) and json_data
@@ -1224,10 +1221,10 @@ def _error_hook(**kwargs):
ok_dialog = False
timeout = 5000
if reason == 'accessNotConfigured':
- notification = _context.localize('key.requirement.notification')
+ notification = self._context.localize('key.requirement')
ok_dialog = True
elif reason == 'keyInvalid' and message == 'Bad Request':
- notification = _context.localize('api.key.incorrect')
+ notification = self._context.localize('api.key.incorrect')
timeout = 7000
elif reason in ('quotaExceeded', 'dailyLimitExceeded'):
notification = message
@@ -1235,13 +1232,13 @@ def _error_hook(**kwargs):
else:
notification = message
- title = '{0}: {1}'.format(_context.get_name(), reason)
+ title = '{0}: {1}'.format(self._context.get_name(), reason)
if ok_dialog:
- _context.get_ui().on_ok(title, notification)
+ self._context.get_ui().on_ok(title, notification)
else:
- _context.get_ui().show_notification(notification,
- title,
- time_ms=timeout)
+ self._context.get_ui().show_notification(notification,
+ title,
+ time_ms=timeout)
info = ('[data] v3 error: {reason}\n'
'\texc: |{exc}|\n'
@@ -1282,15 +1279,16 @@ def perform_v3_request(self, method='GET', headers=None, path=None,
else:
log_params = None
- _context.log_debug('[data] v3 request: |{method}|\n'
- '\tpath: |{path}|\n'
- '\tparams: |{params}|\n'
- '\tpost_data: |{data}|\n'
- '\theaders: |{headers}|'.format(method=method,
- path=path,
- params=log_params,
- data=post_data,
- headers=_headers))
+ self._context.log_debug('[data] v3 request: |{method}|\n'
+ '\tpath: |{path}|\n'
+ '\tparams: |{params}|\n'
+ '\tpost_data: |{data}|\n'
+ '\theaders: |{headers}|'
+ .format(method=method,
+ path=path,
+ params=log_params,
+ data=post_data,
+ headers=_headers))
json_data = self.request(_url,
method=method,
@@ -1336,13 +1334,27 @@ def perform_v1_tv_request(self, method='GET', headers=None, path=None,
log_params['location'] = 'xx.xxxx,xx.xxxx'
else:
log_params = None
- _context.log_debug('[data] v1 request: |{0}| path: |{1}| params: |{2}| post_data: |{3}|'.format(method, path, log_params, post_data))
-
- result = self.request(_url, method=method, headers=_headers, json=post_data, params=_params)
+ self._context.log_debug('[data] v1 request: |{method}|\n'
+ '\tpath: |{path}|\n'
+ '\tparams: |{params}|\n'
+ '\tpost_data: |{data}|\n'
+ '\theaders: |{headers}|'
+ .format(method=method,
+ path=path,
+ params=log_params,
+ data=post_data,
+ headers=_headers))
+
+ result = self.request(_url,
+ method=method,
+ headers=_headers,
+ json=post_data,
+ params=_params)
if result is None:
return {}
- _context.log_debug('[data] v1 response: |{0}| headers: |{1}|'.format(result.status_code, result.headers))
+ self._context.log_debug('[data] v1 response: |{0.status_code}|\n'
+ '\theaders: |{0.headers}|'.format(result))
if result.headers.get('content-type', '').startswith('application/json'):
try:
diff --git a/resources/lib/youtube_plugin/youtube/provider.py b/resources/lib/youtube_plugin/youtube/provider.py
index d406c3739..e13be4b0c 100644
--- a/resources/lib/youtube_plugin/youtube/provider.py
+++ b/resources/lib/youtube_plugin/youtube/provider.py
@@ -194,14 +194,14 @@ def get_client(self, context):
refresh_tokens = refresh_tokens.split('|')
context.log_debug('Access token count: |%d| Refresh token count: |%d|' % (len(access_tokens), len(refresh_tokens)))
- client = YouTube(language=language,
+ client = YouTube(context=context,
+ language=language,
region=region,
items_per_page=items_per_page,
config=dev_keys if dev_keys else youtube_config)
with client:
if not refresh_tokens or not refresh_tokens[0]:
- client.set_log_error(context.log_error)
self._client = client
# create new access tokens
@@ -242,7 +242,6 @@ def get_client(self, context):
client.set_access_token(access_token=access_tokens[1])
client.set_access_token_tv(access_token_tv=access_tokens[0])
- client.set_log_error(context.log_error)
self._client = client
return self._client
From 58195b765658baa4ca09561edf4541212a0631ff Mon Sep 17 00:00:00 2001
From: MoojMidge <56883549+MoojMidge@users.noreply.github.com>
Date: Sun, 24 Dec 2023 14:39:20 +1100
Subject: [PATCH 124/141] Fix regression after 72480b5 caused by typo
---
.../lib/youtube_plugin/youtube/helper/url_to_item_converter.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
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 c81aef0eb..bfc96ee8e 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
@@ -97,7 +97,7 @@ def add_url(self, url, provider, context):
return
playlist_item = DirectoryItem(
- '', context.create_uri(['playlist', playlist_id]), new_params
+ '', context.create_uri(['playlist', playlist_id], new_params),
)
playlist_item.set_fanart(provider.get_fanart(context))
self._playlist_id_dict[playlist_id] = playlist_item
From 9f3ac5dbe5cdaed36545a27324375cf3340db70d Mon Sep 17 00:00:00 2001
From: MoojMidge <56883549+MoojMidge@users.noreply.github.com>
Date: Sun, 24 Dec 2023 14:41:21 +1100
Subject: [PATCH 125/141] Fix incorrect MPEG-DASH mime type
---
resources/lib/youtube_plugin/kodion/network/http_server.py | 4 ++--
resources/lib/youtube_plugin/kodion/ui/xbmc/xbmc_items.py | 2 +-
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/resources/lib/youtube_plugin/kodion/network/http_server.py b/resources/lib/youtube_plugin/kodion/network/http_server.py
index 18d4a041e..f665346da 100644
--- a/resources/lib/youtube_plugin/kodion/network/http_server.py
+++ b/resources/lib/youtube_plugin/kodion/network/http_server.py
@@ -106,7 +106,7 @@ def do_GET(self):
try:
with open(file_path, 'rb') as f:
self.send_response(200)
- self.send_header('Content-Type', 'application/xml+dash')
+ self.send_header('Content-Type', 'application/dash+xml')
self.send_header('Content-Length',
str(os.path.getsize(file_path)))
self.end_headers()
@@ -208,7 +208,7 @@ def do_HEAD(self):
self.send_error(404, response)
else:
self.send_response(200)
- self.send_header('Content-Type', 'application/xml+dash')
+ self.send_header('Content-Type', 'application/dash+xml')
self.send_header('Content-Length',
str(os.path.getsize(file_path)))
self.end_headers()
diff --git a/resources/lib/youtube_plugin/kodion/ui/xbmc/xbmc_items.py b/resources/lib/youtube_plugin/kodion/ui/xbmc/xbmc_items.py
index 0d3a0e615..d89cfba8f 100644
--- a/resources/lib/youtube_plugin/kodion/ui/xbmc/xbmc_items.py
+++ b/resources/lib/youtube_plugin/kodion/ui/xbmc/xbmc_items.py
@@ -81,7 +81,7 @@ def video_playback_item(context, video_item):
and context.addon_enabled('inputstream.adaptive')):
if video_item.use_mpd_video():
manifest_type = 'mpd'
- mime_type = 'application/xml+dash'
+ mime_type = 'application/dash+xml'
"""
# MPD manifest update is currently broken
# Following line will force a full update but restart live stream
From 44251e48261d23268dbcccad6be93e0b80928502 Mon Sep 17 00:00:00 2001
From: MoojMidge <56883549+MoojMidge@users.noreply.github.com>
Date: Sun, 24 Dec 2023 14:44:22 +1100
Subject: [PATCH 126/141] Remove redundant is_empty method on Storage
sub-classes
- Just use re-named base class method
---
resources/lib/youtube_plugin/kodion/sql_store/data_cache.py | 3 ---
.../lib/youtube_plugin/kodion/sql_store/playback_history.py | 3 ---
.../lib/youtube_plugin/kodion/sql_store/search_history.py | 3 ---
resources/lib/youtube_plugin/kodion/sql_store/storage.py | 4 ++--
4 files changed, 2 insertions(+), 11 deletions(-)
diff --git a/resources/lib/youtube_plugin/kodion/sql_store/data_cache.py b/resources/lib/youtube_plugin/kodion/sql_store/data_cache.py
index e0271f939..197ca8add 100644
--- a/resources/lib/youtube_plugin/kodion/sql_store/data_cache.py
+++ b/resources/lib/youtube_plugin/kodion/sql_store/data_cache.py
@@ -24,9 +24,6 @@ def __init__(self, filename, max_file_size_mb=5):
super(DataCache, self).__init__(filename,
max_file_size_kb=max_file_size_kb)
- def is_empty(self):
- return self._is_empty()
-
def get_items(self, content_ids, seconds):
result = self._get_by_ids(content_ids, seconds=seconds, as_dict=True)
return result
diff --git a/resources/lib/youtube_plugin/kodion/sql_store/playback_history.py b/resources/lib/youtube_plugin/kodion/sql_store/playback_history.py
index 56874ba97..03a4a9ce3 100644
--- a/resources/lib/youtube_plugin/kodion/sql_store/playback_history.py
+++ b/resources/lib/youtube_plugin/kodion/sql_store/playback_history.py
@@ -21,9 +21,6 @@ class PlaybackHistory(Storage):
def __init__(self, filename):
super(PlaybackHistory, self).__init__(filename)
- def is_empty(self):
- return self._is_empty()
-
def _add_last_played(self, value, item):
value['last_played'] = self._convert_timestamp(item[1])
return value
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 b53110b32..6f6784534 100644
--- a/resources/lib/youtube_plugin/kodion/sql_store/search_history.py
+++ b/resources/lib/youtube_plugin/kodion/sql_store/search_history.py
@@ -25,9 +25,6 @@ def __init__(self, filename, max_item_count=10):
super(SearchHistory, self).__init__(filename,
max_item_count=max_item_count)
- def is_empty(self):
- return self._is_empty()
-
def get_items(self):
result = self._get_by_ids(oldest_first=False,
limit=self._max_item_count,
diff --git a/resources/lib/youtube_plugin/kodion/sql_store/storage.py b/resources/lib/youtube_plugin/kodion/sql_store/storage.py
index 46c7c2ba9..41faa7d50 100644
--- a/resources/lib/youtube_plugin/kodion/sql_store/storage.py
+++ b/resources/lib/youtube_plugin/kodion/sql_store/storage.py
@@ -285,7 +285,7 @@ def _optimize_item_count(self, limit=-1, defer=False):
# clear db if max item count has been set to 0
if not self._max_item_count:
- if not self._is_empty():
+ if not self.is_empty():
return self.clear(defer)
return False
@@ -329,7 +329,7 @@ def clear(self, defer=False):
self._execute(cursor, 'VACUUM')
return True
- def _is_empty(self):
+ def is_empty(self):
with self as (db, cursor), db:
result = self._execute(cursor, self._sql['is_empty'])
for item in result:
From 478576c28b1237e71a92e7d88e613d41293a2635 Mon Sep 17 00:00:00 2001
From: MoojMidge <56883549+MoojMidge@users.noreply.github.com>
Date: Sun, 24 Dec 2023 15:06:06 +1100
Subject: [PATCH 127/141] Change constants module import aliases
- "content_type" to "content"
- "setting" to "settings"
- "sort_method" to "sort"
---
.../kodion/abstract_provider.py | 15 +-
.../kodion/constants/__init__.py | 16 ++-
.../kodion/context/abstract_context.py | 9 +-
.../kodion/settings/abstract_settings.py | 128 +++++++++---------
.../youtube/helper/yt_specials.py | 26 ++--
.../lib/youtube_plugin/youtube/provider.py | 47 ++++---
6 files changed, 124 insertions(+), 117 deletions(-)
diff --git a/resources/lib/youtube_plugin/kodion/abstract_provider.py b/resources/lib/youtube_plugin/kodion/abstract_provider.py
index 57c5fdffa..f68cab2b0 100644
--- a/resources/lib/youtube_plugin/kodion/abstract_provider.py
+++ b/resources/lib/youtube_plugin/kodion/abstract_provider.py
@@ -12,7 +12,7 @@
import re
-from . import constants
+from .constants import settings, paths, content
from .compatibility import quote, unquote
from .exceptions import KodionException
from .items import (
@@ -39,25 +39,25 @@ def __init__(self):
self.register_path(r''.join([
'^/',
- constants.paths.WATCH_LATER,
+ paths.WATCH_LATER,
'/(?Padd|clear|list|remove)/?$'
]), '_internal_watch_later')
self.register_path(r''.join([
'^/',
- constants.paths.FAVORITES,
+ paths.FAVORITES,
'/(?Padd|clear|list|remove)/?$'
]), '_internal_favorite')
self.register_path(r''.join([
'^/',
- constants.paths.SEARCH,
+ paths.SEARCH,
'/(?Pinput|query|list|remove|clear|rename)/?$'
]), '_internal_search')
self.register_path(r''.join([
'^/',
- constants.paths.HISTORY,
+ paths.HISTORY,
'/$'
]), 'on_playback_history')
@@ -92,7 +92,7 @@ def _process_wizard(self, context):
# start the setup wizard
wizard_steps = []
if context.get_settings().is_setup_wizard_enabled():
- context.get_settings().set_bool(constants.setting.SETUP_WIZARD, False)
+ context.get_settings().set_bool(settings.SETUP_WIZARD, False)
wizard_steps.extend(self.get_wizard_steps(context))
if wizard_steps and context.get_ui().on_yes_no_input(context.get_name(),
@@ -324,8 +324,7 @@ def _internal_search(self, context, re_match):
if isinstance(query, bytes):
query = query.decode('utf-8')
return self.on_search(query, context, re_match)
-
- context.set_content_type(constants.content_type.VIDEOS)
+ context.set_content_type(content.VIDEOS)
result = []
location = context.get_param('location', False)
diff --git a/resources/lib/youtube_plugin/kodion/constants/__init__.py b/resources/lib/youtube_plugin/kodion/constants/__init__.py
index 9a297be63..819a9060e 100644
--- a/resources/lib/youtube_plugin/kodion/constants/__init__.py
+++ b/resources/lib/youtube_plugin/kodion/constants/__init__.py
@@ -10,18 +10,20 @@
from __future__ import absolute_import, division, unicode_literals
-from . import const_settings as setting
-from . import const_sort_methods as sort_method
-from . import const_content_types as content_type
-from . import const_paths as paths
+from . import (
+ const_content_types as content,
+ const_paths as paths,
+ const_settings as settings,
+ const_sort_methods as sort,
+)
ADDON_ID = 'plugin.video.youtube'
__all__ = (
'ADDON_ID',
- 'content_type',
+ 'content',
'paths',
- 'setting',
- 'sort_method',
+ 'settings',
+ 'sort',
)
diff --git a/resources/lib/youtube_plugin/kodion/context/abstract_context.py b/resources/lib/youtube_plugin/kodion/context/abstract_context.py
index 1f3851bc5..3a789dbd9 100644
--- a/resources/lib/youtube_plugin/kodion/context/abstract_context.py
+++ b/resources/lib/youtube_plugin/kodion/context/abstract_context.py
@@ -12,8 +12,9 @@
import os
-from .. import constants, logger
+from .. import logger
from ..compatibility import urlencode
+from ..constants import settings
from ..json_store import AccessManager
from ..sql_store import (
DataCache,
@@ -148,7 +149,7 @@ def get_playback_history(self):
def get_data_cache(self):
if not self._data_cache:
- max_cache_size_mb = self.get_settings().get_int(constants.setting.CACHE_SIZE, -1)
+ max_cache_size_mb = self.get_settings().get_int(settings.CACHE_SIZE, -1)
if max_cache_size_mb <= 0:
max_cache_size_mb = 5
else:
@@ -159,7 +160,7 @@ def get_data_cache(self):
def get_function_cache(self):
if not self._function_cache:
- max_cache_size_mb = self.get_settings().get_int(constants.setting.CACHE_SIZE, -1)
+ max_cache_size_mb = self.get_settings().get_int(settings.CACHE_SIZE, -1)
if max_cache_size_mb <= 0:
max_cache_size_mb = 5
else:
@@ -170,7 +171,7 @@ def get_function_cache(self):
def get_search_history(self):
if not self._search_history:
- max_search_history_items = self.get_settings().get_int(constants.setting.SEARCH_SIZE, 50)
+ max_search_history_items = self.get_settings().get_int(settings.SEARCH_SIZE, 50)
self._search_history = SearchHistory(os.path.join(self.get_cache_path(), 'search'),
max_item_count=max_search_history_items)
return self._search_history
diff --git a/resources/lib/youtube_plugin/kodion/settings/abstract_settings.py b/resources/lib/youtube_plugin/kodion/settings/abstract_settings.py
index 527ab40ff..0375ce2e1 100644
--- a/resources/lib/youtube_plugin/kodion/settings/abstract_settings.py
+++ b/resources/lib/youtube_plugin/kodion/settings/abstract_settings.py
@@ -12,7 +12,7 @@
import sys
-from ..constants import setting as SETTINGS
+from ..constants import settings
class AbstractSettings(object):
@@ -57,7 +57,7 @@ def open_settings(self):
raise NotImplementedError()
def get_items_per_page(self):
- return self.get_int(SETTINGS.ITEMS_PER_PAGE, 50)
+ return self.get_int(settings.ITEMS_PER_PAGE, 50)
def get_video_quality(self, quality_map_override=None):
vq_dict = {0: 240,
@@ -69,75 +69,75 @@ def get_video_quality(self, quality_map_override=None):
if quality_map_override is not None:
vq_dict = quality_map_override
- vq = self.get_int(SETTINGS.VIDEO_QUALITY, 1)
+ vq = self.get_int(settings.VIDEO_QUALITY, 1)
return vq_dict[vq]
def ask_for_video_quality(self):
- return self.get_bool(SETTINGS.VIDEO_QUALITY_ASK, False)
+ return self.get_bool(settings.VIDEO_QUALITY_ASK, False)
def show_fanart(self):
- return self.get_bool(SETTINGS.SHOW_FANART, True)
+ return self.get_bool(settings.SHOW_FANART, True)
def get_search_history_size(self):
- return self.get_int(SETTINGS.SEARCH_SIZE, 50)
+ return self.get_int(settings.SEARCH_SIZE, 50)
def is_setup_wizard_enabled(self):
- return self.get_bool(SETTINGS.SETUP_WIZARD, False)
+ return self.get_bool(settings.SETUP_WIZARD, False)
def is_support_alternative_player_enabled(self):
- return self.get_bool(SETTINGS.SUPPORT_ALTERNATIVE_PLAYER, False)
+ return self.get_bool(settings.SUPPORT_ALTERNATIVE_PLAYER, False)
def alternative_player_web_urls(self):
- return self.get_bool(SETTINGS.ALTERNATIVE_PLAYER_WEB_URLS, False)
+ return self.get_bool(settings.ALTERNATIVE_PLAYER_WEB_URLS, False)
def use_isa(self):
- return self.get_bool(SETTINGS.USE_ISA, False)
+ return self.get_bool(settings.USE_ISA, False)
def subtitle_languages(self):
- return self.get_int(SETTINGS.SUBTITLE_LANGUAGE, 0)
+ return self.get_int(settings.SUBTITLE_LANGUAGE, 0)
def subtitle_download(self):
- return self.get_bool(SETTINGS.SUBTITLE_DOWNLOAD, False)
+ return self.get_bool(settings.SUBTITLE_DOWNLOAD, False)
def audio_only(self):
- return self.get_bool(SETTINGS.AUDIO_ONLY, False)
+ return self.get_bool(settings.AUDIO_ONLY, False)
def set_subtitle_languages(self, value):
- return self.set_int(SETTINGS.SUBTITLE_LANGUAGE, value)
+ return self.set_int(settings.SUBTITLE_LANGUAGE, value)
def set_subtitle_download(self, value):
- return self.set_bool(SETTINGS.SUBTITLE_DOWNLOAD, value)
+ return self.set_bool(settings.SUBTITLE_DOWNLOAD, value)
def use_thumbnail_size(self):
- size = self.get_int(SETTINGS.THUMB_SIZE, 0)
+ size = self.get_int(settings.THUMB_SIZE, 0)
sizes = {0: 'medium', 1: 'high'}
return sizes[size]
def safe_search(self):
- index = self.get_int(SETTINGS.SAFE_SEARCH, 0)
+ index = self.get_int(settings.SAFE_SEARCH, 0)
values = {0: 'moderate', 1: 'none', 2: 'strict'}
return values[index]
def age_gate(self):
- return self.get_bool(SETTINGS.AGE_GATE, True)
+ return self.get_bool(settings.AGE_GATE, True)
def verify_ssl(self):
- verify = self.get_bool(SETTINGS.VERIFY_SSL, False)
+ verify = self.get_bool(settings.VERIFY_SSL, False)
if sys.version_info <= (2, 7, 9):
verify = False
return verify
def get_timeout(self):
- connect_timeout = self.get_int(SETTINGS.CONNECT_TIMEOUT, 9) + 0.5
- read_timout = self.get_int(SETTINGS.READ_TIMEOUT, 27)
+ 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
def allow_dev_keys(self):
- return self.get_bool(SETTINGS.ALLOW_DEV_KEYS, False)
+ return self.get_bool(settings.ALLOW_DEV_KEYS, False)
def use_mpd_videos(self):
if self.use_isa():
- return self.get_bool(SETTINGS.MPD_VIDEOS, False)
+ return self.get_bool(settings.MPD_VIDEOS, False)
return False
_LIVE_STREAM_TYPES = {
@@ -149,26 +149,26 @@ def use_mpd_videos(self):
def get_live_stream_type(self):
if self.use_isa():
- stream_type = self.get_int(SETTINGS.LIVE_STREAMS + '.1', 0)
+ stream_type = self.get_int(settings.LIVE_STREAMS + '.1', 0)
else:
- stream_type = self.get_int(SETTINGS.LIVE_STREAMS + '.2', 0)
+ stream_type = self.get_int(settings.LIVE_STREAMS + '.2', 0)
return self._LIVE_STREAM_TYPES.get(stream_type) or self._LIVE_STREAM_TYPES[0]
def use_isa_live_streams(self):
if self.use_isa():
- return self.get_int(SETTINGS.LIVE_STREAMS + '.1', 0) > 1
+ return self.get_int(settings.LIVE_STREAMS + '.1', 0) > 1
return False
def use_mpd_live_streams(self):
if self.use_isa():
- return self.get_int(SETTINGS.LIVE_STREAMS + '.1', 0) == 3
+ return self.get_int(settings.LIVE_STREAMS + '.1', 0) == 3
return False
def httpd_port(self, port=None):
default_port = 50152
if port is None:
- port = self.get_int(SETTINGS.HTTPD_PORT, default_port)
+ port = self.get_int(settings.HTTPD_PORT, default_port)
try:
port = int(port)
@@ -181,7 +181,7 @@ def httpd_listen(self, for_request=False, ip_address=None):
default_octets = [0, 0, 0, 0,]
if not ip_address:
- ip_address = self.get_string(SETTINGS.HTTPD_LISTEN,
+ ip_address = self.get_string(settings.HTTPD_LISTEN,
default_address)
try:
@@ -197,10 +197,10 @@ def httpd_listen(self, for_request=False, ip_address=None):
return '.'.join(map(str, octets))
def set_httpd_listen(self, value):
- return self.set_string(SETTINGS.HTTPD_LISTEN, value)
+ return self.set_string(settings.HTTPD_LISTEN, value)
def httpd_whitelist(self):
- allow_list = self.get_string(SETTINGS.HTTPD_WHITELIST, '')
+ allow_list = self.get_string(settings.HTTPD_WHITELIST, '')
allow_list = ''.join(allow_list.split()).split(',')
allow_list = [
self.httpd_listen(for_request=True, ip_address=ip_address)
@@ -209,52 +209,52 @@ def httpd_whitelist(self):
return allow_list
def api_config_page(self):
- return self.get_bool(SETTINGS.API_CONFIG_PAGE, False)
+ return self.get_bool(settings.API_CONFIG_PAGE, False)
def api_id(self, new_id=None):
if new_id is not None:
- self.set_string(SETTINGS.API_ID, new_id)
+ self.set_string(settings.API_ID, new_id)
return new_id
- return self.get_string(SETTINGS.API_ID)
+ return self.get_string(settings.API_ID)
def api_key(self, new_key=None):
if new_key is not None:
- self.set_string(SETTINGS.API_KEY, new_key)
+ self.set_string(settings.API_KEY, new_key)
return new_key
- return self.get_string(SETTINGS.API_KEY)
+ return self.get_string(settings.API_KEY)
def api_secret(self, new_secret=None):
if new_secret is not None:
- self.set_string(SETTINGS.API_SECRET, new_secret)
+ self.set_string(settings.API_SECRET, new_secret)
return new_secret
- return self.get_string(SETTINGS.API_SECRET)
+ return self.get_string(settings.API_SECRET)
def api_last_hash(self, new_hash=None):
if new_hash is not None:
- self.set_string(SETTINGS.API_LAST_HASH, new_hash)
+ self.set_string(settings.API_LAST_HASH, new_hash)
return new_hash
- return self.get_string(SETTINGS.API_LAST_HASH, '')
+ return self.get_string(settings.API_LAST_HASH, '')
def user_access_token(self, new_access_token=None):
if new_access_token is not None:
- self.set_string(SETTINGS.USER_ACCESS_TOKEN, new_access_token)
+ self.set_string(settings.USER_ACCESS_TOKEN, new_access_token)
return new_access_token
- return self.get_string(SETTINGS.USER_ACCESS_TOKEN, '')
+ return self.get_string(settings.USER_ACCESS_TOKEN, '')
def user_refresh_token(self, new_refresh_token=None):
if new_refresh_token is not None:
- self.set_string(SETTINGS.USER_REFRESH_TOKEN, new_refresh_token)
+ self.set_string(settings.USER_REFRESH_TOKEN, new_refresh_token)
return new_refresh_token
- return self.get_string(SETTINGS.USER_REFRESH_TOKEN, '')
+ return self.get_string(settings.USER_REFRESH_TOKEN, '')
def user_token_expiration(self, new_token_expiration=None):
if new_token_expiration is not None:
- self.set_int(SETTINGS.USER_TOKEN_EXPIRATION, new_token_expiration)
+ self.set_int(settings.USER_TOKEN_EXPIRATION, new_token_expiration)
return new_token_expiration
- return self.get_int(SETTINGS.USER_TOKEN_EXPIRATION, -1)
+ return self.get_int(settings.USER_TOKEN_EXPIRATION, -1)
def get_location(self):
- location = self.get_string(SETTINGS.LOCATION, '').replace(' ', '').strip()
+ location = self.get_string(settings.LOCATION, '').replace(' ', '').strip()
coords = location.split(',')
latitude = longitude = None
if len(coords) == 2:
@@ -272,19 +272,19 @@ def get_location(self):
return ''
def set_location(self, value):
- self.set_string(SETTINGS.LOCATION, value)
+ self.set_string(settings.LOCATION, value)
def get_location_radius(self):
- return ''.join([str(self.get_int(SETTINGS.LOCATION_RADIUS, 500)), 'km'])
+ return ''.join([str(self.get_int(settings.LOCATION_RADIUS, 500)), 'km'])
def get_play_count_min_percent(self):
- return self.get_int(SETTINGS.PLAY_COUNT_MIN_PERCENT, 0)
+ return self.get_int(settings.PLAY_COUNT_MIN_PERCENT, 0)
def use_local_history(self):
- return self.get_bool(SETTINGS.USE_LOCAL_HISTORY, False)
+ return self.get_bool(settings.USE_LOCAL_HISTORY, False)
def use_remote_history(self):
- return self.get_bool(SETTINGS.USE_REMOTE_HISTORY, False)
+ return self.get_bool(settings.USE_REMOTE_HISTORY, False)
# Selections based on max width and min height at common (utra-)wide aspect ratios
_QUALITY_SELECTIONS = { # Setting | Resolution
@@ -304,14 +304,14 @@ def use_remote_history(self):
def get_mpd_video_qualities(self):
if not self.use_mpd_videos():
return []
- selected = self.get_int(SETTINGS.MPD_QUALITY_SELECTION, 4)
+ selected = self.get_int(settings.MPD_QUALITY_SELECTION, 4)
return [quality
for key, quality in sorted(self._QUALITY_SELECTIONS.items(),
reverse=True)
if selected >= key]
def stream_features(self):
- return self.get_string_list(SETTINGS.MPD_STREAM_FEATURES)
+ return self.get_string_list(settings.MPD_STREAM_FEATURES)
_STREAM_SELECT = {
1: 'auto',
@@ -320,32 +320,32 @@ def stream_features(self):
}
def stream_select(self):
- select_type = self.get_int(SETTINGS.MPD_STREAM_SELECT, 1)
+ select_type = self.get_int(settings.MPD_STREAM_SELECT, 1)
return self._STREAM_SELECT.get(select_type) or self._STREAM_SELECT[1]
def remote_friendly_search(self):
- return self.get_bool(SETTINGS.REMOTE_FRIENDLY_SEARCH, False)
+ return self.get_bool(settings.REMOTE_FRIENDLY_SEARCH, False)
def hide_short_videos(self):
- return self.get_bool(SETTINGS.HIDE_SHORT_VIDEOS, False)
+ return self.get_bool(settings.HIDE_SHORT_VIDEOS, False)
def client_selection(self):
- return self.get_int(SETTINGS.CLIENT_SELECTION, 0)
+ return self.get_int(settings.CLIENT_SELECTION, 0)
def show_detailed_description(self):
- return self.get_bool(SETTINGS.DETAILED_DESCRIPTION, True)
+ return self.get_bool(settings.DETAILED_DESCRIPTION, True)
def get_language(self):
- return self.get_string(SETTINGS.LANGUAGE, 'en_US').replace('_', '-')
+ return self.get_string(settings.LANGUAGE, 'en_US').replace('_', '-')
def get_watch_later_playlist(self):
- return self.get_string(SETTINGS.WATCH_LATER_PLAYLIST, '').strip()
+ return self.get_string(settings.WATCH_LATER_PLAYLIST, '').strip()
def set_watch_later_playlist(self, value):
- return self.set_string(SETTINGS.WATCH_LATER_PLAYLIST, value)
+ return self.set_string(settings.WATCH_LATER_PLAYLIST, value)
def get_history_playlist(self):
- return self.get_string(SETTINGS.HISTORY_PLAYLIST, '').strip()
+ return self.get_string(settings.HISTORY_PLAYLIST, '').strip()
def set_history_playlist(self, value):
- return self.set_string(SETTINGS.HISTORY_PLAYLIST, value)
+ return self.set_string(settings.HISTORY_PLAYLIST, value)
diff --git a/resources/lib/youtube_plugin/youtube/helper/yt_specials.py b/resources/lib/youtube_plugin/youtube/helper/yt_specials.py
index 5a4456ba8..6ac51e84b 100644
--- a/resources/lib/youtube_plugin/youtube/helper/yt_specials.py
+++ b/resources/lib/youtube_plugin/youtube/helper/yt_specials.py
@@ -19,13 +19,13 @@
v3,
)
from ...kodion import KodionException
-from ...kodion.constants import content_type
+from ...kodion.constants import content
from ...kodion.items import DirectoryItem, UriItem
from ...kodion.utils import strip_html_from_text
def _process_related_videos(provider, context):
- provider.set_content_type(context, content_type.VIDEOS)
+ provider.set_content_type(context, content.VIDEOS)
video_id = context.get_param('video_id', '')
if not video_id:
return []
@@ -42,7 +42,7 @@ def _process_related_videos(provider, context):
def _process_parent_comments(provider, context):
- provider.set_content_type(context, content_type.FILES)
+ provider.set_content_type(context, content.FILES)
video_id = context.get_param('video_id', '')
if not video_id:
return []
@@ -56,7 +56,7 @@ def _process_parent_comments(provider, context):
def _process_child_comments(provider, context):
- provider.set_content_type(context, content_type.FILES)
+ provider.set_content_type(context, content.FILES)
parent_id = context.get_param('parent_id', '')
if not parent_id:
return []
@@ -70,7 +70,7 @@ def _process_child_comments(provider, context):
def _process_recommendations(provider, context):
- provider.set_content_type(context, content_type.VIDEOS)
+ provider.set_content_type(context, content.VIDEOS)
json_data = provider.get_client(context).get_activities(
channel_id='home', page_token=context.get_param('page_token', '')
)
@@ -80,7 +80,7 @@ def _process_recommendations(provider, context):
def _process_popular_right_now(provider, context):
- provider.set_content_type(context, content_type.VIDEOS)
+ provider.set_content_type(context, content.VIDEOS)
json_data = provider.get_client(context).get_popular_videos(
page_token=context.get_param('page_token', '')
)
@@ -90,7 +90,7 @@ def _process_popular_right_now(provider, context):
def _process_browse_channels(provider, context):
- provider.set_content_type(context, content_type.FILES)
+ provider.set_content_type(context, content.FILES)
client = provider.get_client(context)
guide_id = context.get_param('guide_id', '')
if guide_id:
@@ -108,7 +108,7 @@ def _process_browse_channels(provider, context):
def _process_disliked_videos(provider, context):
- provider.set_content_type(context, content_type.VIDEOS)
+ provider.set_content_type(context, content.VIDEOS)
json_data = provider.get_client(context).get_disliked_videos(
page_token=context.get_param('page_token', '')
)
@@ -121,7 +121,7 @@ def _process_live_events(provider, context, event_type='live'):
def _sort(x):
return x.get_date()
- provider.set_content_type(context, content_type.VIDEOS)
+ provider.set_content_type(context, content.VIDEOS)
# TODO: cache result
json_data = provider.get_client(context).get_live_events(
event_type=event_type,
@@ -139,7 +139,7 @@ def _process_description_links(provider, context):
addon_id = params.get('addon_id', '')
def _extract_urls(video_id):
- provider.set_content_type(context, content_type.VIDEOS)
+ provider.set_content_type(context, content.VIDEOS)
url_resolver = UrlResolver(context)
progress_dialog = context.get_ui().create_progress_dialog(
@@ -262,7 +262,7 @@ def _display_playlists(playlist_ids):
def _process_saved_playlists_tv(provider, context):
- provider.set_content_type(context, content_type.FILES)
+ provider.set_content_type(context, content.FILES)
json_data = provider.get_client(context).get_saved_playlists(
page_token=context.get_param('next_page_token', ''),
offset=context.get_param('offset', 0)
@@ -271,7 +271,7 @@ def _process_saved_playlists_tv(provider, context):
def _process_new_uploaded_videos_tv(provider, context):
- provider.set_content_type(context, content_type.VIDEOS)
+ provider.set_content_type(context, content.VIDEOS)
json_data = provider.get_client(context).get_my_subscriptions(
page_token=context.get_param('next_page_token', ''),
offset=context.get_param('offset', 0)
@@ -280,7 +280,7 @@ def _process_new_uploaded_videos_tv(provider, context):
def _process_new_uploaded_videos_tv_filtered(provider, context):
- provider.set_content_type(context, content_type.VIDEOS)
+ provider.set_content_type(context, content.VIDEOS)
json_data = provider.get_client(context).get_my_subscriptions(
page_token=context.get_param('next_page_token', ''),
offset=context.get_param('offset', 0)
diff --git a/resources/lib/youtube_plugin/youtube/provider.py b/resources/lib/youtube_plugin/youtube/provider.py
index e13be4b0c..dab29cec9 100644
--- a/resources/lib/youtube_plugin/youtube/provider.py
+++ b/resources/lib/youtube_plugin/youtube/provider.py
@@ -33,8 +33,13 @@
yt_video,
)
from .youtube_exceptions import InvalidGrant, LoginException
-from ..kodion import (AbstractProvider, RegisterProviderPath, constants)
+from ..kodion import AbstractProvider, RegisterProviderPath
from ..kodion.compatibility import xbmcaddon, xbmcvfs
+from ..kodion.constants import (
+ content,
+ paths,
+ sort,
+)
from ..kodion.items import DirectoryItem, NewSearchItem, SearchItem, menu_items
from ..kodion.network import get_client_ip_address, is_httpd_live
from ..kodion.utils import find_video_id, strip_html_from_text
@@ -286,7 +291,7 @@ def on_uri2addon(self, context, re_match):
@RegisterProviderPath('^(?:/channel/(?P[^/]+))?/playlist/(?P[^/]+)/$')
def _on_playlist(self, context, re_match):
- self.set_content_type(context, constants.content_type.VIDEOS)
+ self.set_content_type(context, content.VIDEOS)
client = self.get_client(context)
resource_manager = self.get_resource_manager(context)
@@ -307,7 +312,7 @@ def _on_playlist(self, context, re_match):
@RegisterProviderPath('^/channel/(?P[^/]+)/playlists/$')
def _on_channel_playlists(self, context, re_match):
- self.set_content_type(context, constants.content_type.FILES)
+ self.set_content_type(context, content.FILES)
result = []
channel_id = re_match.group('channel_id')
@@ -348,7 +353,7 @@ def _on_channel_playlists(self, context, re_match):
@RegisterProviderPath('^/channel/(?P[^/]+)/live/$')
def _on_channel_live(self, context, re_match):
- self.set_content_type(context, constants.content_type.VIDEOS)
+ self.set_content_type(context, content.VIDEOS)
result = []
channel_id = re_match.group('channel_id')
@@ -391,7 +396,7 @@ def _on_channel(self, context, re_match):
if method == 'channel' and not channel_id:
return False
- self.set_content_type(context, constants.content_type.VIDEOS)
+ self.set_content_type(context, content.VIDEOS)
resource_manager = self.get_resource_manager(context)
@@ -480,7 +485,7 @@ def _on_channel(self, context, re_match):
# noinspection PyUnusedLocal
@RegisterProviderPath('^/location/mine/$')
def _on_my_location(self, context, re_match):
- self.set_content_type(context, constants.content_type.FILES)
+ self.set_content_type(context, content.FILES)
create_path = context.create_resource_path
create_uri = context.create_uri
@@ -635,7 +640,7 @@ def _on_subscriptions(self, context, re_match):
subscriptions = yt_subscriptions.process(method, self, context)
if method == 'list':
- self.set_content_type(context, constants.content_type.FILES)
+ self.set_content_type(context, content.FILES)
channel_ids = []
for subscription in subscriptions:
channel_ids.append(subscription.get_channel_id())
@@ -818,9 +823,9 @@ def on_search(self, search_text, context, re_match):
safe_search = context.get_settings().safe_search()
if search_type == 'video':
- self.set_content_type(context, constants.content_type.VIDEOS)
+ self.set_content_type(context, content.VIDEOS)
else:
- self.set_content_type(context, constants.content_type.FILES)
+ self.set_content_type(context, content.FILES)
if page == 1 and search_type == 'video' and not event_type and not hide_folders:
if not channel_id and not location:
@@ -1228,7 +1233,7 @@ def on_root(self, context, re_match):
_ = self.get_client(context) # required for self.is_logged_in()
logged_in = self.is_logged_in()
- self.set_content_type(context, constants.content_type.FILES)
+ self.set_content_type(context, content.FILES)
result = []
@@ -1344,7 +1349,7 @@ def on_root(self, context, re_match):
else:
watch_history_item = DirectoryItem(
localize('watch_later'),
- create_uri([constants.paths.WATCH_LATER, 'list']),
+ create_uri([paths.WATCH_LATER, 'list']),
image=create_path('media', 'watch_later.png'),
fanart=self.get_fanart(context)
)
@@ -1399,7 +1404,7 @@ def on_root(self, context, re_match):
elif settings.use_local_history():
watch_history_item = DirectoryItem(
localize('history'),
- create_uri([constants.paths.HISTORY], params={'action': 'list'}),
+ create_uri([paths.HISTORY], params={'action': 'list'}),
image=create_path('media', 'history.png'),
fanart=self.get_fanart(context)
)
@@ -1501,18 +1506,18 @@ def on_root(self, context, re_match):
def set_content_type(context, content_type):
context.set_content_type(content_type)
context.add_sort_method(
- (constants.sort_method.UNSORTED, '%T \u2022 %P', '%D | %J'),
- (constants.sort_method.LABEL_IGNORE_THE, '%T \u2022 %P', '%D | %J'),
+ (sort.UNSORTED, '%T \u2022 %P', '%D | %J'),
+ (sort.LABEL_IGNORE_THE, '%T \u2022 %P', '%D | %J'),
)
- if content_type != constants.content_type.VIDEOS:
+ if content_type != content.VIDEOS:
return
context.add_sort_method(
- (constants.sort_method.PROGRAM_COUNT, '%T \u2022 %P | %D | %J', '%C'),
- (constants.sort_method.VIDEO_RATING, '%T \u2022 %P | %D | %J', '%R'),
- (constants.sort_method.DATE, '%T \u2022 %P | %D', '%J'),
- (constants.sort_method.DATEADDED, '%T \u2022 %P | %D', '%a'),
- (constants.sort_method.VIDEO_RUNTIME, '%T \u2022 %P | %J', '%D'),
- (constants.sort_method.TRACKNUM, '[%N. ]%T \u2022 %P', '%D | %J'),
+ (sort.PROGRAM_COUNT, '%T \u2022 %P | %D | %J', '%C'),
+ (sort.VIDEO_RATING, '%T \u2022 %P | %D | %J', '%R'),
+ (sort.DATE, '%T \u2022 %P | %D', '%J'),
+ (sort.DATEADDED, '%T \u2022 %P | %D', '%a'),
+ (sort.VIDEO_RUNTIME, '%T \u2022 %P | %J', '%D'),
+ (sort.TRACKNUM, '[%N. ]%T \u2022 %P', '%D | %J'),
)
def handle_exception(self, context, exception_to_handle):
From af1738e29519fb04abb32fa4e46e44fa59845c09 Mon Sep 17 00:00:00 2001
From: MoojMidge <56883549+MoojMidge@users.noreply.github.com>
Date: Mon, 25 Dec 2023 12:07:04 +1100
Subject: [PATCH 128/141] Remove use of hardcoded temp and data path
- make_dirs returns translated path on success
- Use to check/create/get in one go
- Rename native_path to addon_path
- Use os.path.join rather than hardcoding path seperators
- Fix unnecessarily nested os.path.join calls
---
.../kodion/constants/__init__.py | 4 +
.../kodion/context/abstract_context.py | 6 +-
.../kodion/context/xbmc/xbmc_context.py | 22 +++--
.../kodion/json_store/json_store.py | 29 ++++---
.../kodion/network/http_server.py | 7 +-
.../youtube_plugin/kodion/utils/methods.py | 39 +++++----
.../youtube_plugin/kodion/utils/monitor.py | 42 ++++++----
.../youtube/helper/subtitles.py | 81 +++++++++---------
.../youtube/helper/video_info.py | 39 +++++----
.../lib/youtube_plugin/youtube/provider.py | 82 ++++++++++++-------
10 files changed, 200 insertions(+), 151 deletions(-)
diff --git a/resources/lib/youtube_plugin/kodion/constants/__init__.py b/resources/lib/youtube_plugin/kodion/constants/__init__.py
index 819a9060e..e2ef4c566 100644
--- a/resources/lib/youtube_plugin/kodion/constants/__init__.py
+++ b/resources/lib/youtube_plugin/kodion/constants/__init__.py
@@ -19,9 +19,13 @@
ADDON_ID = 'plugin.video.youtube'
+DATA_PATH = 'special://profile/addon_data/{id}'.format(id=ADDON_ID)
+TEMP_PATH = 'special://temp/{id}'.format(id=ADDON_ID)
__all__ = (
'ADDON_ID',
+ 'DATA_PATH',
+ 'TEMP_PATH',
'content',
'paths',
'settings',
diff --git a/resources/lib/youtube_plugin/kodion/context/abstract_context.py b/resources/lib/youtube_plugin/kodion/context/abstract_context.py
index 3a789dbd9..9cd7fda80 100644
--- a/resources/lib/youtube_plugin/kodion/context/abstract_context.py
+++ b/resources/lib/youtube_plugin/kodion/context/abstract_context.py
@@ -143,7 +143,7 @@ def get_cache_path(self):
def get_playback_history(self):
if not self._playback_history:
uuid = self.get_access_manager().get_current_user_id()
- db_file = os.path.join(os.path.join(self.get_data_path(), 'playback'), str(uuid))
+ db_file = os.path.join(self.get_data_path(), 'playback', uuid)
self._playback_history = PlaybackHistory(db_file)
return self._playback_history
@@ -286,7 +286,7 @@ def get_data_path(self):
"""
raise NotImplementedError()
- def get_native_path(self):
+ def get_addon_path(self):
raise NotImplementedError()
def get_icon(self):
@@ -299,7 +299,7 @@ def create_resource_path(self, *args):
path_comps = []
for arg in args:
path_comps.extend(arg.split('/'))
- path = os.path.join(self.get_native_path(), 'resources', *path_comps)
+ path = os.path.join(self.get_addon_path(), 'resources', *path_comps)
return path
def get_uri(self):
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 d84843a6d..4c9e88aa8 100644
--- a/resources/lib/youtube_plugin/kodion/context/xbmc/xbmc_context.py
+++ b/resources/lib/youtube_plugin/kodion/context/xbmc/xbmc_context.py
@@ -31,7 +31,12 @@
from ...player.xbmc.xbmc_playlist import XbmcPlaylist
from ...settings.xbmc.xbmc_plugin_settings import XbmcPluginSettings
from ...ui.xbmc.xbmc_context_ui import XbmcContextUI
-from ...utils import current_system_version, loose_version, to_unicode
+from ...utils import (
+ current_system_version,
+ loose_version,
+ make_dirs,
+ to_unicode,
+)
class XbmcContext(AbstractContext):
@@ -290,17 +295,10 @@ def __init__(self, path='/', params=None, plugin_name='', plugin_id='', override
self._plugin_id = plugin_id or ADDON_ID
self._plugin_name = plugin_name or self._addon.getAddonInfo('name')
self._version = self._addon.getAddonInfo('version')
- self._native_path = xbmcvfs.translatePath(self._addon.getAddonInfo('path'))
+ self._addon_path = make_dirs(self._addon.getAddonInfo('path'))
+ self._data_path = make_dirs(self._addon.getAddonInfo('profile'))
self._settings = XbmcPluginSettings(self._addon)
- """
- Set the data path for this addon and create the folder
- """
- self._data_path = xbmcvfs.translatePath(self._addon.getAddonInfo('profile'))
-
- if not xbmcvfs.exists(self._data_path):
- xbmcvfs.mkdir(self._data_path)
-
def get_region(self):
pass # implement from abstract
@@ -388,8 +386,8 @@ def get_debug_path(self):
xbmcvfs.mkdir(self._debug_path)
return self._debug_path
- def get_native_path(self):
- return self._native_path
+ def get_addon_path(self):
+ return self._addon_path
def get_settings(self):
return self._settings
diff --git a/resources/lib/youtube_plugin/kodion/json_store/json_store.py b/resources/lib/youtube_plugin/kodion/json_store/json_store.py
index ca6baad78..baede56d4 100644
--- a/resources/lib/youtube_plugin/kodion/json_store/json_store.py
+++ b/resources/lib/youtube_plugin/kodion/json_store/json_store.py
@@ -13,28 +13,21 @@
import os
from io import open
-from ..compatibility import xbmcaddon, xbmcvfs
+from ..constants import DATA_PATH
from ..logger import log_debug, log_error
from ..utils import make_dirs, merge_dicts, to_unicode
-_addon_id = 'plugin.video.youtube'
-_addon = xbmcaddon.Addon(_addon_id)
-_addon_data_path = _addon.getAddonInfo('profile')
-del _addon
-
-
class JSONStore(object):
- def __init__(self, filename):
- self.base_path = xbmcvfs.translatePath(_addon_data_path)
+ BASE_PATH = make_dirs(DATA_PATH)
- if not xbmcvfs.exists(self.base_path) and not make_dirs(self.base_path):
- log_error('JSONStore.__init__ - invalid path:\n|{path}|'.format(
- path=self.base_path
- ))
- return
+ def __init__(self, filename):
+ if self.BASE_PATH:
+ self.filename = os.path.join(self.BASE_PATH, filename)
+ else:
+ log_error('JSONStore.__init__ - unable to access temp directory')
+ self.filename = None
- self.filename = os.path.join(self.base_path, filename)
self._data = {}
self.load()
self.set_defaults()
@@ -43,6 +36,9 @@ def set_defaults(self, reset=False):
raise NotImplementedError
def save(self, data, update=False, process=None):
+ if not self.filename:
+ return
+
if update:
data = merge_dicts(self._data, data)
if data == self._data:
@@ -75,6 +71,9 @@ def save(self, data, update=False, process=None):
self.set_defaults(reset=True)
def load(self, process=None):
+ if not self.filename:
+ return
+
log_debug('JSONStore.load - loading:\n|{filename}|'.format(
filename=self.filename
))
diff --git a/resources/lib/youtube_plugin/kodion/network/http_server.py b/resources/lib/youtube_plugin/kodion/network/http_server.py
index f665346da..e071c4818 100644
--- a/resources/lib/youtube_plugin/kodion/network/http_server.py
+++ b/resources/lib/youtube_plugin/kodion/network/http_server.py
@@ -26,6 +26,7 @@
xbmcgui,
xbmcvfs,
)
+from ..constants import TEMP_PATH
from ..logger import log_debug
from ..settings import Settings
@@ -42,7 +43,7 @@
class YouTubeProxyRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler, object):
- base_path = xbmcvfs.translatePath('special://temp/{0}'.format(_addon_id))
+ BASE_PATH = xbmcvfs.translatePath(TEMP_PATH)
chunk_size = 1024 * 64
local_ranges = (
'10.',
@@ -98,7 +99,7 @@ def do_GET(self):
self.wfile.write(client_json.encode('utf-8'))
elif _settings.use_mpd_videos() and stripped_path.endswith('.mpd'):
- file_path = os.path.join(self.base_path,
+ file_path = os.path.join(self.BASE_PATH,
self.path.strip('/').strip('\\'))
file_chunk = True
log_debug('HTTPServer: Request file path |{file_path}|'
@@ -200,7 +201,7 @@ def do_HEAD(self):
if not self.connection_allowed():
self.send_error(403)
elif _settings.use_mpd_videos() and self.path.endswith('.mpd'):
- file_path = os.path.join(self.base_path,
+ file_path = os.path.join(self.BASE_PATH,
self.path.strip('/').strip('\\'))
if not os.path.isfile(file_path):
response = ('File Not Found: |{proxy_path}| -> |{file_path}|'
diff --git a/resources/lib/youtube_plugin/kodion/utils/methods.py b/resources/lib/youtube_plugin/kodion/utils/methods.py
index 2dbfc8ced..3528f25c8 100644
--- a/resources/lib/youtube_plugin/kodion/utils/methods.py
+++ b/resources/lib/youtube_plugin/kodion/utils/methods.py
@@ -18,6 +18,7 @@
from math import floor, log
from ..compatibility import quote, xbmc, xbmcvfs
+from ..logger import log_error
__all__ = (
@@ -229,21 +230,31 @@ def print_items(items):
def make_dirs(path):
if not path.endswith('/'):
- path = ''.join([path, '/'])
+ path = ''.join((path, '/'))
+ succeeded = xbmcvfs.exists(path)
+
+ if succeeded:
+ return xbmcvfs.translatePath(path)
+
+ try:
+ succeeded = xbmcvfs.mkdirs(path)
+ except OSError:
+ pass
+
+ if succeeded:
+ return xbmcvfs.translatePath(path)
+
path = xbmcvfs.translatePath(path)
- if not xbmcvfs.exists(path):
- try:
- _ = xbmcvfs.mkdirs(path)
- except:
- pass
- if not xbmcvfs.exists(path):
- try:
- os.makedirs(path)
- except:
- pass
- return xbmcvfs.exists(path)
-
- return True
+ try:
+ os.makedirs(path)
+ succeeded = True
+ except OSError:
+ pass
+
+ if succeeded:
+ return path
+ log_error('Failed to create directory: |{0}|'.format(path))
+ return False
def find_video_id(plugin_path):
diff --git a/resources/lib/youtube_plugin/kodion/utils/monitor.py b/resources/lib/youtube_plugin/kodion/utils/monitor.py
index ecf05bb61..43439d719 100644
--- a/resources/lib/youtube_plugin/kodion/utils/monitor.py
+++ b/resources/lib/youtube_plugin/kodion/utils/monitor.py
@@ -15,6 +15,7 @@
import threading
from ..compatibility import unquote, xbmc, xbmcaddon, xbmcvfs
+from ..constants import TEMP_PATH
from ..logger import log_debug
from ..network import get_http_server, is_httpd_live
from ..settings import Settings
@@ -162,23 +163,30 @@ def restart_httpd(self):
def ping_httpd(self):
return is_httpd_live(port=self.httpd_port())
- def remove_temp_dir(self):
- path = xbmcvfs.translatePath('special://temp/%s' % self._addon_id)
+ @staticmethod
+ def remove_temp_dir():
+ temp_path = TEMP_PATH
+ succeeded = False
- if os.path.isdir(path):
+ if xbmcvfs.exists(temp_path):
try:
- xbmcvfs.rmdir(path, force=True)
- except:
+ succeeded = xbmcvfs.rmdir(temp_path, force=True)
+ except OSError:
pass
- if os.path.isdir(path):
- try:
- shutil.rmtree(path)
- except:
- pass
-
- if os.path.isdir(path):
- log_debug('Failed to remove directory: {path}'.format(
- path=path
- ))
- return False
- return True
+ else:
+ succeeded = True
+
+ if succeeded:
+ return True
+
+ temp_path = xbmcvfs.translatePath(TEMP_PATH)
+ try:
+ shutil.rmtree(temp_path)
+ succeeded = not xbmcvfs.exists(temp_path)
+ except OSError:
+ pass
+
+ if succeeded:
+ return True
+ log_debug('Failed to remove directory: {0}'.format(temp_path))
+ return False
diff --git a/resources/lib/youtube_plugin/youtube/helper/subtitles.py b/resources/lib/youtube_plugin/youtube/helper/subtitles.py
index 94a35d90f..5a011008b 100644
--- a/resources/lib/youtube_plugin/youtube/helper/subtitles.py
+++ b/resources/lib/youtube_plugin/youtube/helper/subtitles.py
@@ -8,6 +8,8 @@
from __future__ import absolute_import, division, unicode_literals
+import os
+
from ...kodion.compatibility import (
parse_qs,
unescape,
@@ -16,6 +18,7 @@
urlsplit,
xbmcvfs,
)
+from ...kodion.constants import TEMP_PATH
from ...kodion.network import BaseRequestsClass
from ...kodion.utils import make_dirs
@@ -27,8 +30,7 @@ class Subtitles(object):
LANG_CURR = 3
LANG_CURR_NO_ASR = 4
- BASE_PATH = 'special://temp/plugin.video.youtube/'
- SRT_FILE = ''.join([BASE_PATH, '%s.%s.srt'])
+ BASE_PATH = make_dirs(TEMP_PATH)
def __init__(self, context, video_id, captions, headers=None):
self.video_id = video_id
@@ -93,23 +95,6 @@ def __init__(self, context, video_id, captions, headers=None):
'is_asr': default_caption.get('kind') == 'asr',
}
- def srt_filename(self, sub_language):
- return self.SRT_FILE % (self.video_id, sub_language)
-
- def _write_file(self, filepath, contents):
- if not make_dirs(self.BASE_PATH):
- self._context.log_debug('Failed to create directories: %s' % self.BASE_PATH)
- return False
- self._context.log_debug('Writing subtitle file: %s' % filepath)
-
- try:
- with xbmcvfs.File(filepath, 'w') as srt_file:
- success = srt_file.write(contents)
- except (IOError, OSError):
- self._context.log_debug('File write failed for: %s' % filepath)
- return False
- return success
-
def _unescape(self, text):
try:
text = unescape(text)
@@ -201,10 +186,17 @@ def _prompt(self):
return []
def _get(self, lang_code='en', language=None, no_asr=False, download=None):
- filename = self.srt_filename(lang_code)
- if xbmcvfs.exists(filename):
- self._context.log_debug('Subtitle exists for: %s, filename: %s' % (lang_code, filename))
- return [filename]
+ filename = '.'.join((self.video_id, lang_code, 'srt'))
+ if not self.BASE_PATH:
+ self._context.log_error('Subtitles._get - '
+ 'unable to access temp directory')
+ return []
+
+ filepath = os.path.join(self.BASE_PATH, filename)
+ if xbmcvfs.exists(filepath):
+ self._context.log_debug('Subtitle exists for |{lang}| - |{file}|'
+ .format(lang=lang_code, file=filepath))
+ return [filepath]
if download is None:
download = self.pre_download
@@ -257,25 +249,34 @@ def _get(self, lang_code='en', language=None, no_asr=False, download=None):
('tlang', lang_code) if has_translation else (None, None),
)
- if subtitle_url:
- self._context.log_debug('Subtitle url: %s' % subtitle_url)
- if not download:
- return [subtitle_url]
-
- response = BaseRequestsClass().request(subtitle_url,
- headers=self.headers)
- if response.text:
- self._context.log_debug('Subtitle found for: %s' % lang_code)
- self._write_file(filename,
- bytearray(self._unescape(response.text),
- encoding='utf8',
- errors='ignore'))
- return [filename]
-
- self._context.log_debug('Failed to retrieve subtitles for: %s' % lang_code)
+ if not subtitle_url:
+ self._context.log_debug('No subtitles found for: %s' % lang_code)
+ return []
+
+ if not download:
+ return [subtitle_url]
+
+ response = BaseRequestsClass().request(
+ subtitle_url,
+ headers=self.headers,
+ error_info=('Failed to retrieve subtitles for: {lang}: {{exc}}'
+ .format(lang=lang_code))
+ )
+ if not response.text:
return []
- self._context.log_debug('No subtitles found for: %s' % lang_code)
+ output = bytearray(self._unescape(response.text),
+ encoding='utf8',
+ errors='ignore')
+ try:
+ with xbmcvfs.File(filepath, 'w') as srt_file:
+ success = srt_file.write(output)
+ except (IOError, OSError):
+ self._context.log_error('Subtitles._get - '
+ 'file write failed for: {file}'
+ .format(file=filepath))
+ if success:
+ return [filepath]
return []
@staticmethod
diff --git a/resources/lib/youtube_plugin/youtube/helper/video_info.py b/resources/lib/youtube_plugin/youtube/helper/video_info.py
index b494e20e0..94864a301 100644
--- a/resources/lib/youtube_plugin/youtube/helper/video_info.py
+++ b/resources/lib/youtube_plugin/youtube/helper/video_info.py
@@ -11,6 +11,7 @@
from __future__ import absolute_import, division, unicode_literals
import json
+import os
import random
import re
from traceback import format_stack
@@ -30,11 +31,14 @@
urlsplit,
xbmcvfs,
)
+from ...kodion.constants import TEMP_PATH, paths
from ...kodion.network import is_httpd_live
from ...kodion.utils import make_dirs
class VideoInfo(YouTubeRequestClient):
+ BASE_PATH = make_dirs(TEMP_PATH)
+
FORMAT = {
# === Non-DASH ===
'5': {'container': 'flv',
@@ -1168,7 +1172,7 @@ def _get_video_info(self):
'default': ('https://i.ytimg.com/vi/{0}/default{1}.jpg'
.format(self.video_id, is_live)),
},
- 'subtitles': captions or [],
+ 'subtitles': captions,
}
if _settings.use_remote_history():
@@ -1609,10 +1613,9 @@ def _generate_mpd_manifest(self, video_data, audio_data, license_url):
if not video_data or not audio_data:
return None, None
- basepath = 'special://temp/plugin.video.youtube/'
- if not make_dirs(basepath):
- self._context.log_debug('Failed to create temp directory: {0}'
- .format(basepath))
+ if not self.BASE_PATH:
+ self._context.log_error('VideoInfo._generate_mpd_manifest - '
+ 'unable to access temp directory')
return None, None
def _filter_group(previous_group, previous_stream, item):
@@ -1671,7 +1674,7 @@ def _filter_group(previous_group, previous_stream, item):
'multi_lang': False,
}
- out_list = [
+ output = [
'\n'
'", ">"))
- out_list.extend((
+ output.extend((
'\t\t\t\n'
@@ -1778,7 +1781,7 @@ def _filter_group(previous_group, previous_stream, item):
num_streams = len(streams)
if media_type == 'audio':
- out_list.extend(((
+ output.extend(((
'\t\t\t\n')
+ output.append('\t\t\n')
set_id += 1
- out_list.append('\t\n'
- '\n')
- out = ''.join(out_list)
+ output.append('\t\n'
+ '\n')
+ output = ''.join(output)
if len(languages.difference({'', 'und'})) > 1:
main_stream['multi_lang'] = True
if roles.difference({'', 'main', 'dub'}):
main_stream['multi_audio'] = True
- filepath = '{0}{1}.mpd'.format(basepath, self.video_id)
+ filename = '.'.join((self.video_id, 'mpd'))
+ filepath = os.path.join(self.BASE_PATH, filename)
try:
with xbmcvfs.File(filepath, 'w') as mpd_file:
- success = mpd_file.write(str(out))
+ success = mpd_file.write(output)
except (IOError, OSError):
+ self._context.log_error('VideoInfo._generate_mpd_manifest - '
+ 'file write failed for: {file}'
+ .format(file=filepath))
success = False
if success:
return 'http://{0}:{1}/{2}.mpd'.format(
diff --git a/resources/lib/youtube_plugin/youtube/provider.py b/resources/lib/youtube_plugin/youtube/provider.py
index dab29cec9..8d6458c7f 100644
--- a/resources/lib/youtube_plugin/youtube/provider.py
+++ b/resources/lib/youtube_plugin/youtube/provider.py
@@ -36,6 +36,8 @@
from ..kodion import AbstractProvider, RegisterProviderPath
from ..kodion.compatibility import xbmcaddon, xbmcvfs
from ..kodion.constants import (
+ DATA_PATH,
+ TEMP_PATH,
content,
paths,
sort,
@@ -1024,38 +1026,56 @@ def maintenance_actions(self, context, re_match):
'settings_xml': 'settings.xml',
'api_keys': 'api_keys.json',
'access_manager': 'access_manager.json',
- 'temp_files': 'special://temp/plugin.video.youtube/'}
- _file = _maint_files.get(maint_type, '')
- success = False
- if _file:
- if 'sqlite' in _file:
- _file_w_path = os.path.join(context.get_cache_path(), _file)
- elif maint_type == 'temp_files':
- _file_w_path = _file
- elif _file == 'playback_history':
- _file = ''.join([str(context.get_access_manager().get_current_user_id()), '.sqlite'])
- _file_w_path = os.path.join(os.path.join(context.get_data_path(), 'playback'), _file)
+ 'temp_files': TEMP_PATH}
+ _file = _maint_files.get(maint_type)
+ succeeded = False
+
+ if not _file:
+ return
+
+ data_path = xbmcvfs.translatePath(DATA_PATH)
+ if 'sqlite' in _file:
+ _file_w_path = os.path.join(data_path, 'kodion', _file)
+ elif maint_type == 'temp_files':
+ _file_w_path = _file
+ elif maint_type == 'playback_history':
+ _file = ''.join((
+ context.get_access_manager().get_current_user_id(),
+ '.sqlite'
+ ))
+ _file_w_path = os.path.join(data_path, 'playback', _file)
+ else:
+ _file_w_path = os.path.join(data_path, _file)
+
+ if not ui.on_delete_content(_file):
+ return
+
+ if maint_type == 'temp_files':
+ temp_path = _file_w_path
+
+ if xbmcvfs.exists(temp_path):
+ try:
+ succeeded = xbmcvfs.rmdir(temp_path, force=True)
+ except OSError:
+ pass
else:
- _file_w_path = os.path.join(context.get_data_path(), _file)
- if ui.on_delete_content(_file):
- if maint_type == 'temp_files':
- _trans_path = xbmcvfs.translatePath(_file_w_path)
- try:
- xbmcvfs.rmdir(_trans_path, force=True)
- except:
- pass
- if xbmcvfs.exists(_trans_path):
- try:
- shutil.rmtree(_trans_path)
- except:
- pass
- success = not xbmcvfs.exists(_trans_path)
- elif _file_w_path:
- success = xbmcvfs.delete(_file_w_path)
- if success:
- ui.show_notification(localize('succeeded'))
- else:
- ui.show_notification(localize('failed'))
+ succeeded = True
+
+ if not succeeded:
+ temp_path = xbmcvfs.translatePath(_file_w_path)
+ try:
+ shutil.rmtree(temp_path)
+ succeeded = not xbmcvfs.exists(temp_path)
+ except OSError:
+ pass
+
+ elif _file_w_path:
+ succeeded = xbmcvfs.delete(_file_w_path)
+
+ if succeeded:
+ ui.show_notification(localize('succeeded'))
+ else:
+ ui.show_notification(localize('failed'))
elif action == 'install' and maint_type == 'inputstreamhelper':
if context.get_system_version().get_version()[0] >= 17:
try:
From 4307c02d34a3a9504538425d3f3c0ae4e5e7c7cc Mon Sep 17 00:00:00 2001
From: MoojMidge <56883549+MoojMidge@users.noreply.github.com>
Date: Mon, 25 Dec 2023 12:26:01 +1100
Subject: [PATCH 129/141] Remove use of hardcoded addon id and path
---
.../youtube_plugin/kodion/constants/__init__.py | 2 ++
.../kodion/json_store/access_manager.py | 6 +++---
resources/lib/youtube_plugin/kodion/logger.py | 15 +++++++--------
.../youtube_plugin/kodion/network/http_server.py | 9 ++++-----
.../youtube_plugin/kodion/network/requests.py | 3 ++-
.../kodion/ui/xbmc/xbmc_context_ui.py | 13 ++++++++-----
.../lib/youtube_plugin/kodion/utils/monitor.py | 16 ++++++----------
resources/lib/youtube_plugin/youtube/provider.py | 16 ++++++++--------
resources/lib/youtube_registration.py | 6 ++++--
9 files changed, 44 insertions(+), 42 deletions(-)
diff --git a/resources/lib/youtube_plugin/kodion/constants/__init__.py b/resources/lib/youtube_plugin/kodion/constants/__init__.py
index e2ef4c566..742c55fe8 100644
--- a/resources/lib/youtube_plugin/kodion/constants/__init__.py
+++ b/resources/lib/youtube_plugin/kodion/constants/__init__.py
@@ -19,11 +19,13 @@
ADDON_ID = 'plugin.video.youtube'
+ADDON_PATH = 'special://home/addons/{id}'.format(id=ADDON_ID)
DATA_PATH = 'special://profile/addon_data/{id}'.format(id=ADDON_ID)
TEMP_PATH = 'special://temp/{id}'.format(id=ADDON_ID)
__all__ = (
'ADDON_ID',
+ 'ADDON_PATH',
'DATA_PATH',
'TEMP_PATH',
'content',
diff --git a/resources/lib/youtube_plugin/kodion/json_store/access_manager.py b/resources/lib/youtube_plugin/kodion/json_store/access_manager.py
index 82eb9febf..544a830b6 100644
--- a/resources/lib/youtube_plugin/kodion/json_store/access_manager.py
+++ b/resources/lib/youtube_plugin/kodion/json_store/access_manager.py
@@ -13,6 +13,7 @@
from hashlib import md5
from .json_store import JSONStore
+from ..constants import ADDON_ID
__author__ = 'bromix'
@@ -34,8 +35,7 @@ def __init__(self, context):
self._settings = context.get_settings()
access_manager_data = self._data['access_manager']
self._user = access_manager_data.get('current_user', 0)
- self._last_origin = access_manager_data.get('last_origin',
- 'plugin.video.youtube')
+ self._last_origin = access_manager_data.get('last_origin', ADDON_ID)
def set_defaults(self, reset=False):
data = {} if reset else self.get_data()
@@ -56,7 +56,7 @@ def set_defaults(self, reset=False):
if 'current_user' not in data['access_manager']:
data['access_manager']['current_user'] = 0
if 'last_origin' not in data['access_manager']:
- data['access_manager']['last_origin'] = 'plugin.video.youtube'
+ data['access_manager']['last_origin'] = ADDON_ID
if 'developers' not in data['access_manager']:
data['access_manager']['developers'] = {}
diff --git a/resources/lib/youtube_plugin/kodion/logger.py b/resources/lib/youtube_plugin/kodion/logger.py
index 0940faf42..03cde49ab 100644
--- a/resources/lib/youtube_plugin/kodion/logger.py
+++ b/resources/lib/youtube_plugin/kodion/logger.py
@@ -11,6 +11,7 @@
from __future__ import absolute_import, division, unicode_literals
from .compatibility import xbmc, xbmcaddon
+from .constants import ADDON_ID
DEBUG = xbmc.LOGDEBUG
@@ -22,31 +23,29 @@
SEVERE = FATAL
NONE = xbmc.LOGNONE
-_ADDON_ID = 'plugin.video.youtube'
-
-def log(text, log_level=DEBUG, addon_id=_ADDON_ID):
+def log(text, log_level=DEBUG, addon_id=ADDON_ID):
if not addon_id:
addon_id = xbmcaddon.Addon().getAddonInfo('id')
log_line = '[%s] %s' % (addon_id, text)
xbmc.log(msg=log_line, level=log_level)
-def log_debug(text, addon_id=_ADDON_ID):
+def log_debug(text, addon_id=ADDON_ID):
log(text, DEBUG, addon_id)
-def log_info(text, addon_id=_ADDON_ID):
+def log_info(text, addon_id=ADDON_ID):
log(text, INFO, addon_id)
-def log_notice(text, addon_id=_ADDON_ID):
+def log_notice(text, addon_id=ADDON_ID):
log(text, NOTICE, addon_id)
-def log_warning(text, addon_id=_ADDON_ID):
+def log_warning(text, addon_id=ADDON_ID):
log(text, WARNING, addon_id)
-def log_error(text, addon_id=_ADDON_ID):
+def log_error(text, addon_id=ADDON_ID):
log(text, ERROR, addon_id)
diff --git a/resources/lib/youtube_plugin/kodion/network/http_server.py b/resources/lib/youtube_plugin/kodion/network/http_server.py
index e071c4818..400b71177 100644
--- a/resources/lib/youtube_plugin/kodion/network/http_server.py
+++ b/resources/lib/youtube_plugin/kodion/network/http_server.py
@@ -26,13 +26,12 @@
xbmcgui,
xbmcvfs,
)
-from ..constants import TEMP_PATH
+from ..constants import ADDON_ID, TEMP_PATH
from ..logger import log_debug
from ..settings import Settings
-_addon_id = 'plugin.video.youtube'
-_addon = xbmcaddon.Addon(_addon_id)
+_addon = xbmcaddon.Addon(ADDON_ID)
_settings = Settings(_addon)
_i18n = _addon.getLocalizedString
_addon_name = _addon.getAddonInfo('name')
@@ -226,12 +225,12 @@ def do_POST(self):
elif self.path.startswith('/widevine'):
home = xbmcgui.Window(10000)
- lic_url = home.getProperty('plugin.video.youtube-license_url')
+ lic_url = home.getProperty('-'.join((ADDON_ID, 'license_url')))
if not lic_url:
self.send_error(404)
return
- lic_token = home.getProperty('plugin.video.youtube-license_token')
+ lic_token = home.getProperty('-'.join((ADDON_ID, 'license_token')))
if not lic_token:
self.send_error(403)
return
diff --git a/resources/lib/youtube_plugin/kodion/network/requests.py b/resources/lib/youtube_plugin/kodion/network/requests.py
index c5585b664..2d4c911d1 100644
--- a/resources/lib/youtube_plugin/kodion/network/requests.py
+++ b/resources/lib/youtube_plugin/kodion/network/requests.py
@@ -17,11 +17,12 @@
from requests.exceptions import InvalidJSONError, RequestException
from ..compatibility import xbmcaddon
+from ..constants import ADDON_ID
from ..logger import log_error
from ..settings import Settings
-_settings = Settings(xbmcaddon.Addon(id='plugin.video.youtube'))
+_settings = Settings(xbmcaddon.Addon(id=ADDON_ID))
class BaseRequestsClass(object):
diff --git a/resources/lib/youtube_plugin/kodion/ui/xbmc/xbmc_context_ui.py b/resources/lib/youtube_plugin/kodion/ui/xbmc/xbmc_context_ui.py
index da63599a7..fddb052aa 100644
--- a/resources/lib/youtube_plugin/kodion/ui/xbmc/xbmc_context_ui.py
+++ b/resources/lib/youtube_plugin/kodion/ui/xbmc/xbmc_context_ui.py
@@ -14,6 +14,7 @@
from ..abstract_context_ui import AbstractContextUI
from ... import utils
from ...compatibility import xbmc, xbmcgui
+from ...constants import ADDON_ID, ADDON_PATH
class XbmcContextUI(AbstractContextUI):
@@ -136,8 +137,10 @@ def open_settings(self):
self._xbmc_addon.openSettings()
def refresh_container(self):
- script_uri = "{}/resources/lib/youtube_plugin/refresh.py".format(self._xbmc_addon.getAddonInfo('path'))
- xbmc.executebuiltin('RunScript(%s)' % script_uri)
+ xbmc.executebuiltin(
+ 'RunScript({path}/resources/lib/youtube_plugin/refresh.py)'
+ .format(path=ADDON_PATH)
+ )
@staticmethod
def get_info_label(value):
@@ -145,17 +148,17 @@ def get_info_label(value):
@staticmethod
def set_property(property_id, value):
- property_id = ''.join(['plugin.video.youtube-', property_id])
+ property_id = '-'.join((ADDON_ID, property_id))
xbmcgui.Window(10000).setProperty(property_id, value)
@staticmethod
def get_property(property_id):
- property_id = ''.join(['plugin.video.youtube-', property_id])
+ property_id = '-'.join((ADDON_ID, property_id))
return xbmcgui.Window(10000).getProperty(property_id)
@staticmethod
def clear_property(property_id):
- property_id = ''.join(['plugin.video.youtube-', property_id])
+ property_id = '-'.join((ADDON_ID, property_id))
xbmcgui.Window(10000).clearProperty(property_id)
@staticmethod
diff --git a/resources/lib/youtube_plugin/kodion/utils/monitor.py b/resources/lib/youtube_plugin/kodion/utils/monitor.py
index 43439d719..fc1495d49 100644
--- a/resources/lib/youtube_plugin/kodion/utils/monitor.py
+++ b/resources/lib/youtube_plugin/kodion/utils/monitor.py
@@ -15,15 +15,14 @@
import threading
from ..compatibility import unquote, xbmc, xbmcaddon, xbmcvfs
-from ..constants import TEMP_PATH
+from ..constants import ADDON_ID, TEMP_PATH
from ..logger import log_debug
from ..network import get_http_server, is_httpd_live
from ..settings import Settings
class YouTubeMonitor(xbmc.Monitor):
- _addon_id = 'plugin.video.youtube'
- _settings = Settings(xbmcaddon.Addon(_addon_id))
+ _settings = Settings(xbmcaddon.Addon(ADDON_ID))
# noinspection PyUnusedLocal,PyMissingConstructor
def __init__(self, *args, **kwargs):
@@ -40,8 +39,7 @@ def __init__(self, *args, **kwargs):
super(YouTubeMonitor, self).__init__()
def onNotification(self, sender, method, data):
- if (sender == 'plugin.video.youtube'
- and method.endswith('.check_settings')):
+ if sender == ADDON_ID and method.endswith('.check_settings'):
if not isinstance(data, dict):
data = json.loads(data)
data = json.loads(unquote(data[0]))
@@ -82,12 +80,12 @@ def onNotification(self, sender, method, data):
else:
self.start_httpd()
- elif sender == 'plugin.video.youtube':
+ elif sender == ADDON_ID:
log_debug('onNotification: |unhandled method| -> |{method}|'
.format(method=method))
def onSettingsChanged(self):
- self._settings.flush(xbmcaddon.Addon(self._addon_id))
+ self._settings.flush(xbmcaddon.Addon(ADDON_ID))
data = {
'use_httpd': (self._settings.use_mpd_videos()
@@ -96,9 +94,7 @@ def onSettingsChanged(self):
'whitelist': self._settings.httpd_whitelist(),
'httpd_address': self._settings.httpd_listen()
}
- self.onNotification('plugin.video.youtube',
- 'Other.check_settings',
- data)
+ self.onNotification(ADDON_ID, 'Other.check_settings', data)
def use_httpd(self):
return self._use_httpd
diff --git a/resources/lib/youtube_plugin/youtube/provider.py b/resources/lib/youtube_plugin/youtube/provider.py
index 8d6458c7f..5c230b422 100644
--- a/resources/lib/youtube_plugin/youtube/provider.py
+++ b/resources/lib/youtube_plugin/youtube/provider.py
@@ -36,6 +36,7 @@
from ..kodion import AbstractProvider, RegisterProviderPath
from ..kodion.compatibility import xbmcaddon, xbmcvfs
from ..kodion.constants import (
+ ADDON_ID,
DATA_PATH,
TEMP_PATH,
content,
@@ -135,14 +136,13 @@ def get_client(self, context):
refresh_tokens = []
if dev_id:
- dev_origin = dev_config.get('origin') if dev_config.get('origin') else dev_id
- if api_last_origin != dev_origin:
- context.log_debug('API key origin changed, clearing cache. |%s|' % dev_origin)
- access_manager.set_last_origin(dev_origin)
- self.get_resource_manager(context).clear()
- elif api_last_origin != 'plugin.video.youtube':
- context.log_debug('API key origin changed, clearing cache. |plugin.video.youtube|')
- access_manager.set_last_origin('plugin.video.youtube')
+ origin = dev_config.get('origin') if dev_config.get('origin') else dev_id
+ else:
+ origin = ADDON_ID
+
+ if api_last_origin != origin:
+ context.log_debug('API key origin changed, clearing cache. |%s|' % origin)
+ access_manager.set_last_origin(origin)
self.get_resource_manager(context).clear()
if dev_id:
diff --git a/resources/lib/youtube_registration.py b/resources/lib/youtube_registration.py
index a5585082d..4a8dbb7fc 100644
--- a/resources/lib/youtube_registration.py
+++ b/resources/lib/youtube_registration.py
@@ -10,8 +10,10 @@
from __future__ import absolute_import, division, unicode_literals
from base64 import b64encode
-from youtube_plugin.kodion.json_store import APIKeyStore
+
+from youtube_plugin.kodion.constants import ADDON_ID
from youtube_plugin.kodion.context import Context
+from youtube_plugin.kodion.json_store import APIKeyStore
def register_api_keys(addon_id, api_key, client_id, client_secret):
@@ -44,7 +46,7 @@ def register_api_keys(addon_id, api_key, client_id, client_secret):
context = Context()
- if not addon_id or addon_id == 'plugin.video.youtube':
+ if not addon_id or addon_id == ADDON_ID:
context.log_error('Register API Keys: |%s| Invalid addon_id' % addon_id)
return
From 82b9525921507f059db57584a530adfe600324d6 Mon Sep 17 00:00:00 2001
From: MoojMidge <56883549+MoojMidge@users.noreply.github.com>
Date: Mon, 25 Dec 2023 16:03:52 +1100
Subject: [PATCH 130/141] Update item creation
- Add resource path constant
- Removes multiple function calls to set default fanart
- Simplify parameter creation/updates
- Use plugin path constants rather than hardcoded paths
- Use fanart parameter in *Item.__init__
- Use action parameter in DirectoryItem.__init__
- Add new location icon for My Location directory item
- Add new icons with consistent sizing for the various search functions
---
.../kodion/abstract_provider.py | 11 +-
.../kodion/constants/__init__.py | 4 +
.../youtube_plugin/kodion/items/base_item.py | 21 +-
.../kodion/items/favorites_item.py | 16 +-
.../kodion/items/new_search_item.py | 34 +-
.../kodion/items/next_page_item.py | 14 +-
.../kodion/items/search_history_item.py | 14 +-
.../kodion/items/search_item.py | 26 +-
.../kodion/items/watch_later_item.py | 16 +-
.../youtube_plugin/youtube/client/youtube.py | 4 +-
.../lib/youtube_plugin/youtube/helper/tv.py | 61 ++--
.../youtube/helper/url_to_item_converter.py | 8 +-
.../youtube_plugin/youtube/helper/utils.py | 4 +-
.../lib/youtube_plugin/youtube/helper/v3.py | 19 +-
.../youtube/helper/yt_playlist.py | 9 +-
.../youtube/helper/yt_specials.py | 2 -
.../lib/youtube_plugin/youtube/provider.py | 320 +++++++++---------
resources/media/incognito_search.png | Bin 0 -> 4725 bytes
resources/media/location.png | Bin 0 -> 5526 bytes
resources/media/new_search.png | Bin 4673 -> 4394 bytes
resources/media/quick_search.png | Bin 0 -> 4453 bytes
resources/media/search.png | Bin 4282 -> 4246 bytes
22 files changed, 305 insertions(+), 278 deletions(-)
create mode 100644 resources/media/incognito_search.png
create mode 100644 resources/media/location.png
create mode 100644 resources/media/quick_search.png
diff --git a/resources/lib/youtube_plugin/kodion/abstract_provider.py b/resources/lib/youtube_plugin/kodion/abstract_provider.py
index f68cab2b0..e8983dd8f 100644
--- a/resources/lib/youtube_plugin/kodion/abstract_provider.py
+++ b/resources/lib/youtube_plugin/kodion/abstract_provider.py
@@ -76,9 +76,6 @@ def __init__(self):
if path:
self.register_path(path, method_name)
- def get_alternative_fanart(self, context):
- return context.get_fanart()
-
def register_path(self, re_path, method_name):
"""
Registers a new method by name (string) for the given regular expression
@@ -330,7 +327,9 @@ def _internal_search(self, context, re_match):
location = context.get_param('location', False)
# 'New Search...'
- new_search_item = NewSearchItem(context, fanart=self.get_alternative_fanart(context), location=location)
+ new_search_item = NewSearchItem(
+ context, location=location
+ )
result.append(new_search_item)
for search in search_history.get_items():
@@ -339,7 +338,9 @@ def _internal_search(self, context, re_match):
search = search.get_name()
# we create a new instance of the SearchItem
- search_history_item = SearchHistoryItem(context, search, fanart=self.get_alternative_fanart(context), location=location)
+ search_history_item = SearchHistoryItem(
+ context, search, location=location
+ )
result.append(search_history_item)
if search_history.is_empty():
diff --git a/resources/lib/youtube_plugin/kodion/constants/__init__.py b/resources/lib/youtube_plugin/kodion/constants/__init__.py
index 742c55fe8..d392adc67 100644
--- a/resources/lib/youtube_plugin/kodion/constants/__init__.py
+++ b/resources/lib/youtube_plugin/kodion/constants/__init__.py
@@ -21,12 +21,16 @@
ADDON_ID = 'plugin.video.youtube'
ADDON_PATH = 'special://home/addons/{id}'.format(id=ADDON_ID)
DATA_PATH = 'special://profile/addon_data/{id}'.format(id=ADDON_ID)
+MEDIA_PATH = ADDON_PATH + '/resources/media'
+RESOURCE_PATH = ADDON_PATH + '/resources'
TEMP_PATH = 'special://temp/{id}'.format(id=ADDON_ID)
__all__ = (
'ADDON_ID',
'ADDON_PATH',
'DATA_PATH',
+ 'MEDIA_PATH',
+ 'RESOURCE_PATH',
'TEMP_PATH',
'content',
'paths',
diff --git a/resources/lib/youtube_plugin/kodion/items/base_item.py b/resources/lib/youtube_plugin/kodion/items/base_item.py
index 9e71bb88a..85c46ec3b 100644
--- a/resources/lib/youtube_plugin/kodion/items/base_item.py
+++ b/resources/lib/youtube_plugin/kodion/items/base_item.py
@@ -15,6 +15,7 @@
from hashlib import md5
from ..compatibility import unescape
+from ..constants import MEDIA_PATH
class BaseItem(object):
@@ -33,10 +34,11 @@ def __init__(self, name, uri, image='', fanart=''):
self._uri = uri
- self._image = ''
+ self._image = None
self.set_image(image)
+ self._fanart = None
+ self.set_fanart(fanart)
- self._fanart = fanart
self._context_menu = None
self._replace_context_menu = False
self._added_utc = None
@@ -96,8 +98,12 @@ def get_uri(self):
return self._uri
def set_image(self, image):
- if image is None:
+ if not image:
self._image = ''
+ return
+
+ if '{media}/' in image:
+ self._image = image.format(media=MEDIA_PATH)
else:
self._image = image
@@ -105,7 +111,14 @@ def get_image(self):
return self._image
def set_fanart(self, fanart):
- self._fanart = fanart
+ if not fanart:
+ self._fanart = '{0}/fanart.jpg'.format(MEDIA_PATH)
+ return
+
+ if '{media}/' in fanart:
+ self._fanart = fanart.format(media=MEDIA_PATH)
+ else:
+ self._fanart = fanart
def get_fanart(self):
return self._fanart
diff --git a/resources/lib/youtube_plugin/kodion/items/favorites_item.py b/resources/lib/youtube_plugin/kodion/items/favorites_item.py
index 8e25552bb..580c343ca 100644
--- a/resources/lib/youtube_plugin/kodion/items/favorites_item.py
+++ b/resources/lib/youtube_plugin/kodion/items/favorites_item.py
@@ -11,20 +11,22 @@
from __future__ import absolute_import, division, unicode_literals
from .directory_item import DirectoryItem
-from .. import constants
+from ..constants import paths
class FavoritesItem(DirectoryItem):
- def __init__(self, context, alt_name=None, image=None, fanart=None):
- name = alt_name
+ def __init__(self, context, name=None, image=None, fanart=None):
if not name:
name = context.localize('favorites')
if image is None:
- image = context.create_resource_path('media/favorites.png')
+ image = '{media}/favorites.png'
+
+ super(FavoritesItem, self).__init__(name,
+ context.create_uri(
+ [paths.FAVORITES, 'list']
+ ),
+ image=image)
- super(FavoritesItem, self).__init__(name, context.create_uri([constants.paths.FAVORITES, 'list']), image=image)
if fanart:
self.set_fanart(fanart)
- else:
- self.set_fanart(context.get_fanart())
diff --git a/resources/lib/youtube_plugin/kodion/items/new_search_item.py b/resources/lib/youtube_plugin/kodion/items/new_search_item.py
index 18f79c97e..73a052e98 100644
--- a/resources/lib/youtube_plugin/kodion/items/new_search_item.py
+++ b/resources/lib/youtube_plugin/kodion/items/new_search_item.py
@@ -11,30 +11,40 @@
from __future__ import absolute_import, division, unicode_literals
from .directory_item import DirectoryItem
-from .. import constants
+from ..constants import paths
class NewSearchItem(DirectoryItem):
- def __init__(self, context, alt_name=None, image=None, fanart=None, incognito=False, channel_id='', addon_id='', location=False):
- name = alt_name
+ def __init__(self,
+ context,
+ name=None,
+ image=None,
+ fanart=None,
+ incognito=False,
+ channel_id='',
+ addon_id='',
+ location=False):
if not name:
name = context.get_ui().bold(context.localize('search.new'))
if image is None:
- image = context.create_resource_path('media/new_search.png')
+ image = '{media}/new_search.png'
- item_params = {}
+ params = {}
if addon_id:
- item_params.update({'addon_id': addon_id})
+ params['addon_id'] = addon_id
if incognito:
- item_params.update({'incognito': incognito})
+ params['incognito'] = incognito
if channel_id:
- item_params.update({'channel_id': channel_id})
+ params['channel_id'] = channel_id
if location:
- item_params.update({'location': location})
+ params['location'] = location
+
+ super(NewSearchItem, self).__init__(name,
+ context.create_uri(
+ [paths.SEARCH, 'input'],
+ params=params
+ ), image=image)
- super(NewSearchItem, self).__init__(name, context.create_uri([constants.paths.SEARCH, 'input'], params=item_params), image=image)
if fanart:
self.set_fanart(fanart)
- else:
- self.set_fanart(context.get_fanart())
diff --git a/resources/lib/youtube_plugin/kodion/items/next_page_item.py b/resources/lib/youtube_plugin/kodion/items/next_page_item.py
index ee266e52b..a73acb1c9 100644
--- a/resources/lib/youtube_plugin/kodion/items/next_page_item.py
+++ b/resources/lib/youtube_plugin/kodion/items/next_page_item.py
@@ -15,17 +15,19 @@
class NextPageItem(DirectoryItem):
def __init__(self, context, current_page=1, image=None, fanart=None):
- new_params = {}
- new_params.update(context.get_params())
- new_params['page'] = current_page + 1
+ new_params = dict(context.get_params(), page=(current_page + 1))
name = context.localize('next_page', 'Next Page')
if name.find('%d') != -1:
name %= current_page + 1
- super(NextPageItem, self).__init__(name, context.create_uri(context.get_path(), new_params), image=image)
+ super(NextPageItem, self).__init__(name,
+ context.create_uri(
+ context.get_path(),
+ new_params
+ ),
+ image=image)
+
if fanart:
self.set_fanart(fanart)
- else:
- self.set_fanart(context.get_fanart())
self.next_page = True
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 b2aeab883..afd349683 100644
--- a/resources/lib/youtube_plugin/kodion/items/search_history_item.py
+++ b/resources/lib/youtube_plugin/kodion/items/search_history_item.py
@@ -12,23 +12,27 @@
from . import menu_items
from .directory_item import DirectoryItem
-from ..constants.const_paths import SEARCH
+from ..constants import paths
class SearchHistoryItem(DirectoryItem):
def __init__(self, context, query, image=None, fanart=None, location=False):
if image is None:
- image = context.create_resource_path('media/search.png')
+ image = '{media}/search.png'
params = {'q': query}
if location:
params['location'] = location
- super(SearchHistoryItem, self).__init__(query, context.create_uri([SEARCH, 'query'], params=params), image=image)
+ super(SearchHistoryItem, self).__init__(query,
+ context.create_uri(
+ [paths.SEARCH, 'query'],
+ params=params
+ ),
+ image=image)
+
if fanart:
self.set_fanart(fanart)
- else:
- self.set_fanart(context.get_fanart())
context_menu = [
menu_items.search_remove(context, query),
diff --git a/resources/lib/youtube_plugin/kodion/items/search_item.py b/resources/lib/youtube_plugin/kodion/items/search_item.py
index 2ff9bbd1f..e505b67be 100644
--- a/resources/lib/youtube_plugin/kodion/items/search_item.py
+++ b/resources/lib/youtube_plugin/kodion/items/search_item.py
@@ -11,22 +11,32 @@
from __future__ import absolute_import, division, unicode_literals
from .directory_item import DirectoryItem
-from ..constants.const_paths import SEARCH
+from ..constants import paths
class SearchItem(DirectoryItem):
- def __init__(self, context, alt_name=None, image=None, fanart=None, location=False):
- name = alt_name
+ def __init__(self,
+ context,
+ name=None,
+ image=None,
+ fanart=None,
+ location=False):
if not name:
name = context.localize('search')
if image is None:
- image = context.create_resource_path('media/search.png')
+ image = '{media}/search.png'
- params = {'location': location} if location else {}
+ params = {}
+ if location:
+ params['location'] = location
+
+ super(SearchItem, self).__init__(name,
+ context.create_uri(
+ [paths.SEARCH, 'list'],
+ params=params
+ ),
+ image=image)
- super(SearchItem, self).__init__(name, context.create_uri([SEARCH, 'list'], params=params), image=image)
if fanart:
self.set_fanart(fanart)
- else:
- self.set_fanart(context.get_fanart())
diff --git a/resources/lib/youtube_plugin/kodion/items/watch_later_item.py b/resources/lib/youtube_plugin/kodion/items/watch_later_item.py
index 01cfc0455..6ae79116e 100644
--- a/resources/lib/youtube_plugin/kodion/items/watch_later_item.py
+++ b/resources/lib/youtube_plugin/kodion/items/watch_later_item.py
@@ -11,20 +11,22 @@
from __future__ import absolute_import, division, unicode_literals
from .directory_item import DirectoryItem
-from .. import constants
+from ..constants import paths
class WatchLaterItem(DirectoryItem):
- def __init__(self, context, alt_name=None, image=None, fanart=None):
- name = alt_name
+ def __init__(self, context, name=None, image=None, fanart=None):
if not name:
name = context.localize('watch_later')
if image is None:
- image = context.create_resource_path('media/watch_later.png')
+ image = '{media}/watch_later.png'
+
+ super(WatchLaterItem, self).__init__(name,
+ context.create_uri(
+ [paths.WATCH_LATER, 'list']
+ ),
+ image=image)
- super(WatchLaterItem, self).__init__(name, context.create_uri([constants.paths.WATCH_LATER, 'list']), image=image)
if fanart:
self.set_fanart(fanart)
- else:
- self.set_fanart(context.get_fanart())
diff --git a/resources/lib/youtube_plugin/youtube/client/youtube.py b/resources/lib/youtube_plugin/youtube/client/youtube.py
index 5b6d7df45..c7e684622 100644
--- a/resources/lib/youtube_plugin/youtube/client/youtube.py
+++ b/resources/lib/youtube_plugin/youtube/client/youtube.py
@@ -604,9 +604,9 @@ def get_channel_by_username(self, username, **kwargs):
"""
params = {'part': 'id'}
if username == 'mine':
- params.update({'mine': 'true'})
+ params['mine'] = True
else:
- params.update({'forUsername': username})
+ params['forUsername'] = username
return self.perform_v3_request(method='GET',
path='channels',
diff --git a/resources/lib/youtube_plugin/youtube/helper/tv.py b/resources/lib/youtube_plugin/youtube/helper/tv.py
index 716a151b1..cd30681f5 100644
--- a/resources/lib/youtube_plugin/youtube/helper/tv.py
+++ b/resources/lib/youtube_plugin/youtube/helper/tv.py
@@ -18,8 +18,6 @@ def my_subscriptions_to_items(provider, context, json_data, do_filter=False):
result = []
video_id_dict = {}
- incognito = context.get_param('incognito', False)
-
filter_list = []
black_list = False
if do_filter:
@@ -29,6 +27,11 @@ def my_subscriptions_to_items(provider, context, json_data, do_filter=False):
filter_list = filter_list.split(',')
filter_list = [x.lower() for x in filter_list]
+ item_params = {'video_id': None}
+ incognito = context.get_param('incognito', False)
+ if incognito:
+ item_params['incognito'] = incognito
+
items = json_data.get('items', [])
for item in items:
channel = item['channel'].lower()
@@ -36,9 +39,7 @@ def my_subscriptions_to_items(provider, context, json_data, do_filter=False):
if not do_filter or (do_filter and (not black_list) and (channel in filter_list)) or \
(do_filter and black_list and (channel not in filter_list)):
video_id = item['id']
- item_params = {'video_id': video_id}
- if incognito:
- item_params.update({'incognito': incognito})
+ item_params['video_id'] = video_id
item_uri = context.create_uri(['play'], item_params)
video_item = VideoItem(item['title'], uri=item_uri)
if incognito:
@@ -59,15 +60,12 @@ def my_subscriptions_to_items(provider, context, json_data, do_filter=False):
# next page
next_page_token = json_data.get('next_page_token', '')
if next_page_token or json_data.get('continue', False):
- new_params = {}
- new_params.update(context.get_params())
- new_params['next_page_token'] = next_page_token
- new_params['offset'] = int(json_data.get('offset', 0))
-
+ new_params = dict(context.get_params(),
+ next_page_token=next_page_token,
+ offset=int(json_data.get('offset', 0)))
new_context = context.clone(new_params=new_params)
-
current_page = new_context.get_param('page', 1)
- next_page_item = NextPageItem(new_context, current_page, fanart=provider.get_fanart(new_context))
+ next_page_item = NextPageItem(new_context, current_page)
result.append(next_page_item)
return result
@@ -77,14 +75,15 @@ def tv_videos_to_items(provider, context, json_data):
result = []
video_id_dict = {}
+ item_params = {'video_id': None}
incognito = context.get_param('incognito', False)
+ if incognito:
+ item_params['incognito'] = incognito
items = json_data.get('items', [])
for item in items:
video_id = item['id']
- item_params = {'video_id': video_id}
- if incognito:
- item_params.update({'incognito': incognito})
+ item_params['video_id'] = video_id
item_uri = context.create_uri(['play'], item_params)
video_item = VideoItem(item['title'], uri=item_uri)
if incognito:
@@ -106,15 +105,12 @@ def tv_videos_to_items(provider, context, json_data):
# next page
next_page_token = json_data.get('next_page_token', '')
if next_page_token or json_data.get('continue', False):
- new_params = {}
- new_params.update(context.get_params())
- new_params['next_page_token'] = next_page_token
- new_params['offset'] = int(json_data.get('offset', 0))
-
+ new_params = dict(context.get_params(),
+ next_page_token=next_page_token,
+ offset=int(json_data.get('offset', 0)))
new_context = context.clone(new_params=new_params)
-
current_page = new_context.get_param('page', 1)
- next_page_item = NextPageItem(new_context, current_page, fanart=provider.get_fanart(new_context))
+ next_page_item = NextPageItem(new_context, current_page)
result.append(next_page_item)
return result
@@ -124,8 +120,11 @@ def saved_playlists_to_items(provider, context, json_data):
result = []
playlist_id_dict = {}
- incognito = context.get_param('incognito', False)
thumb_size = context.get_settings().use_thumbnail_size()
+ incognito = context.get_param('incognito', False)
+ item_params = {}
+ if incognito:
+ item_params['incognito'] = incognito
items = json_data.get('items', [])
for item in items:
@@ -134,17 +133,12 @@ def saved_playlists_to_items(provider, context, json_data):
playlist_id = item['id']
image = utils.get_thumbnail(thumb_size, item.get('thumbnails', {}))
- item_params = {}
- if incognito:
- item_params.update({'incognito': incognito})
-
if channel_id:
item_uri = context.create_uri(['channel', channel_id, 'playlist', playlist_id], item_params)
else:
item_uri = context.create_uri(['playlist', playlist_id], item_params)
playlist_item = DirectoryItem(title, item_uri, image=image)
- playlist_item.set_fanart(provider.get_fanart(context))
result.append(playlist_item)
playlist_id_dict[playlist_id] = playlist_item
@@ -155,15 +149,12 @@ def saved_playlists_to_items(provider, context, json_data):
# next page
next_page_token = json_data.get('next_page_token', '')
if next_page_token or json_data.get('continue', False):
- new_params = {}
- new_params.update(context.get_params())
- new_params['next_page_token'] = next_page_token
- new_params['offset'] = int(json_data.get('offset', 0))
-
+ new_params = dict(context.get_params(),
+ next_page_token=next_page_token,
+ offset=int(json_data.get('offset', 0)))
new_context = context.clone(new_params=new_params)
-
current_page = new_context.get_param('page', 1)
- next_page_item = NextPageItem(new_context, current_page, fanart=provider.get_fanart(new_context))
+ next_page_item = NextPageItem(new_context, current_page)
result.append(next_page_item)
return result
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 bfc96ee8e..90ddfa73e 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
@@ -99,7 +99,6 @@ def add_url(self, url, provider, context):
playlist_item = DirectoryItem(
'', context.create_uri(['playlist', playlist_id], new_params),
)
- playlist_item.set_fanart(provider.get_fanart(context))
self._playlist_id_dict[playlist_id] = playlist_item
elif 'channel_id' in new_params:
@@ -115,7 +114,6 @@ def add_url(self, url, provider, context):
) if live else DirectoryItem(
'', context.create_uri(['channel', channel_id], new_params)
)
- channel_item.set_fanart(provider.get_fanart(context))
self._channel_id_dict[channel_id] = channel_item
else:
@@ -137,9 +135,8 @@ def get_items(self, provider, context, skip_title=False):
context.create_uri(['special', 'description_links'], {
'channel_ids': ','.join(self._channel_ids),
}),
- context.create_resource_path('media', 'playlist.png')
+ image='{media}/playlist.png'
)
- channels_item.set_fanart(provider.get_fanart(context))
result.append(channels_item)
if self._playlist_ids:
@@ -157,9 +154,8 @@ def get_items(self, provider, context, skip_title=False):
context.create_uri(['special', 'description_links'], {
'playlist_ids': ','.join(self._playlist_ids),
}),
- context.create_resource_path('media', 'playlist.png')
+ image='{media}/playlist.png'
)
- playlists_item.set_fanart(provider.get_fanart(context))
result.append(playlists_item)
if self._channel_id_dict:
diff --git a/resources/lib/youtube_plugin/youtube/helper/utils.py b/resources/lib/youtube_plugin/youtube/helper/utils.py
index 311d3719b..3ff8505c7 100644
--- a/resources/lib/youtube_plugin/youtube/helper/utils.py
+++ b/resources/lib/youtube_plugin/youtube/helper/utils.py
@@ -239,10 +239,8 @@ def update_channel_infos(provider, context, channel_id_dict,
for banner in banners:
fanart = fanart_images.get(banner)
if fanart:
+ channel_item.set_fanart(fanart)
break
- else:
- fanart = ''
- channel_item.set_fanart(fanart)
# update channel mapping
if channel_items_dict is not None:
diff --git a/resources/lib/youtube_plugin/youtube/helper/v3.py b/resources/lib/youtube_plugin/youtube/helper/v3.py
index 345a85ca7..0cc753aea 100644
--- a/resources/lib/youtube_plugin/youtube/helper/v3.py
+++ b/resources/lib/youtube_plugin/youtube/helper/v3.py
@@ -67,7 +67,6 @@ def _process_list_response(provider, context, json_data):
video_item.video_id = video_id
if incognito:
video_item.set_play_count(0)
- video_item.set_fanart(provider.get_fanart(context))
result.append(video_item)
video_id_dict[video_id] = video_item
elif kind == 'channel':
@@ -82,7 +81,6 @@ def _process_list_response(provider, context, json_data):
item_params['addon_id'] = addon_id
item_uri = context.create_uri(['channel', channel_id], item_params)
channel_item = DirectoryItem(title, item_uri, image=image)
- channel_item.set_fanart(provider.get_fanart(context))
# if logged in => provide subscribing to the channel
if provider.is_logged_in():
@@ -105,7 +103,6 @@ def _process_list_response(provider, context, json_data):
item_params['addon_id'] = addon_id
item_uri = context.create_uri(['special', 'browse_channels'], item_params)
guide_item = DirectoryItem(title, item_uri)
- guide_item.set_fanart(provider.get_fanart(context))
result.append(guide_item)
elif kind == 'subscription':
snippet = yt_item['snippet']
@@ -119,7 +116,6 @@ def _process_list_response(provider, context, json_data):
item_params['addon_id'] = addon_id
item_uri = context.create_uri(['channel', channel_id], item_params)
channel_item = DirectoryItem(title, item_uri, image=image)
- channel_item.set_fanart(provider.get_fanart(context))
channel_item.set_channel_id(channel_id)
# map channel id with subscription id - we need it for the unsubscription
subscription_id_dict[channel_id] = yt_item['id']
@@ -144,7 +140,6 @@ def _process_list_response(provider, context, json_data):
item_params['addon_id'] = addon_id
item_uri = context.create_uri(['channel', channel_id, 'playlist', playlist_id], item_params)
playlist_item = DirectoryItem(title, item_uri, image=image)
- playlist_item.set_fanart(provider.get_fanart(context))
result.append(playlist_item)
playlist_id_dict[playlist_id] = playlist_item
elif kind == 'playlistitem':
@@ -166,7 +161,6 @@ def _process_list_response(provider, context, json_data):
video_item.video_id = video_id
if incognito:
video_item.set_play_count(0)
- video_item.set_fanart(provider.get_fanart(context))
# Get Track-ID from Playlist
video_item.set_track_number(snippet['position'] + 1)
result.append(video_item)
@@ -197,7 +191,6 @@ def _process_list_response(provider, context, json_data):
video_item.video_id = video_id
if incognito:
video_item.set_play_count(0)
- video_item.set_fanart(provider.get_fanart(context))
result.append(video_item)
video_id_dict[video_id] = video_item
@@ -234,7 +227,6 @@ def _process_list_response(provider, context, json_data):
video_item.video_id = video_id
if incognito:
video_item.set_play_count(0)
- video_item.set_fanart(provider.get_fanart(context))
result.append(video_item)
video_id_dict[video_id] = video_item
# playlist
@@ -256,7 +248,6 @@ def _process_list_response(provider, context, json_data):
item_params['addon_id'] = addon_id
item_uri = context.create_uri(['channel', channel_id, 'playlist', playlist_id], item_params)
playlist_item = DirectoryItem(title, item_uri, image=image)
- playlist_item.set_fanart(provider.get_fanart(context))
result.append(playlist_item)
playlist_id_dict[playlist_id] = playlist_item
elif kind == 'channel':
@@ -271,7 +262,6 @@ def _process_list_response(provider, context, json_data):
item_params['addon_id'] = addon_id
item_uri = context.create_uri(['channel', channel_id], item_params)
channel_item = DirectoryItem(title, item_uri, image=image)
- channel_item.set_fanart(provider.get_fanart(context))
result.append(channel_item)
channel_id_dict[channel_id] = channel_item
else:
@@ -462,14 +452,11 @@ def response_to_items(provider, context, json_data, sort=None, reverse=False, pr
client = provider.get_client(context)
yt_next_page_token = client.calculate_next_page_token(page + 1, yt_results_per_page)
- new_params = {}
- new_params.update(context.get_params())
- new_params['page_token'] = yt_next_page_token
-
+ new_params = dict(context.get_params(),
+ page_token=yt_next_page_token)
new_context = context.clone(new_params=new_params)
-
current_page = new_context.get_param('page', 1)
- next_page_item = NextPageItem(new_context, current_page, fanart=provider.get_fanart(new_context))
+ next_page_item = NextPageItem(new_context, current_page)
result.append(next_page_item)
return result
diff --git a/resources/lib/youtube_plugin/youtube/helper/yt_playlist.py b/resources/lib/youtube_plugin/youtube/helper/yt_playlist.py
index 936d859d8..6939c6013 100644
--- a/resources/lib/youtube_plugin/youtube/helper/yt_playlist.py
+++ b/resources/lib/youtube_plugin/youtube/helper/yt_playlist.py
@@ -207,18 +207,15 @@ def _process_select_playlist(provider, context):
playlist_id = json_data.get('id', '')
if playlist_id:
- new_params = {}
- new_params.update(context.get_params())
- new_params['playlist_id'] = playlist_id
+ new_params = dict(context.get_params(),
+ playlist_id=playlist_id)
new_context = context.clone(new_params=new_params)
_process_add_video(provider, new_context, keymap_action)
break
if result == 'playlist.next':
continue
if result != -1:
- new_params = {}
- new_params.update(context.get_params())
- new_params['playlist_id'] = result
+ new_params = dict(context.get_params(), playlist_id=result)
new_context = context.clone(new_params=new_params)
_process_add_video(provider, new_context, keymap_action)
break
diff --git a/resources/lib/youtube_plugin/youtube/helper/yt_specials.py b/resources/lib/youtube_plugin/youtube/helper/yt_specials.py
index 6ac51e84b..544c70120 100644
--- a/resources/lib/youtube_plugin/youtube/helper/yt_specials.py
+++ b/resources/lib/youtube_plugin/youtube/helper/yt_specials.py
@@ -204,7 +204,6 @@ def _display_channels(channel_ids):
channel_item = DirectoryItem(
'', context.create_uri(['channel', channel_id], item_params)
)
- channel_item.set_fanart(provider.get_fanart(context))
channel_id_dict[channel_id] = channel_item
channel_item_dict = {}
@@ -230,7 +229,6 @@ def _display_playlists(playlist_ids):
playlist_item = DirectoryItem(
'', context.create_uri(['playlist', playlist_id], item_params)
)
- playlist_item.set_fanart(provider.get_fanart(context))
playlist_id_dict[playlist_id] = playlist_item
channel_item_dict = {}
diff --git a/resources/lib/youtube_plugin/youtube/provider.py b/resources/lib/youtube_plugin/youtube/provider.py
index 5c230b422..70f694ca4 100644
--- a/resources/lib/youtube_plugin/youtube/provider.py
+++ b/resources/lib/youtube_plugin/youtube/provider.py
@@ -258,13 +258,6 @@ def get_resource_manager(self, context):
self._resource_manager = ResourceManager(context, self.get_client(context))
return self._resource_manager
- def get_alternative_fanart(self, context):
- return self.get_fanart(context)
-
- @staticmethod
- def get_fanart(context):
- return context.create_resource_path('media', 'fanart.jpg')
-
# noinspection PyUnusedLocal
@RegisterProviderPath('^/uri2addon/$')
def on_uri2addon(self, context, re_match):
@@ -318,25 +311,31 @@ def _on_channel_playlists(self, context, re_match):
result = []
channel_id = re_match.group('channel_id')
- page_token = context.get_param('page_token', '')
resource_manager = self.get_resource_manager(context)
- item_params = {}
- incognito = context.get_param('incognito')
- addon_id = context.get_param('addon_id')
+ params = context.get_params()
+ page_token = params.get('page_token', '')
+ incognito = params.get('incognito')
+ addon_id = params.get('addon_id')
+
+ new_params = {}
if incognito:
- item_params.update({'incognito': incognito})
+ new_params['incognito'] = incognito
if addon_id:
- item_params.update({'addon_id': addon_id})
+ new_params['addon_id'] = addon_id
playlists = resource_manager.get_related_playlists(channel_id)
uploads_playlist = playlists.get('uploads', '')
if uploads_playlist:
- uploads_item = DirectoryItem(context.get_ui().bold(context.localize('uploads')),
- context.create_uri(['channel', channel_id, 'playlist', uploads_playlist],
- item_params),
- image=context.create_resource_path('media', 'playlist.png'))
+ uploads_item = DirectoryItem(
+ context.get_ui().bold(context.localize('uploads')),
+ context.create_uri(
+ ['channel', channel_id, 'playlist', uploads_playlist],
+ new_params
+ ),
+ image='{media}/playlist.png',
+ )
result.append(uploads_item)
# no caching
@@ -380,10 +379,8 @@ def _on_channel_live(self, context, re_match):
def _on_channel(self, context, re_match):
client = self.get_client(context)
localize = context.localize
- create_path = context.create_resource_path
create_uri = context.create_uri
function_cache = context.get_function_cache()
- params = context.get_params()
ui = context.get_ui()
listitem_channel_id = ui.get_info_label('Container.ListItem(0).Property(channel_id)')
@@ -431,15 +428,18 @@ def _on_channel(self, context, re_match):
return False
channel_fanarts = resource_manager.get_fanarts([channel_id])
+
+ params = context.get_params()
page = params.get('page', 1)
page_token = params.get('page_token', '')
incognito = params.get('incognito')
addon_id = params.get('addon_id')
- item_params = {}
+
+ new_params = {}
if incognito:
- item_params.update({'incognito': incognito})
+ new_params['incognito'] = incognito
if addon_id:
- item_params.update({'addon_id': addon_id})
+ new_params['addon_id'] = addon_id
hide_folders = params.get('hide_folders')
@@ -449,25 +449,31 @@ def _on_channel(self, context, re_match):
hide_live = params.get('hide_live')
if not hide_playlists:
- playlists_item = DirectoryItem(ui.bold(localize('playlists')),
- create_uri(['channel', channel_id, 'playlists'], item_params),
- image=create_path('media', 'playlist.png'))
- playlists_item.set_fanart(channel_fanarts.get(channel_id, self.get_fanart(context)))
+ playlists_item = DirectoryItem(
+ ui.bold(localize('playlists')),
+ create_uri(['channel', channel_id, 'playlists'], new_params),
+ image='{media}/playlist.png',
+ fanart=channel_fanarts.get(channel_id),
+ )
result.append(playlists_item)
search_live_id = mine_id if mine_id else channel_id
if not hide_search:
- search_item = NewSearchItem(context, alt_name=ui.bold(localize('search')),
- image=create_path('media', 'search.png'),
- fanart=self.get_fanart(context), channel_id=search_live_id, incognito=incognito, addon_id=addon_id)
- search_item.set_fanart(self.get_fanart(context))
+ search_item = NewSearchItem(
+ context, name=ui.bold(localize('search')),
+ image='{media}/search.png',
+ channel_id=search_live_id,
+ incognito=incognito,
+ addon_id=addon_id,
+ )
result.append(search_item)
if not hide_live:
- live_item = DirectoryItem(ui.bold(localize('live')),
- create_uri(['channel', search_live_id, 'live'], item_params),
- image=create_path('media', 'live.png'))
- live_item.set_fanart(self.get_fanart(context))
+ live_item = DirectoryItem(
+ ui.bold(localize('live')),
+ create_uri(['channel', search_live_id, 'live'], new_params),
+ image='{media}/live.png',
+ )
result.append(live_item)
playlists = resource_manager.get_related_playlists(channel_id)
@@ -489,7 +495,6 @@ def _on_channel(self, context, re_match):
def _on_my_location(self, context, re_match):
self.set_content_type(context, content.FILES)
- create_path = context.create_resource_path
create_uri = context.create_uri
localize = context.localize
settings = context.get_settings()
@@ -498,8 +503,7 @@ def _on_my_location(self, context, re_match):
# search
search_item = SearchItem(
context,
- image=create_path('media', 'search.png'),
- fanart=self.get_fanart(context),
+ image='{media}/search.png',
location=True
)
result.append(search_item)
@@ -508,29 +512,35 @@ def _on_my_location(self, context, re_match):
if settings.get_bool('youtube.folder.completed.live.show', True):
live_events_item = DirectoryItem(
localize('live.completed'),
- create_uri(['special', 'completed_live'], params={'location': True}),
- image=create_path('media', 'live.png')
+ create_uri(
+ ['special', 'completed_live'],
+ params={'location': True}
+ ),
+ image='{media}/live.png',
)
- live_events_item.set_fanart(self.get_fanart(context))
result.append(live_events_item)
# upcoming live events
if settings.get_bool('youtube.folder.upcoming.live.show', True):
live_events_item = DirectoryItem(
localize('live.upcoming'),
- create_uri(['special', 'upcoming_live'], params={'location': True}),
- image=create_path('media', 'live.png')
+ create_uri(
+ ['special', 'upcoming_live'],
+ params={'location': True}
+ ),
+ image='{media}/live.png',
)
- live_events_item.set_fanart(self.get_fanart(context))
result.append(live_events_item)
# live events
live_events_item = DirectoryItem(
localize('live'),
- create_uri(['special', 'live'], params={'location': True}),
- image=create_path('media', 'live.png')
+ create_uri(
+ ['special', 'live'],
+ params={'location': True}
+ ),
+ image='{media}/live.png',
)
- live_events_item.set_fanart(self.get_fanart(context))
result.append(live_events_item)
return result
@@ -563,7 +573,7 @@ def on_play(self, context, re_match):
video_id = find_video_id(path)
if video_id:
context.set_param('video_id', video_id)
- params = context.get_params()
+ params['video_id'] = video_id
else:
return False
else:
@@ -646,10 +656,11 @@ def _on_subscriptions(self, context, re_match):
channel_ids = []
for subscription in subscriptions:
channel_ids.append(subscription.get_channel_id())
+ channel_ids = {subscription.get_channel_id(): subscription
+ for subscription in subscriptions}
channel_fanarts = resource_manager.get_fanarts(channel_ids)
- for subscription in subscriptions:
- if channel_fanarts.get(subscription.get_channel_id()):
- subscription.set_fanart(channel_fanarts.get(subscription.get_channel_id()))
+ for channel_id, fanart in channel_fanarts:
+ channel_ids[channel_id].set_fanart(fanart)
return subscriptions
@@ -821,7 +832,6 @@ def on_search(self, search_text, context, re_match):
page = params.get('page', 1)
page_token = params.get('page_token', '')
search_type = params.get('search_type', 'video')
-
safe_search = context.get_settings().safe_search()
if search_type == 'video':
@@ -831,35 +841,36 @@ def on_search(self, search_text, context, re_match):
if page == 1 and search_type == 'video' and not event_type and not hide_folders:
if not channel_id and not location:
- channel_params = {}
- channel_params.update(params)
- channel_params['search_type'] = 'channel'
- channel_item = DirectoryItem(context.get_ui().bold(context.localize('channels')),
- context.create_uri([context.get_path()], channel_params),
- image=context.create_resource_path('media', 'channels.png'))
- channel_item.set_fanart(self.get_fanart(context))
+ channel_params = dict(params, search_type='channel')
+ channel_item = DirectoryItem(
+ context.get_ui().bold(context.localize('channels')),
+ context.create_uri([context.get_path()], channel_params),
+ image='{media}/channels.png',
+ )
result.append(channel_item)
if not location:
- playlist_params = {}
- playlist_params.update(params)
- playlist_params['search_type'] = 'playlist'
- playlist_item = DirectoryItem(context.get_ui().bold(context.localize('playlists')),
- context.create_uri([context.get_path()], playlist_params),
- image=context.create_resource_path('media', 'playlist.png'))
- playlist_item.set_fanart(self.get_fanart(context))
+ playlist_params = dict(params, search_type='playlist')
+ playlist_item = DirectoryItem(
+ context.get_ui().bold(context.localize('playlists')),
+ context.create_uri([context.get_path()], playlist_params),
+ image='{media}/playlist.png',
+ )
result.append(playlist_item)
if not channel_id:
# live
- live_params = {}
- live_params.update(params)
- live_params['search_type'] = 'video'
- live_params['event_type'] = 'live'
- live_item = DirectoryItem(context.get_ui().bold(context.localize('live')),
- context.create_uri([context.get_path().replace('input', 'query')], live_params),
- image=context.create_resource_path('media', 'live.png'))
- live_item.set_fanart(self.get_fanart(context))
+ live_params = dict(params,
+ search_type='video',
+ event_type='live')
+ live_item = DirectoryItem(
+ context.get_ui().bold(context.localize('live')),
+ context.create_uri(
+ [context.get_path().replace('input', 'query')],
+ live_params
+ ),
+ image='{media}/live.png',
+ )
result.append(live_item)
function_cache = context.get_function_cache()
@@ -936,10 +947,10 @@ def configure_addon(self, context, re_match):
# noinspection PyUnusedLocal
@RegisterProviderPath('^/my_subscriptions/filter/$')
def manage_my_subscription_filter(self, context, re_match):
- params = context.get_params()
settings = context.get_settings()
ui = context.get_ui()
+ params = context.get_params()
action = params.get('action')
channel = params.get('channel_name')
if (not channel) or (not action):
@@ -1076,6 +1087,7 @@ def maintenance_actions(self, context, re_match):
ui.show_notification(localize('succeeded'))
else:
ui.show_notification(localize('failed'))
+
elif action == 'install' and maint_type == 'inputstreamhelper':
if context.get_system_version().get_version()[0] >= 17:
try:
@@ -1090,10 +1102,10 @@ def maintenance_actions(self, context, re_match):
@RegisterProviderPath('^/api/update/$')
def api_key_update(self, context, re_match):
localize = context.localize
- params = context.get_params()
settings = context.get_settings()
ui = context.get_ui()
+ params = context.get_params()
api_key = params.get('api_key')
client_id = params.get('client_id')
client_secret = params.get('client_secret')
@@ -1244,7 +1256,6 @@ def on_root(self, context, re_match):
if old_action:
return yt_old_actions.process_old_action(self, context, re_match)
- create_path = context.create_resource_path
create_uri = context.create_uri
localize = context.localize
settings = context.get_settings()
@@ -1258,12 +1269,13 @@ def on_root(self, context, re_match):
result = []
# sign in
- if not self.is_logged_in() and settings.get_bool('youtube.folder.sign.in.show', True):
- sign_in_item = DirectoryItem(ui.bold(localize('sign.in')),
- create_uri(['sign', 'in']),
- image=create_path('media', 'sign_in.png'))
- sign_in_item.set_action(True)
- sign_in_item.set_fanart(self.get_fanart(context))
+ if not logged_in and settings.get_bool('youtube.folder.sign.in.show', True):
+ sign_in_item = DirectoryItem(
+ ui.bold(localize('sign.in')),
+ create_uri(['sign', 'in']),
+ image='{media}/sign_in.png',
+ action=True
+ )
result.append(sign_in_item)
if self.is_logged_in() and settings.get_bool('youtube.folder.my_subscriptions.show', True):
@@ -1276,8 +1288,8 @@ def on_root(self, context, re_match):
my_subscriptions_item = DirectoryItem(
ui.bold(localize('my_subscriptions')),
create_uri(['special', 'new_uploaded_videos_tv']),
- create_path('media', 'new_uploads.png'))
- my_subscriptions_item.set_fanart(self.get_fanart(context))
+ image='{media}/new_uploads.png',
+ )
result.append(my_subscriptions_item)
if self.is_logged_in() and settings.get_bool('youtube.folder.my_subscriptions_filtered.show', True):
@@ -1285,8 +1297,8 @@ def on_root(self, context, re_match):
my_subscriptions_filtered_item = DirectoryItem(
localize('my_subscriptions.filtered'),
create_uri(['special', 'new_uploaded_videos_tv_filtered']),
- create_path('media', 'new_uploads.png'))
- my_subscriptions_filtered_item.set_fanart(self.get_fanart(context))
+ image='{media}/new_uploads.png',
+ )
result.append(my_subscriptions_filtered_item)
access_manager = context.get_access_manager()
@@ -1298,8 +1310,8 @@ def on_root(self, context, re_match):
recommendations_item = DirectoryItem(
localize('recommendations'),
create_uri(['special', 'recommendations']),
- create_path('media', 'popular.png'))
- recommendations_item.set_fanart(self.get_fanart(context))
+ image='{media}/popular.png',
+ )
result.append(recommendations_item)
# what to watch
@@ -1307,36 +1319,41 @@ def on_root(self, context, re_match):
what_to_watch_item = DirectoryItem(
localize('popular_right_now'),
create_uri(['special', 'popular_right_now']),
- create_path('media', 'popular.png'))
- what_to_watch_item.set_fanart(self.get_fanart(context))
+ image='{media}/popular.png',
+ )
result.append(what_to_watch_item)
# search
if settings.get_bool('youtube.folder.search.show', True):
- search_item = SearchItem(context, image=create_path('media', 'search.png'),
- fanart=self.get_fanart(context))
+ search_item = SearchItem(
+ context,
+ )
result.append(search_item)
if settings.get_bool('youtube.folder.quick_search.show', True):
- quick_search_item = NewSearchItem(context,
- alt_name=localize('search.quick'),
- fanart=self.get_fanart(context))
+ quick_search_item = NewSearchItem(
+ context,
+ name=localize('search.quick'),
+ image='{media}/quick_search.png',
+ )
result.append(quick_search_item)
if settings.get_bool('youtube.folder.quick_search_incognito.show', True):
- quick_search_incognito_item = NewSearchItem(context,
- alt_name=localize('search.quick.incognito'),
- image=create_path('media', 'search.png'),
- fanart=self.get_fanart(context),
- incognito=True)
+ quick_search_incognito_item = NewSearchItem(
+ context,
+ name=localize('search.quick.incognito'),
+ image='{media}/incognito_search.png',
+ incognito=True,
+ )
result.append(quick_search_incognito_item)
# my location
if settings.get_bool('youtube.folder.my_location.show', True) and settings.get_location():
- my_location_item = DirectoryItem(localize('my_location'),
- create_uri(['location', 'mine']),
- image=create_path('media', 'channel.png'))
- my_location_item.set_fanart(self.get_fanart(context))
+ my_location_item = DirectoryItem(
+ localize('my_location'),
+ create_uri(['location', 'mine']),
+ image='{media}/location.png',
+ )
result.append(my_location_item)
# my channel
@@ -1344,8 +1361,7 @@ def on_root(self, context, re_match):
my_channel_item = DirectoryItem(
localize('my_channel'),
create_uri(['channel', 'mine']),
- image=create_path('media', 'channel.png'),
- fanart=self.get_fanart(context)
+ image='{media}/channel.png',
)
result.append(my_channel_item)
@@ -1356,8 +1372,7 @@ def on_root(self, context, re_match):
watch_later_item = DirectoryItem(
localize('watch_later'),
create_uri(['channel', 'mine', 'playlist', playlist_id]),
- image=create_path('media', 'watch_later.png'),
- fanart=self.get_fanart(context)
+ image='{media}/watch_later.png',
)
context_menu = [
menu_items.play_all_from_playlist(
@@ -1370,8 +1385,7 @@ def on_root(self, context, re_match):
watch_history_item = DirectoryItem(
localize('watch_later'),
create_uri([paths.WATCH_LATER, 'list']),
- image=create_path('media', 'watch_later.png'),
- fanart=self.get_fanart(context)
+ image='{media}/watch_later.png',
)
result.append(watch_history_item)
@@ -1383,8 +1397,7 @@ def on_root(self, context, re_match):
liked_videos_item = DirectoryItem(
localize('video.liked'),
create_uri(['channel', 'mine', 'playlist', playlists['likes']]),
- image=create_path('media', 'likes.png'),
- fanart=self.get_fanart(context)
+ image='{media}/likes.png',
)
context_menu = [
menu_items.play_all_from_playlist(
@@ -1399,8 +1412,7 @@ def on_root(self, context, re_match):
disliked_videos_item = DirectoryItem(
localize('video.disliked'),
create_uri(['special', 'disliked_videos']),
- image=create_path('media', 'dislikes.png'),
- fanart=self.get_fanart(context)
+ image='{media}/dislikes.png',
)
result.append(disliked_videos_item)
@@ -1411,8 +1423,7 @@ def on_root(self, context, re_match):
watch_history_item = DirectoryItem(
localize('history'),
create_uri(['channel', 'mine', 'playlist', playlist_id]),
- image=create_path('media', 'history.png'),
- fanart=self.get_fanart(context)
+ image='{media}/history.png',
)
context_menu = [
menu_items.play_all_from_playlist(
@@ -1425,8 +1436,7 @@ def on_root(self, context, re_match):
watch_history_item = DirectoryItem(
localize('history'),
create_uri([paths.HISTORY], params={'action': 'list'}),
- image=create_path('media', 'history.png'),
- fanart=self.get_fanart(context)
+ image='{media}/history.png',
)
result.append(watch_history_item)
@@ -1435,8 +1445,7 @@ def on_root(self, context, re_match):
playlists_item = DirectoryItem(
localize('playlists'),
create_uri(['channel', 'mine', 'playlists']),
- image=create_path('media', 'playlist.png'),
- fanart=self.get_fanart(context)
+ image='{media}/playlist.png',
)
result.append(playlists_item)
@@ -1445,8 +1454,7 @@ def on_root(self, context, re_match):
playlists_item = DirectoryItem(
localize('saved.playlists'),
create_uri(['special', 'saved_playlists']),
- image=create_path('media', 'playlist.png'),
- fanart=self.get_fanart(context)
+ image='{media}/playlist.png',
)
result.append(playlists_item)
@@ -1455,8 +1463,7 @@ def on_root(self, context, re_match):
subscriptions_item = DirectoryItem(
localize('subscriptions'),
create_uri(['subscriptions', 'list']),
- image=create_path('media', 'channels.png'),
- fanart=self.get_fanart(context)
+ image='{media}/channels.png',
)
result.append(subscriptions_item)
@@ -1465,59 +1472,64 @@ def on_root(self, context, re_match):
browse_channels_item = DirectoryItem(
localize('browse_channels'),
create_uri(['special', 'browse_channels']),
- image=create_path('media', 'browse_channels.png'),
- fanart=self.get_fanart(context)
+ image='{media}/browse_channels.png',
)
result.append(browse_channels_item)
# completed live events
if settings.get_bool('youtube.folder.completed.live.show', True):
- live_events_item = DirectoryItem(localize('live.completed'),
- create_uri(['special', 'completed_live']),
- image=create_path('media', 'live.png'))
- live_events_item.set_fanart(self.get_fanart(context))
+ live_events_item = DirectoryItem(
+ localize('live.completed'),
+ create_uri(['special', 'completed_live']),
+ image='{media}/live.png',
+ )
result.append(live_events_item)
# upcoming live events
if settings.get_bool('youtube.folder.upcoming.live.show', True):
- live_events_item = DirectoryItem(localize('live.upcoming'),
- create_uri(['special', 'upcoming_live']),
- image=create_path('media', 'live.png'))
- live_events_item.set_fanart(self.get_fanart(context))
+ live_events_item = DirectoryItem(
+ localize('live.upcoming'),
+ create_uri(['special', 'upcoming_live']),
+ image='{media}/live.png',
+ )
result.append(live_events_item)
# live events
if settings.get_bool('youtube.folder.live.show', True):
- live_events_item = DirectoryItem(localize('live'),
- create_uri(['special', 'live']),
- image=create_path('media', 'live.png'))
- live_events_item.set_fanart(self.get_fanart(context))
+ live_events_item = DirectoryItem(
+ localize('live'),
+ create_uri(['special', 'live']),
+ image='{media}/live.png',
+ )
result.append(live_events_item)
# switch user
if settings.get_bool('youtube.folder.switch.user.show', True):
- switch_user_item = DirectoryItem(localize('user.switch'),
- create_uri(['users', 'switch']),
- image=create_path('media', 'channel.png'))
- switch_user_item.set_action(True)
- switch_user_item.set_fanart(self.get_fanart(context))
+ switch_user_item = DirectoryItem(
+ localize('user.switch'),
+ create_uri(['users', 'switch']),
+ image='{media}/channel.png',
+ action=True,
+ )
result.append(switch_user_item)
# sign out
- if self.is_logged_in() and settings.get_bool('youtube.folder.sign.out.show', True):
- sign_out_item = DirectoryItem(localize('sign.out'),
- create_uri(['sign', 'out']),
- image=create_path('media', 'sign_out.png'))
- sign_out_item.set_action(True)
- sign_out_item.set_fanart(self.get_fanart(context))
+ if logged_in and settings.get_bool('youtube.folder.sign.out.show', True):
+ sign_out_item = DirectoryItem(
+ localize('sign.out'),
+ create_uri(['sign', 'out']),
+ image='{media}/sign_out.png',
+ action=True,
+ )
result.append(sign_out_item)
if settings.get_bool('youtube.folder.settings.show', True):
- settings_menu_item = DirectoryItem(localize('settings'),
- create_uri(['config', 'youtube']),
- image=create_path('media', 'settings.png'))
- settings_menu_item.set_action(True)
- settings_menu_item.set_fanart(self.get_fanart(context))
+ settings_menu_item = DirectoryItem(
+ localize('settings'),
+ create_uri(['config', 'youtube']),
+ image='{media}/settings.png',
+ action=True,
+ )
result.append(settings_menu_item)
return result
diff --git a/resources/media/incognito_search.png b/resources/media/incognito_search.png
new file mode 100644
index 0000000000000000000000000000000000000000..2dcb821a33e65d5d06d16b1923f7830b6d2cc9eb
GIT binary patch
literal 4725
zcmbtYc{r5o`yX3GGU}vI43(pp8HUD~h8SauBKxi}GnO%C#w;59mTcLMN{S*nwrs~<
zM6#rkgHvd+@05y3g!zrO<9Dv}TdwQ-UhjLo@AG-?`}5rQ^SPhh=dNg``l0q$I^xv7=D
zqN1X*va+YAr;CdV1OhQNH6@eDNl8gLIXR`Jr8PA*4@n1Y&)Cox|Y_4GlRtIW;ymj*N_a`SN9AVxqORH8V5Q-Q67qgIQTwB_t%U
z+3b~-m9(_9vuDp57#O^L`_{(BMqOP!BqZd?lP4)DDQGnM^5x4PK71%IFGrzJi;IgE
z78cpr*~`nz4<0-?dGaKK!I+tudH(!)MMXtHLBaLw*E>2o8X6k>{QTzT=BB2mjvhVQ
z-rjC!XLtJa>FDTaZEbBh9PZ}khR5UIym=EF8~f_jtMKsf^z`(|$jF6-1qTO*zP>&r
z5=kTyF&GSs#R>=rFfuZtQmHyRI^EsfN=iyWK|xhjRZ&q<@$vD!y}ic9#z&4E$;->D
ztgM`$pD!sX`Sj`2&6_t13kz*+Z6_xuH#RnI-@bkC-o4@BVQXva@$vE1)z#YC+Pin}
zE-fvYn3zmYPy74(kByDBwY6QndNn2{hD0J26%{cH0v>Ti+DD~-;6q?wK`b8$$uqAa=$lW
zs65vS1VYj9?nn!a!FMa}NK4+6!Js0c&~xX`LC&c`C^QeKs-~tUR0Rfw!N6P#Fx{Wb
zz_P$(`oV7`-)S%eI*#T=Wq47@piNq=8->Zxl9&I^t;=B0yxe}$lj#ubSA@Cg0O4j1
z#p3BuRfx*>&_LV^_00*Xi>H4LcN6=*nkB(^3-lc%d3{}(ilxyBR{m6imb?XlPGQn;
zgddT9WB8j{k4C^U2zYIn3QQHOf&jzdDCqY^e@t!e)8E&Rkn~v$W3C&50CU$+LxACM
zH8@yZ9j5`-RKvl+s%q};aCNMky1EYsGSTn;EOO
z+gPIQxJyhJe>72_cbCxP5fHBdzxa7p7T;q$7Gu~R$@KHfc+=e==I8ebI)uDmNJgX2
zrJl~O9xA;x6Ec%K*AQUstg%xU$p8Pj#gC`s#1V;5`>xp3JcrXYH@v(TrV+1R3@;PYnKYpGEDN5Pt*38uHACVw;>_m&D3ch#gHHQ3%&od(c~uq^g{Pga
z4gP`H5x=hID;eq$(Vv~nYb_`0t}46l&5jS*Z3=O|cYP@5@HMIW(v0Rk
z9l4jlth@O&%?($+91e2oh>SMr?Lm*y)(7EHX1gEF-s<5gq`qvI9jTDYn4X=nzG4yH
zctv)sqUFQ$m~K5H47R_}{CYsGLc-w`Kk1SQn2n{>6)8YkXK^3#-Tc8`Hb$y*-@@!s
zZDZiC$fy?z<2Pxaj(-8GB8@sEcDmjR)>he>
ze)|&UilKDU67#?k^(0K2cGEZVRfKT~OH%jB~#zSr1&7-Bt5$
zYRCn$EY-VHuce&af2XU3#`9ahXSh^7Vd65lTJ$oKypQn}d6?Jw&|kbniWT0d;;zO7trx`qlAH<#SsdY~nd4bSHg
zt#_8is_YFFw>*(#y7W$G*YUYB6`Yw|d93nin~l_qvq!YL;m@p}-YN?VhmB6&kILnF
z9g!eeQ5s5;R>eILeR}hD{L9B4a^=y1F_M9Tx_T^8B{g@)zDN9{t&c_D{_*H)h?{GC
zlduC2T7yjjw1VcJ(t!ODl3hFU`TlZ@4lEGuoWCZ%tkdo-7$J30n*&ZPd>`U|yjJXk
z%#P0c(M>yLX8nfyfL6bTj;$>at5XM)AZzcxEF%g!=svT@gyzp~KlQ#@g8zAX|C;aZ
zD+09}7lT
zzZ{jNuu>(?oU>lSED@$y%7l$QN-nmnaF@``tg;b8$
zKI>2qtHHQlz6afl<`~0$+0qd@^?6tfXXCX!bYSSW9c%c)B5BADU;WaKdm|L}o>CMQP(+
zGjE@-OyY7*sK>l)FsH?fg-_hA+oh}d@yy4H*}R<2v%gIY;Xk-;AhC$2``sGu%uiQ@
z#U)7}7I`$qG2@i
z%0198YpVImGG!b2{y~?zu_H?8ja4&-NK9OE%Qf*HQON^omwjctYvQ&M$}R
zpLcq1Jlhq^gwui_O@cg-p45tK9HnN*EM@l8WVNSeLHZ#XbK#Tg-j$&bukfj%gFjIA
z@=Dic?GG!L5F)BLzTxa?7IcX0dt6$?8&(GwO&}~C>JEdXgmeCS;cAz)W>O{+<13!%
z-S<+wQqbOaN5m|?dc)B%^xd0*;Slz2w|IdWUO8E*PThpWk3bc5i`934^}6Wcb->-+
zj~N7^d7Z>05h-=EmwJ%j_ZT2V!w=t46g;a;jWY};72H;Pz3tPEgp*d0$%bHR947dY
zuA=WvO(D`quIauT>huJk+|m(v^#q`Opxe0j&-8icS;!hZTk1yVc(JME4fwqTM);}V
zG^0rKQ&aaJjGw_G&f3JO_N^vd
z+Mm~MQtYH$m*uDt&gw6(6dpI6vc*QE?7ItAcu{GlT21ObS$++*SRG7@o*fjTyFatd
z)xRFY!lP7*0ubx$s@FFq>PBT`LDd;!`l;?0GmCS4QSaN*)?kNPc!yYyL{%H@C6}z_
z){yOcxJXiTvfIbTY9*lItw3Gkosf2s?d3Wy;5i*$iBkR0poYmt&@Y3AXyy0lcWjU~
z5WDPE+5EomKym%r#ve1Z2{}0lRvm7C*g?3hSVL0|qlnLSS9rx@k0b1=R!cCM
zc#(Aer5ll*IjCgNELIY~0}r1W=K4|Zv#o`!o}L@rru_Lp|GQM@&_P=}p0_voSHl+3
zlX%5j(>}8IopLbBZzwZL!VQ{(8k3z0w~Uv!{C>|Y6$V*$cn#)T}TCV9Z
zX%(1-`TNVwDWOuQ`JVCHSWfe|s)mZb^{cF5#+|6E@!qJ6Tf?{%mV{Q-aGg=6F13d19JpI27XHSwaKCwJu6>iQOeV5;zwTVg9Z*u=M$G}@
zwe0ud;|^hJaJteLce*`Wk}5p0q9#4ouGbX+oY46L3^<7^s;JogCVg73qrdLR2MGkq
zt(47h9Hw^&L`N$RLj~vU(fy|Nw5H$nFLkzz^KQ5nf-QhLA9kMIdG^E3&OAFi4^m$jN(o>F;Njs>YQfZ>;o;%mrTBQH1b5(CX=e0a
z{>*!K)r(-MT9X9aVKy@$kMRlV95s;o;rG=|45n_^$~c9{>Q*)6=uFv-9)wKYaM`
z$&)9Jj*h{>!DuwPqN1X&uWw;tVQp<~V`F1^d3khnw7$MRBO@a+GV;}{S4KugGBPrP
zf`Xi!oQ#Z&l$4YN1O%Fznx3AXnVFe&b#)jFW`2HtZ*LEW!`9`_$CbWo2a-7Z<;N{rdRvuXa}QyUwb?d|Q^+1a;m-;$D&T3cJEq@=L0
zuvAr5ZEbC(rlzW^tJ~Sx9UmVP6BECG|6WQ;%H7?4XlO`FOG{Z<`RmuOPEJnZ;^K{s
zjSdbDfq{W}d3n0Jx;i>KbaZsm($WzT5d{SWH8nM~w6rrbGfYfO#l^*;p`j`&Dqp^Q
ziH?q5TwK)F))o^J>*(k>I5;pfGkg8|wW_MBnwnaBd;8_(<@)-1c6K%p2vky1!eX%&
z78bIyvRz$W-rnAyKYuPNDhdw|KRG!G3JT)k;d%D#+4t|?ZEbBqAkg&mG%qi&fq_9*
zR@U?9&*S6cqoSgknwlmiCR|)xl9H0l&CNf2_~7B;VQ+6gIXTI|!0__r%doI85fPCe
zKYrxq=KlQo)62`Nx3@PvJ-xHDQ$az&*Vk7!ti;HVuU?4U&HaR)j$H(XK
z#>U1|Q&T0{$sKnBAoGNo`{3b`*!&mx&?t*LEzxpmsjC_V
zSnuWr2GDCT_l$?D+&9u6y8kgXGlo>gGE*QnVxCxQB+Nj+8bcXlqq(ZDN=x(L;2sVA
zgW5xYz+xClr9k|LxUhIY!V3Q1@}2GGojs-Jih*f2&7~DBu|+|03MW^>ffa9>-<0fJ
zZdcT-5Z(({0o^D4KcGB`{zZj9B)1uPqFb@F{7+)&)%0d%9m`|q*~-9#qVztD_cj-A
zy|(Pe{INyp<+a?ZwA=WQ$JCE5qSC#L`*sc4;xk`;}3
zmRtj({=U-B8mynFUM^M1pSM=TlsDbd(?A^FUEzHQGzJ}SySiWrTp7%{b$hvuGE=7f
zf5uL--kVC?PFOJ|_%W)}vmEJ4YTrBKa&c765$ZoocHT?>q7Zq$nbkh6m1f{Bu_`EN
z_-;P@Nr`zfHST79>v5EFEAb-%W$I|@mzt^?$4bXN5GRKy=0zj!%SiD$cTHsd>JU4!
zt@B^51S8|4)D7oxxwG~c7Dw)a?L_SBlXDcVx7S3C4@Ekc00jvNUaIexKNrCC(fek;
z8+L^Sbsh&+?4e}ddwI#V&j9jE@9Fscx4wa2cA}jd@!2q4ys#i-Z5wbtWpjdr|Evi
zH;QIbkboMF={6v!xa?i%X0?RAnq%wt4lWV}n9(h5VQ(Kbs+nMdY3vB=tsxB28JscZ
zlEoC>M7w=5{REXeN#4c>+H6Hpf+Z*C#?@7N77-$V9jW8CwHmhws(3MIJinPQaXgW<
zo94agF=jHh>_{WX{TQABGect>Fw>H7`=`2LW~$e7Fnq*AbqJ~tSRD9R
zZM=cAAhi+3G408xt`?CyF${2vHPgD5pG8hyhr{9oWhErB$`>~n{$NID0CGfzf^+FN
zm6MRUsY_+0w}k?&9c&08pd*`!?cq{=`6~zphgV=()HcI7|1qdP*h1{C+L*5{%&)r-
zUe~-T?Ra&vXH*1cP{DdLf`wYi6ZNn0>xIs7xgtOGd%qb=+@9@%nOApBcKoWH>3}7i
zwP0o@#H>Q%MdfB7M%?a~n*a>BgcB`r4Pfm!=l{iQejTuUc+NvR5{1K~@
zj1KJb#BrI$!!;33e67_DJ}+bHlms=#cSI6is>#5{@rpG5h1yy}YwtO-!<Z<{8XqYmg$W!{47(QG82(6
znOCsK$u>1N^7I0j$pkKq*xy1?*p`$zki2#-W4gD$hB|y+mUgXA;#4xh^};pCNv?>-
zS7qX{9cGg%+)E%V!UJ!D)Y@cFb|zm%!jYp^Li6}9d&c
ze4CFObJ?bl=em}uZGw9>`CGIrCI7cbqAiMR0rnz%c!7h4Y<>V+!@93Co{TaI<;YO2iSP3|z|%&`oN9@DFs`JC+a2Q(lVbyNuRuFX^YZIxYxHo8GaNvCrJqoi^c
za=jsHxCdeekB*5qVWty~s=krx&n{+FCE!9|yfhD!*tue)4Y9ZWweHuc3F#@vUV70d
z&U~%Vm;|?inPo*VrzQCydZ>u%mojQhR?h?Xtcw4@Dq%&I`o!_*ettnfQeFJ?#rLRd
zM*0!whM-#FM@^cUE+y=H8?x&y65Ig&3_U#q8Bz`3^rSbhfy3ynA+t|ZQa=L{a;qo8
zb(^wDwwyP^%?Dvw8e|@$y%V^iEEreUzU*0+T1kB(XeLVXD-?7H%1v#hkM>P9X=36Y
z?zW^`cZI{hN@Pwlt5n)ufevAOVswnEKB@Hc3F7lE=H9$u@DJo3c>qm@xQJUBH8(dF
z45Q+rNpsVy5Q5HQCsk_uI4yf%(cLd;!C*k{KJm)%&+kAMkzlFF?)FgnES>dogd1kN
z;0JHjGjgXPgB^wWc-s?wA7h_54>HMYLZs{G-|;KYyLGVgcWibO&Z5dc*$RaU37t`J
zk|v|gM*ps9j%~`2+ZRh!S?^qeU{$Yy-r0~kEp+hkk{VV#4fu&YVx3j_ew%&;?s&90&$CtN2jWPMbTe9oFesskj|W-qG-_!SWKV?o;cfUyR=^
zK*DT=oLOv#ie3n6q3OE^hArueJSBYN8@lPKk>aAZ&Lnjr+J6}mpfDu{I>4&C(j(+)p3qiP+}ms$*NcKU$>#Ep%c0qfL%CVy>bNq`)n-qy{U
z5GWNK6K+C#@k@&Bpi{)B58aC45su#`j^8zhEGTO~WJgviSuAcq;P6+9goEOkgGKM*
zAuc~j3CVs^Ann%dUSv;nWe4@^dD9q~T8j|;rE>d;g5H7|b(dIjru5@a4=@J92@}=%yTf#!>A;U-;XbaB`+L2=<5t49>}?kouVmOrPv0
zl=^7G68Txs0XDn>iFw&h0ao5xKcCf?_-!HhT(;!I1P3Q~YeA-SC-y|6Y)1kJtu-`z
z#Ciui2m8QL#!ELj&5qJ%eooRUamj}OCj~I(VE&?t&ewDIarA-HI8huJTl4Do;&(ia0n%7>UW3w4~GJvkYj@^cD;!7Zo-y!yKll5
zc;MR8PH)LVUTFRIev^=8zqq-QV_U|wKK;)u57~8z;M-2JAEr{H27qoLuL~k3rf*_@
zIb4&2#K$WC>PeteY@LjYd>|1Bt%
z$5)Jb*9AEl`7N_rK|(5}N|Vd`y`Kh~Ye)B3*Q%bjh=oD%r+x;P;L~_gwrv{b4`I3z
zqo9cj{&PmarsPCm$CI4BA%FZH6$IK)4+PzcHzSIO^wQJoAUD||+(p`YK9p2r=Uh({
zKuIN|6`4vLN@vpHc0Z7^NKku+`RLV7
z8L?>0l>!8(uhYE?Y>Kko#>q#t;(e>mgFsqma2n4+%&L6dP%f^1AI=W73{|k5SR(wN
zN8BE|koe8v6$khtMH56_n2gwem+VQ_^@^w=D(VZ+Wlhgwa0G{S)yWAYN5}rDtKB!P
z&7qLeNuOB130D9VT2cv2X2;MQ
zXT@TS=;B<7;yWxOgi(?lFu|)4`0(|CK4hb)ac1AHBC#5dn_S9wjmLmgqw~WYI&4p5
zQo+@w=RUrg4Do#hEc@ev2He*CFP<`o=bgSvpG(R8g50rs&GG$8cywmI0xABLMhF{y
zl`;9r?Zh*vG@6Egw6E2#V{VJaZGr6!E$zUS=xXjOZ=}dJq4^=d%JTq!tTlv6XPD@p
zN+i0NBiHacwm}Uid$Y`lhK(c^WI_h_a+usFD||`O40VyQ?jYOGxFaR3jFRm3r1&kp
zQwe%)Hr1nDo3
zh)hNHtjTPd`!lubf3N4yVoSK-WMR`n;q^rArc`GU6`8kmcKSa|Nlv5Wk_xF&40f+S
zZuu#?RUF8R(-5RlevVc!!q|~)Nk1!Q*4TSmRcCyB<$>DW0xZiO5lBFw+>8AWVN$X-
z%}XYiMuS37ii6atQA6Pwo@sBrGWmIpB(%->`X<2>p0wZL;Bnr+0V~ndSmC&Ltz{=e
zee=8Lnzq^VMG4jEZBSE6Sd;IqltWd!7r0jL(+;@K7as+2I1R|iERt7hsdEppj*U_?
zbF*6;;0zl3*Rn<%GJzOmv9u(s<#MD17^^PnNlD9HIt=g?xND;MhDsN1{(aq3k-rV7
zX}z^A8t_vh$n4d2k^*I0T+=mW%VV9tF8Y9Cwod32zna+Q#D;`M`y$Y)vO#2lWZ=#4
zoc#lfa19=XUP9&~X9I#k&jj&B`{7}k@?CN>ZmAo(`hh1rdXF-(W4XG|!`xjQXI&rG
z%L_zF2W;aW@?YrCLK<@t$A#UDgD@@`zsNM6CZfF8Qi>0egnZruz_jxJs?+4Xg-0!zD}EniXBVkWl%t7FlBWWkvmSGcy=I@b^3V>Eg2E#u3E
z^h(#--wONb&mhUnOpg
zJV@u7WA#zyc;8>-eV6<8R~d@6N)!3?R9`)Ow+!k_Q`k!VLL(mG^DhS@{sTl93eR`hHIMm|PVJAPlge!etb(=%)BR>-;
zG#AC5JbLN)K;WWv_TvGg|8qx4-+yx9YF3iMj{JiujP}d72BL(qf5&nM*cG{lUACKV
z4W&}+)}B;xDrp$lB^m3+1|He7X+r&5EK
zclLXJRrOW$4dX
xMaQ4yQI`8vh1`Gi*a<=Ap=$c0{{S-
zoi
z^z`)X?Ch9K=9x2Ru3Wj2mzP&qSXf&c
zRaLgOwmcrMzrVk&t?lmJyEks!NJ~qrtE($7FAoR^7#JAn>+93f(t7;(F@ZpEaBzr@
zjy5+pH#RmVlgU&n_3hiYF)=Z(U%!rsh&Xrd+|i>)M@B|GJUm{$e2GG#4jw#+#bWt<
zesFNGjg1YP&9<E=R5PfXkv-g=ZAClaV-w7wIR`u7DZ#!JBo(^d
zTn3EhqeKjcXgrcS{}6-8Me)s`OT;MA^CAp`imcSt(HyEb$_b16VI^9bK@aeFY!nO@
z7#OG*XsE~H&|&&WBoYQUfEgI*iY#=wK};Trugm0a`cCqL220~oI6iEi4~wb3NJ}EK
z{CQ?j=nrlTkH_&L|DtDd^+-#I|DuDQC~_Ey%7y9c!GB0o7hUM@PACkOyCioJ`*E5x
z&2Jg>17!Ftoy;b2xHNnan`Q=eqH$UN9188H((eqvnXNc95|2hTH-H=H>%vWS4UEyS
zABX!&1ZK5wVU&6H-~ZDMGuYiw+2tcyTUOmvZk6k}a|LvL?m1c{75kWGIP
zes}qIIviQVW@KPwU~GicN9da%jZERc9G5o#-4D<5ryN)e)!g8h`EP8w4;r>u6o${T
ztbK=;XYv=v?~H#pT2|a21WT>64F0#8Qz^gd&GzRomYRu5fzcSWLo_CDvD*5}t4*b#
zcs@J^ZMnon(HI7A8T7l>qSi(+NKCpJl&?#rd6WDZJgB)l%OCGgrm63wu$bKcGw9`%
zXxKjv{%=BNFMEX+dwl6_UhHnshA3Xc)Ij6vm%-fW{Wn@f
zBj8v5|Cbg%iT%$vO=OE&>Ub6h&tfsmtw_uO5*qeT`2Q32U8-oBh>9ih{5gRZSHGqh
zjVT&k9MO!bo{Yu;0N}u#SPY);HI%;6>UGiDD0^W_^G=||({2mR=8cYjX@WEqpC{A)
zYF=fhDgHL~{SAdWYEqrKjGbu$D{YZSu)8^rU5d21s{Xw0jzRayGgrU6`{(o
zYn&HhuUEZO3&eT#cC{OQu{{B*u6GZ`=A6^!d)?TNQu^@NOHW5nk0)rLUzIZN_Qy=Q
z3|1)9Q#xa(cfpI%qa`B(--?EUEETr`<5|e8S8k2`=+)GxUmslGFEsII#qwv+Bkjpq
znU>w`k-%0(uTvWbc0H9Wdg!C}w$j(lvGrX`W};KfLJp(*-mB5ax2LRv4bYJ5PH7&m
zAY_tn&;QD80+&Moj7mYbokm=~=+*DbEDjjC;S1e@j7}>a$UO(U7
zb*-Z-E#`XJ)R}u%89-y-a~>^0y4Gv+itj)mQ4LNfFVAJ2xKpb#jdJl!J|dZxewBy2
zd!$lvrL1jSlAE~kc1x7v+y;f|_KqX;KR!KwzWO0`(_2(Z>}@P=ed2pv?T`xT*OPNc
z>W79G^s1GoWpvtGva@gYgD2=C%IbQQA-E7HJNEb$odBh^CZs40=15LYay#@1&=1nR
zG{(BMrTnNj`M1E6`*Z6h8BnFmd1PkO@y2)B1SowS*zmr1to_wEllva|D|U^RXZh7W
z*rQm!xwl~sybEAHk!Ch?{%Ea0_;Pzd_=S9>qXTye4h67PXULlqR4Nf-7Yc^A-p{pmj1Dcv?FZJ%cTQN*lOw9uPK?`K2?6v-
zu}`Bjgn@*?&h(VLHIPV>8nAJlRKt8k)#~=LfKJKp$kyGTbRmO8cXU~oW~rZJ(3y+9lNJHSIXJ3pVY7^S&-K#Y~WM
zVoJE06{Dfx4l`-PxQedl<}Yi2ogUnk1slbZZ*NFt?>}1hbPu6*>s9h|TH7}j{K*dZ
z!u8{UfbJ05%I6XH0o_lD^dPq5(h2S>Pf
zDSE7I4jyecfj_=g%j%iY;e#8r#Jr>zBw(T(oqPw_(^{pU0VO(qflU~PfL->KWoX0l
zHzYdBe|G-b6vIP|4aGHJ8VkBWF^=+%@$=kb-SZANC1cjLN0OjKToG(O{UK5O3?2Y?
zm9(;Wh^@ST6OiiLdL_TW#O4KCm76Lh*JN#aYae$Oe{(*Oho%d?wVBXl$X51wE-NusjwF(IL4=04N(sQpbWogV{F>J;g)R
zPTAs*#?c!NoDgA8q8+=@5)ejLgbu_DA(4P92?8;?5cl^1PjkDKPSx22v{pTLC`Czc
zKjl+>w>vaxb9McPg7`T^$UApINQYLVxyLc3_2p5gCBx#81RYENRbvx5oQ-S&H?&xNA|{D(tVEB
znL6t_j>B#VB%KuOna?Cp_df*TBnN^dY_?O%a(Wj?q(M*zCP?b)LYzRzEH(Lc8r%igZP$P_Y_Uo{(o^_hrcJ
zWAeTu^Lq(vhb}>C!?_xHRMX2J52~q_T*m1<$Q@{uu0YOj`!ET;>?eqrFqv(U%R^`G
zoyk<*W<_TXWIZ@Dg{ZTt0e*r9@r_+vgKG6xT=08#?K4>M^(?)!AhY;}{cK&i^IVoo
zulAUu|Lmx6zQVLNya!!-iQqglRe1P?HZ8209Zc$fLZ}T-P;+T30o+Pr9n|c-#?m~r
zpEDrW1nJyANIrC+$*^kX&|nBIh4x^dd3WQP2!dv(={D)5QMbsTN7H`f;#nqg_+Xgv+;BnTWr6h6`_Box1i3IJw3|
zLY02yzL!K~U#Sy@$(kEcZ-!gRi;ud58yXmJX1Fr2?ldsdGM-{__6@{QKCTz?*(Sg~RdmaM*aWZo(-jB<
z#H)$NxB`H2M2XHYCnb%sV09t5*`;zYJZ=(>2Zi(>VzyW!c85;dFa3ngkFZEh|U
zffe@7i`}xgJl6--c}xu3XPqAuY4jib1PqMoncc80N4e-aSgmI9KlV;*2kb3NV&wk-
Dm^Vpq
literal 4673
zcmb_gc{r49+n*U^%@&CkLxnKLI+!eD5~Gmpq-8b?W5~=f_MxPbCre1OM_ICD8ABpO
zB9g5LNs$oQJ+^nKujhH*^4Ce
zS^@w-))ENdVq?AB3+$~~FLoCLGXnsiOKWCkW@2I@D=TYlZEbjX
zSV>7KEiDa&LVfx2B_<}OtE+2yd3k(%9DzWzw6xr~aU(7+PESuSB_#!kL{3jnTUuIL
zTU*m;G;eS3j*gDLzP`l7#LmvnhK7byr%t_i@xs*9bYNfrgTX{aMKw1!4-O96+S+Dk
zXIEEOtE;QSV6c&qk>KFq7cSV_+Xn^)
z>gec@NF-%t<({6N`1trMSFV(omnS79wYIiKMn)DD6?u7im6Vj2o16Rl`%@^C_wU~u
z8XD&2<{B9pg@%S&SXf-TbjioZCpSZDx1
zN1x_}#=7GvU<}@cK+=_+sjig;6L7k+b{b~NW?m=ot^}07H{QzM+#2iej@8D=>g)07
z(BLcr4?G18rg;!aWH?P%c83?vT5qeNvMi7{&KYiLX!Hxhl5}NVDHJa_6zc2itK_Su
zQ&56c%QYpH!
zvOM5l)CLraHv#jTokUhb?`WvoCQ2-)p=cZ#s-mR4eJwCp=O-R+fFo~d-~0#Vk2sKRuh|F*S#
z#$N^}X%rI{Tnn%2tf{62fx*;Z5OsB|CPZ5e3xlYrIXlDD(HM1gjMi_WpCMEMr8d}P~4R$vFiMRHoVqLdg(Np~m`%AZj)q!qDmq_>-lpWz$RDN6h
z4B1cM-_r8GPZbXPJ563xZ{kh~;IL3U5$}N~QMQw$@+(O=ESy515b=Md$bd-sXNp+0
z2PdLQF1oTb2oCRzrV=T#dJdjcYbpj0HoB#_*7wgLa6Vio#Mxc^TfWp(23nEvAiG_=>>ik=07
z?-Z-2x3#AyQSStrTj2cE|0InB;ai0N0F1~Wm=y;g*`XE1Z@kM6WG~PwT7w6-w%DF^
ze$kQvYL}e6Wjqo4F5z>MaZ%Hg0vz}AtL)JLVFU;T;06Pa(EnQkA_6f!Wlmn}#E*QN
zB0ep?+?~3AZH{5ijEI)~q1yMP-@r4ct?gsc=M%HDFiE2^-p6|%uVYXcySR&c7&e06
zuxaFc&4IxotW^sG8s7g}z6n}%@Qu6~)QMq&D3lH1x5$s2*6lpuFs9QUNfm_~Ujd
zda*ogaNx?xG0FA(P46coNfS+8;d%9y?yF}L0v6c5Iqr{LE#UlW*@bQdVCG+J_O%m{F<CHXO`bCjn%F^x8)u+A-P96F>`u#Y&S9yhZ^7txH&
z5F>oZs-6FXBp^8-=V;)uHl7&`Tx(tHz-hFo7tUOA2z>H+7x~CwQBZZ**Ov4+(KDQUsXw{zt~r&9(%mp?K#~2HEp@hsVTIthHb=S<<6znmsaPPBh-__7W)k@$$GiKOvn3=riSd<#UIF`9}QH60R#H3?d1RIM{#_
zp8
z{_6d-<4J8=m3aMSU9S2N{T169{+K9B=xp{W@ihG0=M^Y-gZ>)s-A)5*f
zj(7T_`8VfiQO>*Uhz7ay%9UinIa_p26zsqO%YzdMp?lI%?@Ebk$tD(43;4!fP~rTOTT?M|)UooxVX@i`9mHPteEXXI{9y49v8E
z_F8CFO83o~Tp}t|lCR&3b2u70lz!$*#wJauKRlaGx^h29q%CLNh<^
zE-7L&ex7!Ejc@)#0Dac78ueP-_zuY5t+0M8Eb%FueOXVP?x_QN2XBhdo_2ClW#9nF
zSFv1O7v9AEU+d@})F0n!u9HFaraOmLJuy24eADEITDTD)AP7q0rF2Qy7}Rl*mftIo
zwlFd!INZ@LVSUkcLau9D(A0X1DMqm0^nOiL8jtz1;V5xt%uEqBl*mKNK*tC;bl;1~
zG&)c4e;kuPq!?lX+$WKmkAGm@@VyKVs*ACpi>^FQpQYVj)JjkvyU-^0QM`0leR@WO
zkJ>HG073y6mIWXQ?ZY8Gz##i@#n`5&>Eaxwp}JteWR}sx1u5Q{%ceky{INEr4>GdTn!pjy53VW3!G@UEzii}3Sx*U#>^1P1vsu1jizKVN^KB8xKuBIEXI
z_=<}(+I?>#$n08wA!?HQJXA0dfn=k{obJ-oY|hNB-aU8NIIi+X@e_&oq_Y6io26nJ
zWVN!kGaN2mLidyQ&E8c2O!;SZ%1~X9H`r$Hd^qB9m(fD<*NyDEgp)J;0e2WeLpfidj37uj=C--QqBM1JZBzJ
zqKZ#gHD4sUx6-5tQ_%kXp4JffV(fc8t!KYXXE9
zy@#ikro0nEtfe-jgI>lE59L!|mbyD~*mgs$I>q`GRQTlS6zZd0RlRe~B&=VkV0DTKH@$C#UhjN$wvtLf6p4S7IC;`qC|9Fb$3zhBBo*U~&cfSFC`o
z8*ZcinGD!%cs^4pO9in0?sHTlA4G{znVoWS^Ey|4P<@wHYcc5MD>ymzsKU&>CvSZ!
zkWH}F6van6OWE~5zEztl&=sH4Eac*y)qA1Vj1PWCw2$WDTy=xVLuDYY3A>V4nUQ+u
z`Qmt;teosFBua_f0i+<=y{}f@^7iLE$VvZ1+K`Hlj`rga?78Tm_|o0{oYvy^O_)
zguI5|cN^!urxOR(TQ(u0ZsnVD`eQ-v`+|?3B)sZI8qU}zBx+UE_OL-TzfR=YTo59j
z$0fy1Ei^~*?PiWrdr4}inkOr@&7b+I>x&^#TO?SU9EG<
zpQ>DcJj>k6?lJyhH1=-e!)U>MEvF|}&-rvre&G#P1i4*O8b(P;y?GHbY4EKsb5d+5
zY$RmxmEX{Jv!y!<-TQ<=HM5T%bO%p0_rz5llnVBjSv-7$dap3(^A#)t?DTE(8TI&;&tLh{{=`m0jx~PXIK!oZ
zsG^-|iqLN>>^;=PLFp@tS)#|7oAJRxEUCcXZ=Du1U>6s#f72Ci{qLJu?=y
z(n99DqgRftdAJ@UjeUsfn6uB#!tpBW8H}X~#LfFkF4Pp98}~L^vPkmsGlu
z`vL+lIHLs{R1XR2@wKUmKeC?t;B%eXXz5(6v%nNY?M4h03xRR8;^fGS{!phxRHL54
zd~-^dt2Do|MR2RTk{|-CiO>4tL=JFmhOgrHIjcgz2
za^D5_BL7R5!5`RhMSnmAOwS0EAI;;?u^aa#zPd=_Awa#fEa#iYikHLg>DqBfo`PCz
z(n5?u?OL-hkJoWpWQuSuJTqJcIQcLyErTj046WE=0kzVGcj#EKxEV;1zqoN%@7bdk
zRoOS?f!TCdce?M@mrEcaw#gbX{IkV
z>c+XVB)F5f8fza3%e_u8jzt`JRxawd+ptD#ojcT`1sq#L5Aqe_mIL*PCfuzYvyuFi
zv$3F)xw%Jr6meR8AdPUZIrL>k?GZu3mH}H|ih#;fzuA_xd%ObiSlj>J_cpkUeN%wP
XSJG9?E#~(2uLqNp=7xpHOJV;4UsLz}
diff --git a/resources/media/quick_search.png b/resources/media/quick_search.png
new file mode 100644
index 0000000000000000000000000000000000000000..25623a3ba7932544261b3f09846c8b7d3dbfa553
GIT binary patch
literal 4453
zcmbtX2{e@L+n*Um5?K-oVM^JDF~k^SX{;gH5+$=)$ILV{7(2<9Eqk_#vPHa-2xY5~
zP}$#PNwy?Pl5F2-^}Xl)mUI5kdFDLN{kyO0cmJ;Cxz3p=b29@jj{O_}0D#NL5N!bf
z0GY2q06Pow;a*~EwY8#I80Z2@J4I%g3sz?xQyl=HJbu?22F%<;o10kaNl8h`$;r96
zxHvdCC@3hHn3#}Ar1<#w8#iv0m6cUhRW&y^cXV_N4i0v8b=|*zKPf4RN~M~cn=2|R
zs;Q~1udi=zZjOwMoH=u*zP^5JY;1LPbz)-T@#DwY+1XA`PB0kE%E~G>Hg;)gX=P<4
zEiKK_(NSMte{OEh#>NH?hX)1*wzsz@BqZqS>ZYWmeEj&ayu2KVL@q8aT3A@*GYYIncm*sii(P&qN2>q%(k|+2M->2d3nvw&Q48D$;imGw6xgT+M1b}
zMMOlPP$*SZRV)^Z$KzkWejODR_3G8DkdTn{^z_is(E0g!J3G7n{(b}k;pXOsMxz-F
zhOe)$p`js#LebXNe(~aktgNiRzkg+AWms5PbaZrgcXvTSL0(>7ZEfxN_&AYBymswc
zadB~u29}?h-<=f0(=-5J_oJ;JFhzE^Ff-@#u2!~mTT>GRj_jd;!IQBB1%?NOX$=4%
zwHOo(&YeIP#}b@fNt%%P+Ion%D_#?FO2t&slyZ#V;%ewiC0P2JS>b%$aq4)87K)1l
z$v`j}JP34*IKzWTq9GWXknh9@=KB^5g)pte#gSCJ6T$+m|I>=O(uBCs=@bMM>h0~V
z;H|7craD8F)Ya9YiZCb)CeO5xr}>cR7=}EFcJK$uPZ~6VhNHSt=&odv_!cb&OZKE|
zLLfi6b?9`eEA}@%iKc-0j(Bc4C@>?3V(>Jml7iw-Y2wTa`NIjJgQtC$yM_Hc&603_
z8}t(-x_&>If}zp~Rz4JhCd7h3BYRSDgkMU3F#KUYMkQeA1Uw3+2vd?*RFj9PBB4JI
z{k64~r$5&(NqP*r5z|eL0CPI3tR}Cjs;nvxhvSaQt1IJF<&~73oK)c$EF6wi`%U=6
z<=^S_u}n4+(1|5kH6?svT@o>b!ZX2Rp31R}wMK%#F|TWNc>@i+wC
zl};pVm$)Tbhe+QB{i!vxwGl)N$ypP^kjE38FrGv@1a+G1Y2}F}h#TR^B-;NN^ma-l
z^dATRCm~a|eL`D3{{3s->Tc!+GhXHG@iHNVjwjuPOhs9fh^{1e&CT`Bag8JZSXn}T4;2-fn3dm=l|;@@KXO2ahxPw+C#cjNy0?u)KQSJW}C<
zdnNzhoG&%&+D@~vEgl==j7YjZ09zA}z%}lz0Uz)hN^-DiL1S;u@D5Y*ghYkQO6{^i
z{$5kdJL~3dJy%ipw7Cy9dz3r%k*jV=M)CF0gXt9aSY0kx<(nIa1c+k3s
zgs!5R4Q^(vBz`eohZWsb+@PO#buIVv&k+yh5qA*ezjkWH!9=htbu4;A@`_!$zY^tH
z<%pAp2I+RjzyfZ;Z|-oM;gN|r>}d2iYyZKow_?&xeEQb$G$!V<+L_JH6XU)7cOI7b
z2g_!qmRB+6IQJuJ{j6?Uz0oQbX2^T4DPh+xUyb23km<=EAwG39!ViUaCB}3oN965j
zaL&73aZ971)$?JJj&h^$xfWUJ+?=xv4~ww?7Kg|u9bmNCXhmg@ra)}Q%X8*KasEqO
zVlp~wLd9!4CX*E(G%I_E*v2hsSGhCrGj<*kzTYrsb@Z^Q*Lp(8p#x)IMkKPbOqbmwgmps65;?P#
zevZ0N1u(SK8SA8eW32BShpQu}s)2O$w`-6#K6l-tx4sfTs1QjnF)+I|mV9*eNgZ1s
zB_7{f8_*gS$jNwAXjks~u{YJLhPW%q8kzbBBDka7S(a
zh_^`U#g$77uzTQWd!)e!+Y_;Wm-P{LWE$qZjlL4S*oU82my4-87vRz@778X|ORua@~dx}voFcgvT{%J89q|Ml0%bpi32;A_3{ypk%pXL))AHt
z#p;IZpRdvc!y_M^YzVe#*w3jzIqo}OA<>R776B(0Ji5>7HDPM%YJC*Yj?4axnP3z*mD$L6=+p=iO
zvG7^ZB7D8Q;IwqZ9hugsf?!pjb@fh8!4+w0^D!R!-`TmoP=_#4$62BJ!gtFuz^^9p
z;txM7=%ydspmk1|+&Ne)8j`C(iP#HeM@L!B67@q6Er#)}q{JG`iu#yTNieE!fA0Y(5D$DEX#_t}mUJR`*-+
zZa!YS@$KU=r-+MQR`5G3PAz
zeHxS%%H=x_%mv%%BjbGCnvbPOOuqW^cZ)^CuCe;jot$YWPIL2Aii+$x4s31;;w9HQ
z6lUZ`lp-GoT4AL}3qD^d!p0&+1D@T($yX%@9+z^osfKQd
zHjaY4QvgEl7bPI)`Txq}08G~QsHAp>qPtxAiu6Umk%7E%^L<0SJv#=6q`o%`Tlu34
z$-$PpVhV>(UVUGqkCnP^iRd~V*2sn5tZpC$Yjc8I+wMZxO%>IeGWY;EF-+ve2OzKB
zo_z;cyleC&Z|SyHIH{nU9DsNB7-to*hRP0I77Ra^k!ULU+5woU7VGXEJSF<>I8Hh7
zwbx&F`-?i8PT=f6mZ*=sO&RN-EKHq)YkZUIyG>Sc7F21G0}HPpZOR6f@7UTnPQScQYSxS6CD
zN6`3UI$2rzMx1{bNN9CODte#dY!UyNER~jYp(Zf)po&Y$O#Ka1_9E!;l*>f`BU@@^
zX+&VcIr=Uj+V5>o9dAOL#}z(YOi*F&^a8e6_#NNQ(#B+-s>g8P(o9lFjYuY{>vVEw
zG_mtg4H7QE%lrP~)sP{3#`;`BXMt9tW0JMtB>p2ebU55}bhK$U{_Em(gqZ9h)xew)
zxkGg9WSw7C@RWz^`U+7w2ihE{Y#FoC3BCE&=XGPR%ps~EeMW}oL_{146!v1dn7z>u
z)FH4Z)6O#Hb`9L}y$On~9kp^y(1D$Hk6-3x=f22kzOs&z17;(S-9F4uRgVKVo8GJS
zPd!t?mCS)s#y84x+M$-lkOk+zZVKR12HZm)^36(SiA(Yh2sQ0Z+MEz;BI@-gezpxT
zyKKQ10#v!pB1j><+?bu+waGfRAkD*{s6S!JdoMgo<_s)EW+sjh>`7`>h#i$dUhMP1TZWNrcBYvVpi?20vRK5HR!PaUpN
z4w9kI_4eU-eE9UN^y>`!z)u^E4MdIro$`*L
z@HjjBPjhchd=O37x(zv(5OHgqJFaRo?4e6>Zu7E>YvE(lJuefAdV>}(ykVvICEe$
zcRmHC9X{E&hbFkN2@?etU5wG!b_kRX;Y?!z@rIV@Ys-qi+HZcc^$r{VU~jO6cB;zt
zHZP+q1TNOgY~k9CqV1Ky*Pyz4N*P&qz2wP&6bZ
znydGOY`#cq;by+bbmy(j>Rm9ububc?{^gp1qa=87*3I}+_wvpQ;_Uy&ZK!p1IcVKr
WNn|O}@5R=Cb0a-7bg8c6h5rK818b20
literal 0
HcmV?d00001
diff --git a/resources/media/search.png b/resources/media/search.png
index da5138ed5dad19069a0ec168649be32e27a5c648..e658c7b32fd454776dd27abdefb197beab0f7586 100644
GIT binary patch
literal 4246
zcmbtX2T)V#7EYur0-`9=1i`=}HGw1qh@pib5uzeB0%9t~gd`>b0+ywSsECLxC`}*I
zq^Q_HK$>(=SYQREND~z4pwa|hu<~~Hz1f*}Cz*Ty??31Jzf^T54gdi1AAtZ70sf7ai+5OCaqO(nfICeJBm9M+r}=(!0N`Gf*k_^;f6v?&=U}O>
zt_}u+aX1`ZEbaRb+xs%
z-Q3*D%F6ot`Cdy-MfcEp=M`i?d$;p8Q8`Js8-N?co<^TW^Rcl{htd_(Me!?fc9q?TIejJj_^Z^qoOcE8$^I`F=
z0RWUKk3}TYs9a?d)zh0{44N!111Wn`j6u%&`*ruTET~@I*g!VbKJb78Igmy+qJT_I
zHi@EmNIruPl}l9S`Oq00B+nT1g&4_yUV|YZzLl~vicRrA+F|y7v*NFeL0()g3kiYv
z`}>3aVPGcP6QXBiWCYQLLZDC`zJ(4afWam5bQm0!uO#1SFjNki?aktPGa1Tjv_ulq
zmun0Hed9Lga@pRbAM^|knD_rC%9-GFz~zL@t$L0@a1;>F65jKoKa&
zw?n^gt>x+G^<9!BkGqfWW=Ms47{Cm55C|AT2M#A2=orDs2pv6`hX(>qB*Eb%!yklS
zUH+bKFNx2l57mbv^o{i3dIm=NhPpo-zij@!p99mE?6nrE3G|2gPi(yp3bIxdy7#)Q
zeTCL%@(0JyjDIy+SKMy|Us`7!{C72{kbl&h<;$jjX(kF8LZwrEs0{8}we{9ln?go%
zy}5MidWmbI&FS2A(9c@)TN_CyGCYkzJRJ(vgXl}=f=muGeI0yBRONkSCWG@|gI-UG
zg8b#+|0iVDx>sne$G^PIYu(M?;KvJFA1@z5n)`CSm~0bA2Hl%ML#1xm9>Z(Ag(e-#KK_725T-{RQv6upH*kp`B%rlC|lt_s3KlfKX<=+eweHcTC`#V=_#kny
z^urAM;?{*>tNEw5NnHAqr}Z{=LCrf?k->L+q}4Jr!pNq5A=1VNC;D;$HV@vzEdTVu2nI1NSXPYui219_t>dvOLu0j
zbQ+q(S-zf7k*P=FSS|pZ4SmS2^JAj&MMWomx6_>N=tP)AD3%RHDz(OZW;up+KC8JT
zWdrivQL0Hv%7AMd158|B-YzPN83|OegbhS82V!Jf{?K@W&?xQ*+MJzwT(V)~$^4zg
zT{jQeFN(339y$B{2Ki-C&uK+q_?j-K9Ok2dFKplRoERZ>u>4o`m5m&2USrNFHGrs9
zUi&kw6Rqy3@gyM<2%Gb*2|ya2{V0#~v%sWyG#|}FHhWQl`EIWL{?Qc|ibLFR3=!zf
z&lU|w_I%c#o3XuOFB{b2;}U)3z^*f7L;>RsWdV60v+f%7)yG)@V)xVJC&5WcigUE^
zF7r?(B5lxj{x!BCj=+hF*N^F7drcz`OA3(ljI8(lxhh4B!*44(RP9;akyp5{lLDXH
zflFa!iXRTp>zh$YBe$&VnjS4(N%l;u#qJT0iTIG<2Cm#Hzuz{lBe%Om+An@-_0!_S
zDp+O9+^^bAEzAqqAz`1!DuvE9;$?7mTEai;eFDbWzG|PjFJ#bIVKLk7uMl^6K_oz~
zE38`Qx{7~pt=-a#jFaVRt>}46+%i3W%sBj4u3w5S`9$7hjC=rjv|Up>-N0oq_e6>M
zVq;z6>PBg?XBOm|Zko~_bkoa(>e&a}qdDEch&@j*O)ouVbEklsu;I?}KEavl2B$=A
zXh!nONAl*|W`bs-8J{gEJ@=`{F
z%PI+x{x0toWxENEs3-jz{vx6Ji;huRO&hhrwwkO-p}1`G*t9J`?Kg<*xsC8dO;zDeGd4$3)0A7{tTrCi-Xs&7;zq9_x@gm~m
z00hSGj7W?wo@zI7A+uJu8?|&U$UE0{@0^?lVR?9=fujOH_wJLMzX}y!7U1F_`0}o(
zCi2bnTf?>^ZxZV^T~)pr?pLA-I5~{lOo~@oaj7n%B8U3<+u-@@o_p1&1V`hdJ(I9a
z9}-4w7tUw@5;I~k_AKwdd#sY_P5Ztb8_NBMrQTr#a|u1rCv}Gm+tkuXt)4AMgkuLx
z<$jY=JWBP$|89WZ>UsV0MB~_e?nU3m++7a*Uq+`>4D1hi}WM_=BYQgvFIzzW+lQ$A(!fKYIy
zG_>cjpD^!rmI>VH#C`v)lBphy-E?l)!I`=#GRFfZDDlA^bnH}4u*IJuw?Dl`R|@pj
zqqHSb+F#CHWSa~Jd
z5WZP~kaKT>`PpRlwnW2Y6D3LD#bq&@TjJLi9PWcJB@E)k`vLc!gw-Uh)V}X9f)Lo3
z)C2){w}tg0@>PFxO|!&ztEARMp2JO4O39NyTWvQA-|gDt6po$RjN5hGS$vyqnaR-1
zScpw)K=gC;tu_ntR>_Ls&^a@pt0(ftX_G3ooP8JT2TZRQizTL_2IfN10(a1ehbv;s
z9-v*$R!z=%IUCIC^0u{B=k;EcBA3o?(+F_)XFa^1k|Y#UBwE#$m0DmH`)<||%TMAf
z63MDoicAAXN)=xzsCamA#kOY~u?hSR9hP|x32JYo!Lq~KoO>N?(>v7HOt99eY3R5v_96Bfq<@3nA;Jj}E8NP}KoQP<9M~r6#iXEy
znyC0KqM~6?byg?jsM0({B)m*TO;n+=aA%qrD0%#alkCcg?BuJxM`8Q#psF#!G4|U4
z3VMZsF9cB5v*Ytuut{w(ggV{bYNC`7}SZYTMDN?Qs^%yU*(NDK^67E~y80p0&E8E@^W&Au!@8^T1T3
zCG5PH0$F)h?l(9_>EMXd@u88|b%kAv@euMUjl!W-i$SFL
zva?N{U3SR>2Pcx_O>cutqZ6~odv7pTBgo>s*8-o<>&H$KjLc5i^&5zL_U>1dYm}XW
zlC$wL<&PRHRl?lFwTF6a7>WaO0-v9PhtIZO-v|bikL)-Vw|p`ns#sZ=lpgd}Kf@uT
zIe;cD#Fos@zmwBhY7_cW)lZa{7`om5jF!x^t?j>Sbj?*-DV+nm0?|c8a@B2E>>v#UH>)fv6x)W_}%mn$Q_y7QapoO`y
zJpce?4}kz4PWC6@fisHz;qo=GHUI$1Q~B4?JGN$n?ad4UC9n5Sv&Y7^Rwxq%1%=7U
zNo8ea9UUE8TU!>3wYs{xxw$zvH)n5euc)Zl-rhbvJ?-b`r=p^=wzf7fFo3~ey1Kgh
z`uY|Z7jtuSt*oq|Q0S*mpDZmcZ``<{sj0cVyquSpr?0PHSy?$eJWM1KJ32ZL2t;FJ
zBa_L@&(AkBH1zTD0fWI;u3U+Zj%F|zr%s)knVHGT%JTH|OiWBvS69!@&VKv$t+%
zeSLjlVPR)yCkzHlO-*fXZZ7w6(Q4Iy%P2#wH~txx2e(WMrhJ
zr9FK3kiRpJ!457j+1w=<0NC|x>j7pc?b^4M0DqJ-)!Et#jwJyOiwchay{u_hbg{rt@%D0pOq4GJ3(fYrvy>mm4b
zXmB<`AfAerr3Dg*!ElF770tpKX1Ba=B
zNfcj*swkBlD1k83Cg2n|yRKY4++me;l`H6=c;DWcrpNk;zLEFMl
ziQvDz5*bYi#-qZ?cwKpWd@w15g2it$Y#IF`F{0qnR6Gs=Re`F4RA3;erVixarnc7j
z%fN(2wP3?>cpMtzqYcu)X{&@VFmRtK^bU4sA5pll1jqVn6~XUKj6
z|CW~jeX4NS-)SO;Pzc*4fWt!Y1biT#NZm@3>aQf>uyCqBm4N>%MFs@wKU2i6JvaeP
z^wpK8fpB;qbO?bek8mM{ph7TsSqm(Q82lex`o*FH`P%~j4~NKqE9
zy;1w+4O@a6gi!rR6odnj;7<(D*#i7a#V+*iaQ~k|%I?J9G5yC5XlU}^ik=OFZx<_x
zf+CR!2qQEx6s-gKclCeV%Fo@gdzszUkl($}7Ji=|cq04Aps>#d`xn#+0Dv#q!q@;s
z1OAz{>L#Ww*>*0pJNBy42aZWkW1z?-BSpmnk7OUS-{-ksf@eJ0Cp|-}opJ1AXvv+d
z7spQ@xSM4$^kmMrfoTK1vZpIAfkzm4NT2IGKtz_qnEu}sf_Qdkch1QS4hs~LrXYbC4n#Y&_aJGA^9OdYoeniSS6w7Rr
zJAd!9>{>?u^kaX-<~MM<;Nd2vknfK~@J5iNB3ciB`&KE
z<`{$NqYA*}7@F{xpz@geLMDI=){J#x!pDGs*i~JD9q%U>Z}<&8fsc6L&wD)ikXv~z
zuIP}!Vu6n9^0U%Z9EAgJO&Oi({gGeMu;`_8mV6k}DUMw|jKTwKzR+A~PjDXD#%P$IXS>0#4k`iYx+`Y*<`9@+OYsUgpK&4hLHfuap5z
zU$Q=L&ic!n)>ZLN9GePtq962u`}6va_SM*Nn3h70>V6030x*M{$zPjv6Z(cPdKNr9
z!o+;#4C{Wa`|8k;Jl>-?MTrtnWSN%`HKAC}_eHtvK#}VVAG1yiv+7;co-IC#m&>yn
z_iS+bx+;{+aNM~pj&5vo&$4S_x|N2)P{fu)HP
zSNrCrRIUx0I(J_C(0zbcssAiX*AgI}6gZ6H^IisNUhesxLwWs$Gg;bXHQXKodfyVz
zBcBvV>+88**6kzLPLSv=&S@yeE#~!(wOHTS>2o`Sgf0Y4_E-1Xyp$xFDDMLafLxJZ
z9O;{0Rbv(>7A!aoIr{64(K&%h^#JDtT<*^mcXk*f>c;;ei5&Gl^fpXSkqUS
z2ITgX_2NLv(ZrA;@!fsEqVZAxCIR7!Fl0|1SQiVq{3glOcaK8wYco5gs?r#S$)o(bd^&y=&v&!zU<%ngD!XM-f%oXl3{sD_Q9>2
zpbjW(clxbya*m3`9*YXrx+gtdU$YC)RhKv%u4lf-+Zl8-{@K~5mem1zQO$zyyqhh}
z9J;Qo508Nje(8*phk)d@&^E^Ohh{6)@|$-nkp-cp
zw56S*9Ye)*sj$V9J}l45w=V{;M*xoHaBlhH(r!y2p?}`J5VZP7ijpjY8Lt6DB$zb3
z8j|V=@^8<-!+80bagg=A{q&VxPazdA`jA(A0zZr)_f8hg7icp{^RF;7pOalH)T7Q|
zbfKYPFM`qp#%q{d|IY`X3i-oyx(H!2-%8lOSveuz=q94YX1^H9=-3=Mr66k9X-
zt^jD=85tLF?hRdi^7_svf-ktb~+0Gp`0t!Fv%UBJ(9tlQj=VdcEy}3OUx1lc%zP@i6{7JKDYh^v=|JMEmj!
zjfBn6lXAX4ZtfNe+GFng+MllWM`qK8m2hd41P?u60yod98SBgD6nqq_k$!OE>fP^M
zakqF~#ikFeyP3JoSzr4qUS~mXQ~y3OntO3M8F2A#i6&ynT<#4$fCWqjyboqA`+9A@
z^SG+003qo;S$e29RwdfT)CcX{h^CpcDsxHIT5FPiP5qTQE;NxP79d>KFun5vmlRoB
zNIgk~Fr1pf{q%Nmjour;WGwT9e%R5$eG)xlA+v$$+zLXrF|uy}i`@GoJ%x{tYup$1
z8(H@hTs*x)VopQ@UZz^jEtR9AA5Q<)w0cB;oP%{|bkK|c^_+`73;2>Vo!gfE>|Z%$
zmpl~x{dR;qSt}w-rs&RNv+tvA7Zh#O4LPS^(bw&!vu9ScV`pO?LNDGaRQ|8u?EZC6
dyezgNqpsE{B+a4Tx%I
Date: Mon, 25 Dec 2023 16:16:49 +1100
Subject: [PATCH 131/141] Add http server path constants
- Adds "/youtube/" to path
- for future ISA requirements to identify license provider
---
.../kodion/constants/const_paths.py | 7 ++
.../kodion/network/http_server.py | 75 ++++++++++---------
.../youtube/helper/video_info.py | 16 ++--
3 files changed, 56 insertions(+), 42 deletions(-)
diff --git a/resources/lib/youtube_plugin/kodion/constants/const_paths.py b/resources/lib/youtube_plugin/kodion/constants/const_paths.py
index b0ba36f27..cd14cf4bb 100644
--- a/resources/lib/youtube_plugin/kodion/constants/const_paths.py
+++ b/resources/lib/youtube_plugin/kodion/constants/const_paths.py
@@ -15,3 +15,10 @@
FAVORITES = 'kodion/favorites'
WATCH_LATER = 'kodion/watch_later'
HISTORY = 'kodion/playback_history'
+
+API = '/youtube/api'
+API_SUBMIT = '/youtube/api/submit'
+DRM = '/youtube/widevine'
+IP = '/youtube/client_ip'
+MPD = '/youtube/manifest/dash/'
+PING = '/youtube/ping'
diff --git a/resources/lib/youtube_plugin/kodion/network/http_server.py b/resources/lib/youtube_plugin/kodion/network/http_server.py
index 400b71177..0c5c9880e 100644
--- a/resources/lib/youtube_plugin/kodion/network/http_server.py
+++ b/resources/lib/youtube_plugin/kodion/network/http_server.py
@@ -26,7 +26,7 @@
xbmcgui,
xbmcvfs,
)
-from ..constants import ADDON_ID, TEMP_PATH
+from ..constants import ADDON_ID, TEMP_PATH, paths
from ..logger import log_debug
from ..settings import Settings
@@ -41,7 +41,7 @@
_server_requests = BaseRequestsClass()
-class YouTubeProxyRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler, object):
+class RequestHandler(BaseHTTPServer.BaseHTTPRequestHandler, object):
BASE_PATH = xbmcvfs.translatePath(TEMP_PATH)
chunk_size = 1024 * 64
local_ranges = (
@@ -53,11 +53,9 @@ class YouTubeProxyRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler, object):
'::1',
)
- def __init__(self, request, client_address, server):
+ def __init__(self, *args, **kwargs):
self.whitelist_ips = _settings.httpd_whitelist()
- super(YouTubeProxyRequestHandler, self).__init__(request,
- client_address,
- server)
+ super(RequestHandler, self).__init__(*args, **kwargs)
def connection_allowed(self):
client_ip = self.client_address[0]
@@ -71,7 +69,7 @@ def connection_allowed(self):
if not conn_allowed:
log_debug('HTTPServer: Connection from |{client_ip| not allowed'.
format(client_ip=client_ip))
- elif self.path != '/ping':
+ elif self.path != paths.PING:
log_debug(' '.join(log_lines))
return conn_allowed
@@ -80,15 +78,15 @@ def do_GET(self):
api_config_enabled = _settings.api_config_page()
# Strip trailing slash if present
- stripped_path = self.path.rstrip('/').lower()
- if stripped_path != '/ping':
+ stripped_path = self.path.rstrip('/')
+ if stripped_path != paths.PING:
log_debug('HTTPServer: GET Request uri path |{stripped_path}|'
- .format(stripped_path=stripped_path))
+ .format(stripped_path=self.path))
if not self.connection_allowed():
self.send_error(403)
- elif stripped_path == '/client_ip':
+ elif stripped_path == paths.IP:
client_json = json.dumps({"ip": "{ip}"
.format(ip=self.client_address[0])})
self.send_response(200)
@@ -97,9 +95,9 @@ def do_GET(self):
self.end_headers()
self.wfile.write(client_json.encode('utf-8'))
- elif _settings.use_mpd_videos() and stripped_path.endswith('.mpd'):
+ elif self.path.startswith(paths.MPD):
file_path = os.path.join(self.BASE_PATH,
- self.path.strip('/').strip('\\'))
+ self.path[len(paths.MPD):])
file_chunk = True
log_debug('HTTPServer: Request file path |{file_path}|'
.format(file_path=file_path))
@@ -119,7 +117,7 @@ def do_GET(self):
.format(proxy_path=self.path, file_path=file_path))
self.send_error(404, response)
- elif api_config_enabled and stripped_path == '/api':
+ elif api_config_enabled and stripped_path == paths.API:
html = self.api_config_page()
html = html.encode('utf-8')
@@ -131,7 +129,7 @@ def do_GET(self):
for chunk in self.get_chunks(html):
self.wfile.write(chunk)
- elif api_config_enabled and stripped_path.startswith('/api_submit'):
+ elif api_config_enabled and self.path.startswith(paths.API_SUBMIT):
xbmc.executebuiltin('Dialog.Close(addonsettings, true)')
query = urlsplit(self.path).query
@@ -186,7 +184,7 @@ def do_GET(self):
for chunk in self.get_chunks(html):
self.wfile.write(chunk)
- elif stripped_path == '/ping':
+ elif stripped_path == paths.PING:
self.send_error(204)
else:
@@ -199,9 +197,10 @@ def do_HEAD(self):
if not self.connection_allowed():
self.send_error(403)
- elif _settings.use_mpd_videos() and self.path.endswith('.mpd'):
+
+ elif self.path.startswith(paths.MPD):
file_path = os.path.join(self.BASE_PATH,
- self.path.strip('/').strip('\\'))
+ self.path[len(paths.MPD):])
if not os.path.isfile(file_path):
response = ('File Not Found: |{proxy_path}| -> |{file_path}|'
.format(proxy_path=self.path, file_path=file_path))
@@ -212,6 +211,7 @@ def do_HEAD(self):
self.send_header('Content-Length',
str(os.path.getsize(file_path)))
self.end_headers()
+
else:
self.send_error(501)
@@ -222,7 +222,8 @@ def do_POST(self):
if not self.connection_allowed():
self.send_error(403)
- elif self.path.startswith('/widevine'):
+
+ elif self.path.startswith(paths.DRM):
home = xbmcgui.Window(10000)
lic_url = home.getProperty('-'.join((ADDON_ID, 'license_url')))
@@ -296,6 +297,7 @@ def do_POST(self):
for chunk in self.get_chunks(response_body):
self.wfile.write(chunk)
+
else:
self.send_error(501)
@@ -351,31 +353,31 @@ class Pages(object):
- {title}
-
+ {{title}}
+
-
{header}
-