338 lines
12 KiB
Python
338 lines
12 KiB
Python
# -*- 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 six.moves import urllib
|
|
|
|
import datetime
|
|
import json
|
|
import os
|
|
import sys
|
|
import weakref
|
|
|
|
import xbmc
|
|
import xbmcaddon
|
|
import xbmcplugin
|
|
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 ... import utils
|
|
|
|
try:
|
|
xbmc.translatePath = xbmcvfs.translatePath
|
|
except AttributeError:
|
|
pass
|
|
|
|
|
|
class XbmcContext(AbstractContext):
|
|
def __init__(self, path='/', params=None, plugin_name='', plugin_id='', override=True):
|
|
AbstractContext.__init__(self, path, params, plugin_name, plugin_id)
|
|
|
|
if plugin_id:
|
|
self._addon = xbmcaddon.Addon(id=plugin_id)
|
|
else:
|
|
self._addon = xbmcaddon.Addon(id='plugin.video.youtube')
|
|
|
|
"""
|
|
I don't know what xbmc/kodi is doing with a simple uri, but we have to extract the information from the
|
|
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.
|
|
"""
|
|
# first the path of the uri
|
|
if override:
|
|
self._uri = sys.argv[0]
|
|
comps = urllib.parse.urlparse(self._uri)
|
|
self._path = urllib.parse.unquote(comps.path)
|
|
|
|
# after that try to get the params
|
|
if len(sys.argv) > 2:
|
|
params = sys.argv[2][1:]
|
|
if len(params) > 0:
|
|
self._uri = '?'.join([self._uri, params])
|
|
|
|
self._params = {}
|
|
params = dict(urllib.parse.parse_qsl(params))
|
|
for _param in params:
|
|
item = params[_param]
|
|
self._params[_param] = item
|
|
|
|
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_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._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'))
|
|
|
|
if not xbmcvfs.exists(self._data_path):
|
|
xbmcvfs.mkdir(self._data_path)
|
|
|
|
def get_region(self):
|
|
pass # implement from abstract
|
|
|
|
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 format_date_short(self, date_obj):
|
|
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_time(self, time_obj):
|
|
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 get_language(self):
|
|
"""
|
|
The xbmc.getLanguage() method is fucked up!!! We always return 'en-US' for now
|
|
"""
|
|
|
|
'''
|
|
if self.get_system_version().get_release_name() == 'Frodo':
|
|
return 'en-US'
|
|
|
|
try:
|
|
language = xbmc.getLanguage(0, region=True)
|
|
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__())
|
|
return 'en-US'
|
|
'''
|
|
|
|
return 'en-US'
|
|
|
|
def get_video_playlist(self):
|
|
if not self._video_playlist:
|
|
self._video_playlist = XbmcPlaylist('video', weakref.proxy(self))
|
|
return self._video_playlist
|
|
|
|
def get_audio_playlist(self):
|
|
if not self._audio_playlist:
|
|
self._audio_playlist = XbmcPlaylist('audio', weakref.proxy(self))
|
|
return self._audio_playlist
|
|
|
|
def get_video_player(self):
|
|
if not self._video_player:
|
|
self._video_player = XbmcPlayer('video', weakref.proxy(self))
|
|
return self._video_player
|
|
|
|
def get_audio_player(self):
|
|
if not self._audio_player:
|
|
self._audio_player = XbmcPlayer('audio', weakref.proxy(self))
|
|
return self._audio_player
|
|
|
|
def get_ui(self):
|
|
if not self._ui:
|
|
self._ui = XbmcContextUI(self._addon, weakref.proxy(self))
|
|
return self._ui
|
|
|
|
def get_handle(self):
|
|
return self._plugin_handle
|
|
|
|
def get_data_path(self):
|
|
return self._data_path
|
|
|
|
def get_debug_path(self):
|
|
if not self._debug_path:
|
|
self._debug_path = os.path.join(self.get_data_path(), 'debug')
|
|
if not xbmcvfs.exists(self._debug_path):
|
|
xbmcvfs.mkdir(self._debug_path)
|
|
return self._debug_path
|
|
|
|
def get_native_path(self):
|
|
return self._native_path
|
|
|
|
def get_settings(self):
|
|
return self._settings
|
|
|
|
def localize(self, text_id, default_text=u''):
|
|
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:
|
|
try:
|
|
result = self._addon.getLocalizedString(int(text_id))
|
|
if result is not None and result:
|
|
result = utils.to_unicode(result)
|
|
except ValueError:
|
|
pass
|
|
|
|
if not result:
|
|
result = default_text
|
|
|
|
return result
|
|
|
|
def set_content_type(self, content_type):
|
|
self.log_debug('Setting content-type: "%s" for "%s"' % (content_type, self.get_path()))
|
|
xbmcplugin.setContent(self._plugin_handle, content_type)
|
|
|
|
def add_sort_method(self, *sort_methods):
|
|
for sort_method in sort_methods:
|
|
xbmcplugin.addSortMethod(self._plugin_handle, sort_method)
|
|
|
|
def clone(self, new_path=None, new_params=None):
|
|
if not new_path:
|
|
new_path = self.get_path()
|
|
|
|
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._function_cache = self._function_cache
|
|
new_context._search_history = self._search_history
|
|
new_context._favorite_list = self._favorite_list
|
|
new_context._watch_later_list = self._watch_later_list
|
|
new_context._access_manager = self._access_manager
|
|
new_context._ui = self._ui
|
|
new_context._video_playlist = self._video_playlist
|
|
new_context._video_player = self._video_player
|
|
|
|
return new_context
|
|
|
|
def execute(self, command):
|
|
xbmc.executebuiltin(command)
|
|
|
|
def sleep(self, milli_seconds):
|
|
xbmc.sleep(milli_seconds)
|
|
|
|
def addon_enabled(self, addon_id):
|
|
rpc_request = json.dumps({"jsonrpc": "2.0",
|
|
"method": "Addons.GetAddonDetails",
|
|
"id": 1,
|
|
"params": {"addonid": "%s" % addon_id,
|
|
"properties": ["enabled"]}
|
|
})
|
|
response = json.loads(xbmc.executeJSONRPC(rpc_request))
|
|
try:
|
|
return response['result']['addon']['enabled'] is True
|
|
except KeyError:
|
|
message = response['error']['message']
|
|
code = response['error']['code']
|
|
error = 'Requested |%s| received error |%s| and code: |%s|' % (rpc_request, message, code)
|
|
self.log_debug(error)
|
|
return False
|
|
|
|
def set_addon_enabled(self, addon_id, enabled=True):
|
|
rpc_request = json.dumps({"jsonrpc": "2.0",
|
|
"method": "Addons.SetAddonEnabled",
|
|
"id": 1,
|
|
"params": {"addonid": "%s" % addon_id,
|
|
"enabled": enabled}
|
|
})
|
|
response = json.loads(xbmc.executeJSONRPC(rpc_request))
|
|
try:
|
|
return response['result'] == 'OK'
|
|
except KeyError:
|
|
message = response['error']['message']
|
|
code = response['error']['code']
|
|
error = 'Requested |%s| received error |%s| and code: |%s|' % (rpc_request, message, code)
|
|
self.log_debug(error)
|
|
return False
|
|
|
|
def send_notification(self, method, data):
|
|
data = json.dumps(data)
|
|
self.log_debug('send_notification: |%s| -> |%s|' % (method, data))
|
|
data = '\\"[\\"%s\\"]\\"' % urllib.parse.quote(data)
|
|
self.execute('NotifyAll(plugin.video.youtube,%s,%s)' % (method, data))
|
|
|
|
def use_inputstream_adaptive(self):
|
|
addon_enabled = self.addon_enabled('inputstream.adaptive')
|
|
if self._settings.use_dash() and not addon_enabled:
|
|
if self.get_ui().on_yes_no_input(self.get_name(), self.localize(30579)):
|
|
use_dash = self.set_addon_enabled('inputstream.adaptive')
|
|
else:
|
|
use_dash = False
|
|
elif self._settings.use_dash() and addon_enabled:
|
|
use_dash = True
|
|
else:
|
|
use_dash = False
|
|
return use_dash
|
|
|
|
def inputstream_adaptive_capabilities(self, capability=None):
|
|
# return a list inputstream.adaptive capabilities, if capability set return version required
|
|
|
|
capabilities = []
|
|
|
|
use_dash = self.use_inputstream_adaptive()
|
|
try:
|
|
inputstream_version = xbmcaddon.Addon('inputstream.adaptive').getAddonInfo('version')
|
|
except RuntimeError:
|
|
inputstream_version = ''
|
|
|
|
if not use_dash or not inputstream_version:
|
|
return None if capability is not None else capabilities
|
|
|
|
capability_map = {
|
|
'live': '2.0.12',
|
|
'drm': '2.2.12',
|
|
'vp9': '2.3.14',
|
|
'vp9.2': '2.3.14',
|
|
'vorbis': None,
|
|
'opus': None,
|
|
'av1': None,
|
|
}
|
|
|
|
if capability is None:
|
|
ia_loose_version = utils.loose_version(inputstream_version)
|
|
|
|
for key in list(capability_map.keys()):
|
|
if capability_map[key] and (ia_loose_version >= utils.loose_version(capability_map[key])):
|
|
capabilities.append(key)
|
|
|
|
return capabilities
|
|
else:
|
|
return capability_map[capability] if capability_map.get(capability) else None
|
|
|
|
def inputstream_adaptive_auto_stream_selection(self):
|
|
try:
|
|
return xbmcaddon.Addon('inputstream.adaptive').getSetting('STREAMSELECTION') == '0'
|
|
except RuntimeError:
|
|
return False
|
|
|
|
def abort_requested(self):
|
|
return str(self.get_ui().get_home_window_property('abort_requested')).lower() == 'true'
|