September 2018 Update

Added - Koding AIO to repo as addons depend on it
Updated - DefCon now has 2018 talks in
This commit is contained in:
Oli Passey
2018-09-17 19:54:29 +01:00
parent 9982a3f13f
commit f0bbdc56fb
53 changed files with 13094 additions and 282 deletions

Binary file not shown.

View File

@@ -0,0 +1,118 @@
# -*- coding: utf-8 -*-
# script.module.python.koding.aio
# Python Koding AIO (c) by TOTALREVOLUTION LTD (support@trmc.freshdesk.com)
# Python Koding AIO is licensed under a
# Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License.
# You should have received a copy of the license along with this
# work. If not, see http://creativecommons.org/licenses/by-nc-nd/4.0.
# Please make sure you've read and understood the license, this code can NOT be used commercially
# and it can NOT be modified and redistributed. If you're found to be in breach of this license
# then any affected add-ons will be blacklisted and will not be able to work on the same system
# as any other add-ons which use this code. Thank you for your cooperation.
import os
import re
import shutil
import sys
import time
import urllib
import urllib2
import xbmc
import xbmcaddon
import xbmcgui
import xbmcvfs
import inspect
try:
import simplejson as json
except:
import json
from addons import *
from android import *
from database import *
from directory import *
from filetools import *
from guitools import *
from router import *
from systemtools import *
from tutorials import *
from video import *
from vartools import *
from web import *
def converthex(url):
""" internal command ~"""
import binascii
return binascii.unhexlify(url)
try:
ADDON_ID = xbmcaddon.Addon().getAddonInfo('id')
except:
ADDON_ID = Caller()
AddonVersion = xbmcaddon.Addon(id=ADDON_ID).getAddonInfo('version')
ORIG_ID = ADDON_ID
TestID = ADDON_ID
if not ADDON_ID.endswith(converthex('2e74657374')):
TestID = ADDON_ID+converthex('2e74657374')
MODULE_ID = 'script.module.python.koding.aio'
ADDON = xbmcaddon.Addon(id=ADDON_ID)
THIS_MODULE = xbmcaddon.Addon(id=MODULE_ID)
USERDATA = 'special://profile'
ADDON_DATA = os.path.join(USERDATA,'addon_data')
ADDONS = 'special://home/addons'
PACKAGES = os.path.join(ADDONS,'packages')
UPDATE_ICON = os.path.join(ADDONS,MODULE_ID,'resources','update.png')
DEBUG = Addon_Setting(addon_id=ORIG_ID,setting=converthex('6465627567'))
KODI_VER = int(float(xbmc.getInfoLabel("System.BuildVersion")[:2]))
dialog = xbmcgui.Dialog()
dp = xbmcgui.DialogProgress()
if not xbmcvfs.exists(os.path.join(ADDON_DATA,ORIG_ID,'cookies')):
xbmcvfs.mkdirs(os.path.join(ADDON_DATA,ORIG_ID,'cookies'))
#----------------------------------------------------------------
# TUTORIAL #
def dolog(string, my_debug=False, line_info=False):
"""
Print to the Kodi log but only if debugging is enabled in settings.xml
CODE: koding.dolog(string, [my_debug])
AVAILABLE PARAMS:
(*) string - This is your text you want printed to log.
my_debug - This is optional, if you set this to True you will print
to the log regardless of what the debug setting is set at in add-on settings.
line_info - By default this is set to True and will show the line number where
the dolog command was called from along with the filepath it was called from.
EXAMPLE CODE:
koding.dolog(string='Quick test to see if this gets printed to the log', my_debug=True, line_info=True)
dialog.ok('[COLOR gold]CHECK LOGFILE 1[/COLOR]','If you check your log file you should be able to see a new test line we printed \
and immediately below that should be details of where it was called from.')
koding.dolog(string='This one should print without the line and file info', my_debug=True, line_info=False)
dialog.ok('[COLOR gold]CHECK LOGFILE 2[/COLOR]','If you check your log file again you should now be able to see a new line printed \
but without the file/line details.')
~"""
import xbmc
if DEBUG == 'true' or my_debug:
try:
xbmc.log('### %s (%s) : %s'%(ADDON_ID,AddonVersion,string), level=xbmc.LOGNOTICE)
except:
xbmc.log(Last_Error(),level=xbmc.LOGNOTICE)
if line_info:
try:
from inspect import getframeinfo, stack
caller = getframeinfo(stack()[1][0])
xbmc.log('^ Line No. %s | File: %s'%(caller.lineno,caller.filename),level=xbmc.LOGNOTICE)
except:
xbmc.log(Last_Error(),level=xbmc.LOGNOTICE)

View File

@@ -0,0 +1,977 @@
# -*- coding: utf-8 -*-
# script.module.python.koding.aio
# Python Koding AIO (c) by TOTALREVOLUTION LTD (support@trmc.freshdesk.com)
# Python Koding AIO is licensed under a
# Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License.
# You should have received a copy of the license along with this
# work. If not, see http://creativecommons.org/licenses/by-nc-nd/4.0.
# Please make sure you've read and understood the license, this code can NOT be used commercially
# and it can NOT be modified and redistributed. If you're found to be in breach of this license
# then any affected add-ons will be blacklisted and will not be able to work on the same system
# as any other add-ons which use this code. Thank you for your cooperation.
import datetime
import os
import sys
import shutil
import xbmc
import xbmcaddon
import xbmcgui
import xbmcvfs
import filetools
ADDONS = 'special://home/addons'
XBMC_PATH = xbmc.translatePath('special://xbmc')
kodi_ver = int(float(xbmc.getInfoLabel("System.BuildVersion")[:2]))
dialog = xbmcgui.Dialog()
#----------------------------------------------------------------
# TUTORIAL #
def Addon_Genre(genre='adult',custom_url=''):
"""
Return a dictionary of add-ons which match a specific genre.
CODE: Addon_Genre([genre, custom_url])
AVAILABLE PARAMS:
genre - By default this is set to 'adult' which will return a dictionary of all known
adult add-ons. We recommend using the genre labels listed below as they are already in use
by some add-on developers, however you can of course create your very own genre keys in
your custom genre file if you wish.
custom_url - If you have your own custom url which returns a dictionary
of genres and add-ons you can enter it here. The page must adhere to the format shown below.
Recommended Genre Keys:
adult, anime, audiobooks, comedy, comics, documentary, food, gaming, health, howto, kids,
livetv, movies, music, news, podcasts, radio, religion, space, sports, subscription,
tech, trailers, tvshows, world
Correct Genre Dictionary Structure:
The dictionary must be a dictionary of genres with each genre dictionary containing keys for
each add-on ID and the value being the name you want displayed. See below for an example:
{ "movies":{"plugin.video.mymovie":"My movie add-on","plugin.video.trailers":"My Trailer add-on"}, "sports":{"plugin.video.sports":"Sport Clips"} }
EXAMPLE CODE:
dialog.ok('ADD-ON GENRES','We will now list all known comedy based add-ons. If you have add-ons installed which you feel should be categorised as supplying comedy but they aren\'t then you can help tag them up correctly via the Add-on Portal at NaN.')
comedy_addons = koding.Addon_Genre(genre='comedy')
if comedy_addons:
my_return = 'LIST OF AVAILABLE COMEDY BASED ADD-ONS:\n\n'
# Convert the dictionary into a list:
comedy_addons = comedy_addons.items()
for item in comedy_addons:
my_return += '[COLOR=gold]Name:[/COLOR] %s | [COLOR=dodgerblue]ID:[/COLOR] %s\n' % (item[0],item[1])
koding.Text_Box('[COLOR gold]COMEDY ADD-ONS[/COLOR]',my_return)
~"""
import binascii
from __init__ import converthex
from filetools import Text_File
from systemtools import Timestamp
from vartools import Merge_Dicts
from web import Open_URL
download_new = True
local_path = binascii.hexlify('genre_list')
cookie_path = "special://profile/addon_data/script.module.python.koding.aio/cookies/"
custom_genres= "special://profile/addon_data/script.module.python.koding.aio/genres.txt"
final_path = os.path.join(cookie_path,local_path)
if not xbmcvfs.exists(cookie_path):
xbmcvfs.mkdirs(cookie_path)
if xbmcvfs.exists(final_path):
modified = xbmcvfs.Stat(final_path).st_mtime()
old = int(modified)
now = int(Timestamp('epoch'))
# Add a 24hr wait so we don't kill server
if now < (modified+86400):
download_new = False
# Create new file
if download_new and custom_url != '':
addon_list = Open_URL(custom_url)
Text_File(final_path, "w", addon_list)
# Grab details of the relevant genre
if xbmcvfs.exists(final_path):
try:
addon_list = eval( Text_File(final_path, 'r') )
addon_list = addon_list[genre]
except:
xbmcvfs.delete(final_path)
addon_list = {}
if xbmcvfs.exists(custom_genres):
try:
custom_list = eval( Text_File(custom_genres, 'r') )
custom_list = custom_list[genre]
addon_list = Merge_Dicts(addon_list,custom_list)
except:
pass
return addon_list
#----------------------------------------------------------------
# TUTORIAL #
def Addon_Info(id='',addon_id=''):
"""
Retrieve details about an add-on, lots of built-in values are available
such as path, version, name etc.
CODE: Addon_Setting(id, [addon_id])
AVAILABLE PARAMS:
(*) id - This is the name of the id you want to retrieve.
The list of built in id's you can use (current as of 15th April 2017)
are: author, changelog, description, disclaimer, fanart, icon, id, name,
path, profile, stars, summary, type, version
addon_id - By default this will use your current add-on id but you
can access any add-on you want by entering an id in here.
EXAMPLE CODE:
dialog.ok('ADD-ON INFO','We will now try and pull name and version details for our current running add-on.')
version = koding.Addon_Info(id='version')
name = koding.Addon_Info(id='name')
dialog.ok('NAME AND VERSION','[COLOR=dodgerblue]Add-on Name:[/COLOR] %s' % name,'[COLOR=dodgerblue]Version:[/COLOR] %s' % version)
~"""
import xbmcaddon
if addon_id == '':
addon_id = Caller()
ADDON = xbmcaddon.Addon(id=addon_id)
if id == '':
dialog.ok('ENTER A VALID ID','You\'ve called the Addon_Info function but forgot to add an ID. Please correct your code and enter a valid id to pull info on (e.g. "version")')
else:
return ADDON.getAddonInfo(id=id)
#----------------------------------------------------------------
# TUTORIAL #
def Addon_List(enabled=True, inc_new=False):
"""
Return a list of enabled or disabled add-ons found in the database.
CODE: Addon_List([enabled, inc_new])
AVAILABLE PARAMS:
enabled - By default this is set to True which means you'll
get a list of all the enabled add-ons found in addons*.db but
if you want a list of all the disabled ones just set this to
False.
inc_new - This will also add any new add-on folders found on
your system that aren't yet in the database (ie ones that have
been recently been manually extracted but not scanned in). By
default this is set to False.
EXAMPLE CODE:
enabled_list = Addon_List(enabled=True)
disabled_list = Addon_List(enabled=False)
my_return = ''
for item in enabled_list:
my_return += '[COLOR=lime]ENABLED:[/COLOR] %s\n' % item
for item in disabled_list:
my_return += '[COLOR=red]DISABLED:[/COLOR] %s\n' % item
koding.Text_Box('ADDON STATUS',my_return)
~"""
from database import DB_Query
from guitools import Text_Box
from filetools import DB_Path_Check, Get_Contents
enabled_list = []
disabled_list = []
addons_db = DB_Path_Check('addons')
on_system = DB_Query(addons_db,'SELECT addonID, enabled from installed')
# Create a list of enabled and disabled add-ons already on system
for item in on_system:
if item["enabled"]:
enabled_list.append(item["addonID"])
else:
disabled_list.append(item["addonID"])
if inc_new:
my_addons = Get_Contents(path=ADDONS, exclude_list=['packages','temp'])
for item in my_addons:
addon_id = Get_Addon_ID(item)
if not addon_id in enabled_list and not addon_id in disabled_list:
disabled_list.append(addon_id)
if enabled:
return enabled_list
else:
return disabled_list
#----------------------------------------------------------------
# TUTORIAL #
def Addon_Service(addons='all', mode='list', skip_service=[]):
"""
Send through an add-on id, list of id's or leave as the default which is "all". This
will loop through the list of add-ons and return the ones which are run as services.
This enable/disable feature will comment out the service lines, and does not stop a running
service or start a service. This is designed more for if you've manually extracted a new
add-on into your system and it isn't yet enabled. Occasionally if the add-ons have dependencies
which are run as services then trying to enable them can cause Kodi to freeze.
CODE: Addon_Service([addon,disable])
AVAILABLE PARAMS:
addons - By default this is set to "all" but if there's a sepcific set of add-ons you
want to disable the service for just send through the id's in the form of a list.
mode - By default this is set to 'list' meaning you'll get a return of add-on folders
which contain an instance of service in the add-on.xml. You can set this to "disable" to
comment out the instances of service and similarly when you need to re-enable you can use
"enable" and that will uncomment out the service item. Please note that by uncommenting
the service will not automatically start - you'll need to reload the profile for that.
skip_service - When running the enable or disable mode you can choose to add a list of
add-ons you'd like to skip the process for. Of course you may be thinking why would I send
through a list of addons I want the service enabled/disabled for but then I also add them
to the skip_service list to say DON'T enable/disable - it makes no sense?! Well you'd be
correct that doesn't make any sense as presumably you've already filtered out the add-ons
you don't want affected, this command is designed more for those who don't send through a
list of add-ons and instead use the default "all" value for the addons paramater. This
then makes it very easy to just skip a handful of add-on services and enable all others.
EXAMPLE CODE:
dialog.ok('CHECKING FOR SERVICES','We will now check for all add-ons installed which contain services')
service_addons = Addon_Service(mode='list')
my_text = 'List of add-ons running as a service:\n\n'
for item in service_addons:
my_text += item+'\n'
koding.Text_Box('[COLOR gold]SERVICE ADDONS[/COLOR]',my_text)
~"""
from filetools import Get_Contents, Physical_Path, Text_File
from vartools import Data_Type
from guitools import Text_Box
service_addons = []
if addons=='all':
addons = Get_Contents(path=ADDONS, exclude_list=['packages','temp'],full_path=False)
else:
if Data_Type(addons) == 'str':
addons = [addons]
if Data_Type(skip_service) == 'str':
skip_service = [skip_service]
service_line = '<extension point="xbmc.service"'
for item in addons:
addon_path = os.path.join(ADDONS,item,'addon.xml')
content = Text_File(addon_path,'r')
if service_line in content:
if item not in service_addons:
service_addons.append(item)
if mode != 'list':
if not item in skip_service:
for line in content.splitlines():
if service_line in line:
if not (line.strip().startswith('<!--')) and (mode == 'disable'):
replace_line = '<!--%s-->'%line
Text_File(addon_path,'w',content.replace(line,replace_line))
break
elif line.strip().startswith('<!--') and mode == 'enable':
replace_line = line.replace(r'<!--','').replace(r'-->','')
Text_File(addon_path,'w',content.replace(line,replace_line))
break
return service_addons
#----------------------------------------------------------------
# TUTORIAL #
def Addon_Setting(setting='',value='return_default',addon_id=''):
"""
Change or retrieve an add-on setting.
CODE: Addon_Setting(setting, [value, addon_id])
AVAILABLE PARAMS:
(*) setting - This is the name of the setting you want to access, by
default this function will return the value but if you add the
value param shown below it will CHANGE the setting.
value - If set this will change the setting above to whatever value
is in here.
addon_id - By default this will use your current add-on id but you
can access any add-on you want by entering an id in here.
EXAMPLE CODE:
dialog.ok('ADDON SETTING','We will now try and pull the language settings for the YouTube add-on')
if os.path.exists(xbmc.translatePath('special://home/addons/plugin.video.youtube')):
my_setting = koding.Addon_Setting(setting='youtube.language',addon_id='plugin.video.youtube')
dialog.ok('YOUTUBE SETTING','[COLOR=dodgerblue]Setting name:[/COLOR] youtube.language','[COLOR=dodgerblue]Value:[/COLOR] %s' % my_setting)
else:
dialog.ok('YOUTUBE NOT INSTALLED','Sorry we cannot run this example as you don\'t have YouTube installed.')
~"""
import xbmcaddon
if addon_id == '':
addon_id = Caller()
ADDON = xbmcaddon.Addon(id=addon_id)
if value == 'return_default':
mysetting = ADDON.getSetting(setting)
return mysetting
else:
ADDON.setSetting(id=setting, value=value)
#----------------------------------------------------------------
# TUTORIAL #
def Adult_Toggle(adult_list=[], disable=True, update_status=0):
"""
Remove/Enable a list of add-ons, these are put into a containment area until enabled again.
CODE: Adult_Toggle(adult_list, [disable, update_status])
AVAILABLE PARAMS:
(*) adult_list - A list containing all the add-ons you want to be disabled.
disable - By default this is set to true so any add-ons in the list sent
through will be disabled. Set to False if you want to enable the hidden add-ons.
update_status - When running this function it needs to disable the
auto-update of add-ons by Kodi otherwise it risks crashing. This
update_status paramater is the state you want Kodi to revert back to
once the toggle of add-ons has completed. By default this is set to 0
which is auto-update. You can also choose 1 (notify of updates) or 2
(disable auto updates).
~"""
from filetools import End_Path, Move_Tree, Physical_Path
adult_store = Physical_Path("special://profile/addon_data/script.module.python.koding.aio/adult_store")
disable_list = []
if not xbmcvfs.exists(adult_store):
xbmcvfs.mkdirs(adult_store)
my_addons = Installed_Addons()
if disable:
for item in my_addons:
if item != None:
item = item["addonid"]
if item in adult_list:
disable_list.append(item)
Toggle_Addons(addon=disable_list, enable=False, safe_mode=True, refresh=True, update_status=update_status)
for item in disable_list:
try:
addon_path = xbmcaddon.Addon(id=item).getAddonInfo("path")
except:
addon_path = Physical_Path(os.path.join(ADDONS,item))
path_id = End_Path(addon_path)
if os.path.exists(addon_path):
Move_Tree(addon_path,os.path.join(adult_store,path_id))
else:
KODI_VER = int(float(xbmc.getInfoLabel("System.BuildVersion")[:2]))
addon_vault = []
if os.path.exists(adult_store):
for item in os.listdir(adult_store):
store_dir = os.path.join(adult_store,item)
addon_dir = os.path.join(ADDONS, item)
if os.path.exists(store_dir):
Move_Tree(store_dir,addon_dir)
addon_vault.append(item)
if KODI_VER >= 16:
Toggle_Addons(addon=addon_vault, safe_mode=True, refresh=True, update_status=update_status)
else:
Refresh(['addons','repos'])
#----------------------------------------------------------------
# TUTORIAL #
def Caller(my_return='addon'):
"""
Return the add-on id or path of the script which originally called
your function. If it's been called through a number of add-ons/scripts
you can grab a list of paths that have been called.
CODE: Caller(my_return)
AVAILABLE PARAMS:
my_return - By default this is set to 'addon', view the options below:
'addon' : Return the add-on id of the add-on to call this function.
'addons': Return a list of all add-on id's called to get to this function.
'path' : Return the full path to the script which called this function.
'paths' : Return a list of paths which have been called to get to this
final function.
EXAMPLE CODE:
my_addon = koding.Caller(my_return='addon')
my_addons = koding.Caller(my_return='addons')
my_path = koding.Caller(my_return='path')
my_paths = koding.Caller(my_return='paths')
dialog.ok('ADD-ON ID', 'Addon id you called this function from:','[COLOR=dodgerblue]%s[/COLOR]' % my_addon)
dialog.ok('SCRIPT PATH', 'Script which called this function:','[COLOR=dodgerblue]%s[/COLOR]' % my_path)
addon_list = 'Below is a list of add-on id\'s which have been called to get to this final piece of code:\n\n'
for item in my_addons:
addon_list += item+'\n'
koding.Text_Box('ADD-ON LIST', addon_list)
koding.Sleep_If_Window_Active(10147)
path_list = 'Below is a list of scripts which have been called to get to this final piece of code:\n\n'
for item in my_paths:
path_list += item+'\n'
koding.Text_Box('ADD-ON LIST', path_list)
~"""
import inspect
stack = inspect.stack()
last_stack = len(stack)-1
stack_array = []
addon_array = []
for item in stack:
last_stack = item[1].replace('<string>','')
last_stack = last_stack.strip()
stack_array.append(last_stack)
try:
scrap,addon_id = last_stack.split('addons%s'%os.sep)
addon_id = addon_id.split(os.sep)[0]
addon_id = Get_Addon_ID(addon_id)
if addon_id not in addon_array:
addon_array.append(addon_id)
except:
pass
if my_return == 'addons':
return addon_array
if my_return == 'addon':
return addon_array[len(addon_array)-1]
if my_return == 'path':
return stack_array[len(stack_array)-1]
if my_return == 'paths':
return stack_array
#----------------------------------------------------------------
def Check_Deps(addon_path, depfiles = []):
import re
from filetools import Text_File
from __init__ import dolog
exclude_list = ['xbmc.gui', 'script.module.metahandler', 'metadata.common.allmusic.com',\
'kodi.resource','xbmc.core','xbmc.metadata','xbmc.addon','xbmc.json','xbmc.python']
file_location = os.path.join(addon_path,'addon.xml')
if xbmcvfs.exists(file_location):
readxml = Text_File(file_location,'r')
dmatch = re.compile('import addon="(.+?)"').findall(readxml)
for requires in dmatch:
if not requires in exclude_list and not requires in depfiles:
depfiles.append(requires)
return depfiles
#----------------------------------------------------------------
# TUTORIAL #
def Check_Repo(repo,show_busy=True,timeout=10):
"""
This will check the status of repo and return True if the repo is online or False
if it contains paths that are no longer accessible online.
IMPORTANT: If you're running an old version of Kodi which uses the old Python 2.6
(OSX and Android lower than Kodi 17 or a linux install with old Python installed on system)
you will get a return of False on https links regardless of their real status. This is due
to the fact Python 2.6 cannot access secure links. Any still using standard http links
will return the correct results.
CODE: Check_Repo(repo, [show_busy, timeout])
AVAILABLE PARAMS:
(*) repo - This is the name of the folder the repository resides in.
You can either use the full path or just the folder name which in 99.99%
of cases is the add-on id. If only using the folder name DOUBLE check first as
there are a handful which have used a different folder name to the actual add-on id!
show_busy - By default this is set to True and a busy dialog will show during the check
timeout - By default this is set to 10 (seconds) - this is the maximum each request
to the repo url will take before timing out and returning False.
EXAMPLE CODE:
repo_status = Check_Repo('special://xbmc',show_busy=False,timeout=10)
if repo_status:
dialog.ok('REPO STATUS','The repository modules4all is: [COLOR=lime]ONLINE[/COLOR]')
else:
dialog.ok('REPO STATUS','The repository modules4all is: [COLOR=red]OFFLINE[/COLOR]')
~"""
import re
from __init__ import dolog
from filetools import Text_File
from guitools import Show_Busy
from web import Validate_Link
xbmc.log('### CHECKING %s'%repo,2)
status = True
if show_busy:
Show_Busy()
if not ADDONS in repo and not XBMC_PATH in repo:
repo_path = os.path.join(ADDONS,repo)
else:
repo_path = repo
repo_path = Physical_Path(repo_path)
xbmc.log(repo_path,2)
repo_path = os.path.join(repo_path,'addon.xml')
xbmc.log(repo_path,2)
if os.path.exists(repo_path):
content = Text_File(repo_path,'r')
md5_urls = re.findall(r'<checksum>(.+?)</checksum>', content, re.DOTALL)
for item in md5_urls:
link_status = Validate_Link(item,timeout)
dolog(item)
dolog('STATUS: %s'%link_status)
if link_status < 200 or link_status >= 400:
status = False
break
if show_busy:
Show_Busy(False)
return status
else:
if show_busy:
Show_Busy(False)
return False
#----------------------------------------------------------------
# TUTORIAL #
def Default_Setting(setting='',addon_id='',reset=False):
"""
This will return the DEFAULT value for a setting (as set in resources/settings.xml)
and optionally reset the current value back to this default. If you pass through
the setting as blank it will return a dictionary of all default settings.
CODE: Default_Setting(setting, [addon_id, reset])
AVAILABLE PARAMS:
setting - The setting you want to retreive the value for.
Leave blank to return a dictionary of all settings
addon_id - This is optional, if not set it will use the current id.
reset - By default this is set to False but if set to true and it will
reset the current value to the default.
EXAMPLE CODE:
youtube_path = xbmc.translatePath('special://home/addons/plugin.video.youtube')
if os.path.exists(youtube_path):
my_value = koding.Default_Setting(setting='youtube.region', addon_id='plugin.video.youtube', reset=False)
dialog.ok('YOUTUBE SETTING','Below is a default setting for plugin.video.youtube:','Setting: [COLOR=dodgerblue]youtube.region[/COLOR]','Value: [COLOR=dodgerblue]%s[/COLOR]' % my_value)
else:
dialog.ok('YOUTUBE NOT INSTALLED','We cannot run this example as it uses the YouTube add-on which has not been found on your system.')
~"""
import re
from filetools import Text_File
from vartools import Data_Type
if addon_id == '':
addon_id = Caller()
values = {}
addon_path = Addon_Info(id='path',addon_id=addon_id)
settings_path = os.path.join(addon_path,'resources','settings.xml')
content = Text_File(settings_path,'r').splitlines()
for line in content:
if 'id="' in line and 'default="' in line:
idx = re.compile('id="(.*?)"').findall(line)
idx = idx[0] if (len(idx) > 0) else ''
value = re.compile('default="(.*?)"').findall(line)
value = value[0] if (len(value) > 0) else ''
if setting != '' and idx == setting:
values = value
break
elif idx != '' and value != '' and setting == '':
values[idx] = value
if reset:
if Data_Type(values) == 'dict':
for item in values.items():
Addon_Setting(addon_id=addon_id,setting=item[0],value=item[1])
elif setting != '':
Addon_Setting(addon_id=addon_id,setting=setting,value=value)
return values
#----------------------------------------------------------------
# TUTORIAL #
def Dependency_Check(addon_id = 'all', recursive = False):
"""
This will return a list of all dependencies required by an add-on.
This information is grabbed directly from the currently installed addon.xml,
an individual add-on id or a list of add-on id's.
CODE: Dependency_Check([addon_id, recursive])
AVAILABLE PARAMS:
addon_id - This is optional, if not set it will return a list of every
dependency required from all installed add-ons. If you only want to
return results of one particular add-on then send through the id.
recursive - By default this is set to False but if set to true and you
also send through an individual addon_id it will return all dependencies
required for that addon id AND the dependencies of the dependencies.
EXAMPLE CODE:
current_id = xbmcaddon.Addon().getAddonInfo('id')
dependencies = koding.Dependency_Check(addon_id=current_id, recursive=True)
clean_text = ''
for item in dependencies:
clean_text += item+'\n'
koding.Text_Box('Modules required for %s'%current_id,clean_text)
~"""
import xbmcaddon
import re
from filetools import Text_File
from vartools import Data_Type
processed = []
depfiles = []
if addon_id == 'all':
addon_id = xbmcvfs.listdir(ADDONS)
elif Data_Type(addon_id) == 'str':
addon_id = [addon_id]
for name in addon_id:
try:
addon_path = xbmcaddon.Addon(id=name).getAddonInfo('path')
except:
addon_path = os.path.join(ADDONS, name)
if not name in processed:
processed.append(name)
# Get list of master dependencies
depfiles = Check_Deps(addon_path,[name])
# Recursively check all other dependencies
depchecks = depfiles
if recursive:
while len(depchecks):
for depfile in depfiles:
if depfile not in processed:
try:
dep_path = xbmcaddon.Addon(id=depfile).getAddonInfo('path')
except:
dep_path = os.path.join(ADDONS,depfile)
newdepfiles = Check_Deps(dep_path, depfiles)
# Pass through the path of sub-dependency and add items to master list and list to check
for newdep in newdepfiles:
if not (newdep in depchecks) and not (newdep in processed):
depchecks.append(newdep)
if not newdep in depfiles:
depfiles.append(newdep)
processed.append(depfile)
depchecks.remove(depfile)
if name in depchecks:
depchecks.remove(name)
return processed[1:]
#----------------------------------------------------------------
# TUTORIAL #
def Get_Addon_ID(folder):
"""
If you know the folder name of an add-on but want to find out the
addon id (it may not necessarily be the same as folder name) then
you can use this function. Even if the add-on isn't enabled on the
system this will regex out the add-on id.
CODE: Get_Addon_ID(folder)
AVAILABLE PARAMS:
folder - This is folder name of the add-on. Just the name not the path.
EXAMPLE CODE:
dialog.ok('ABOUT','This function allows us to pass through a folder name found in the addons folder and it will return the real id. The vast majority of add-ons use the same folder name as id but there are exceptions. Let\'s check Python Koding...')
my_id = koding.Get_Addon_ID(folder='script.module.python.koding.aio')
dialog.ok('PYTHON KODING ID','The add-on id found for this folder folder is:','[COLOR=dodgerblue]%s[/COLOR]'%my_id)
~"""
from filetools import Text_File
import re
xmlpath = os.path.join(ADDONS, folder, 'addon.xml')
if xbmcvfs.exists(xmlpath):
contents = Text_File(xmlpath,'r')
addon_id = re.compile('id="(.+?)"').findall(contents)
addon_id = addon_id[0] if (len(addon_id) > 0) else ''
return addon_id
else:
return folder
#----------------------------------------------------------------
# TUTORIAL #
def Installed_Addons(types='unknown', content ='unknown', properties = ''):
"""
This will send back a list of currently installed add-ons on the system.
All the three paramaters you can send through to this function are optional,
by default (without any params) this function will return a dictionary of all
installed add-ons. The dictionary will contain "addonid" and "type" e.g. 'xbmc.python.pluginsource'.
CODE: Installed_Addons([types, content, properties]):
AVAILABLE PARAMS:
types - If you only want to retrieve details for specific types of add-ons
then use this filter. Unfortunately only one type can be filtered at a time,
it is not yet possible to filter multiple types all in one go. Please check
the official wiki for the add-on types avaialble but here is an example if
you only wanted to show installed repositories: koding.Installed_Addons(types='xbmc.addon.repository')
content - Just as above unfortunately only one content type can be filtered
at a time, you can filter by video,audio,image and executable. If you want to
only return installed add-ons which appear in the video add-ons section you
would use this: koding.Installed_Addons(content='video')
properties - By default a dictionary containing "addonid" and "type" will be
returned for all found add-ons meeting your criteria. However you can add any
properties in here available in the add-on xml (check official Wiki for properties
available). Unlike the above two options you can choose to add multiple properties
to your dictionary, see example below:
koding.Installed_Addons(properties='name,thumbnail,description')
EXAMPLE CODE:
my_video_plugins = koding.Installed_Addons(types='xbmc.python.pluginsource', content='video', properties='name')
final_string = ''
for item in my_video_plugins:
final_string += 'ID: %s | Name: %s\n'%(item["addonid"], item["name"])
koding.Text_Box('LIST OF VIDEO PLUGINS',final_string)
~"""
try: import simplejson as json
except: import json
addon_dict = []
if properties != '':
properties = properties.replace(' ','')
properties = '"%s"' % properties
properties = properties.replace(',','","')
query = '{"jsonrpc":"2.0", "method":"Addons.GetAddons","params":{"properties":[%s],"enabled":"all","type":"%s","content":"%s"}, "id":1}' % (properties,types,content)
response = xbmc.executeJSONRPC(query)
data = json.loads(response)
if "result" in data:
try:
addon_dict = data["result"]["addons"]
except:
pass
return addon_dict
#----------------------------------------------------------------
# TUTORIAL #
def Open_Settings(addon_id='',focus='',click=False,stop_script=True):
"""
By default this will open the current add-on settings but if you pass through an addon_id it will open the settings for that add-on.
CODE: Open_Settings([addon_id, focus, click, stop_script])
AVAILABLE PARAMS:
addon_id - This optional, it can be any any installed add-on id. If nothing is passed
through the current add-on settings will be opened.
focus - This is optional, if not set the settings will just open to the first item
in the list (normal behaviour). However if you want to open to a specific category and
setting then enter the number in here separated by a dot. So for example if we want to
focus on the 2nd category and 3rd setting in the list we'd send through focus='2.3'
click - If you want the focused item to automatically be clicked set this to True.
stop_script - By default this is set to True, as soon as the addon settings are opened
the current script will stop running. If you pass through as False then the script will
continue running in the background - opening settings does not pause a script, Kodi just
see's it as another window being opened.
EXAMPLE CODE:
youtube_path = xbmc.translatePath('special://home/addons/plugin.video.youtube')
if os.path.exists(youtube_path):
dialog.ok('YOUTUBE SETTINGS','We will now open the YouTube settings.','We will focus on category 2, setting 3 AND send a click.')
koding.Open_Settings(addon_id='plugin.video.youtube',focus='2.3',click=True,stop_script=True)
else:
dialog.ok('YOUTUBE NOT INSTALLED','We cannot run this example as it uses the YouTube add-on which has not been found on your system.')
~"""
import xbmcaddon
if addon_id == '':
addon_id = Caller()
xbmc.log('ADDON ID: %s'%addon_id,2)
xbmc.executebuiltin('Addon.OpenSettings(%s)' % addon_id)
if focus != '':
category, setting = focus.split('.')
xbmc.executebuiltin('SetFocus(%d)' % (int(category) + 99))
xbmc.executebuiltin('SetFocus(%d)' % (int(setting) + 199))
if click:
xbmc.sleep(500)
xbmc.executebuiltin('Action(Select,10140)')
if stop_script:
try:
sys.exit()
except:
pass
#----------------------------------------------------------------
# TUTORIAL #
def Toggle_Addons(addon='all', enable=True, safe_mode=True, exclude_list=[], new_only=True, refresh=True, update_status=0):
"""
Send through either a list of add-on ids or one single add-on id.
The add-ons sent through will then be added to the addons*.db
and enabled or disabled (depending on state sent through).
WARNING: If safe_mode is set to False this directly edits the
addons*.db rather than using JSON-RPC. Although directly amending
the db is a lot quicker there is no guarantee it won't cause
severe problems in later versions of Kodi (this was created for v17).
DO NOT set safe_mode to False unless you 100% understand the consequences!
CODE: Toggle_Addons([addon, enable, safe_mode, exclude_list, new_only, refresh])
AVAILABLE PARAMS:
(*) addon - This can be a list of addon ids, one single id or
'all' to enable/disable all. If enabling all you can still use
the exclude_list for any you want excluded from this function.
enable - By default this is set to True, if you want to disable
the add-on(s) then set this to False.
safe_mode - By default this is set to True which means the add-ons
are enabled/disabled via JSON-RPC which is the method recommended by
the XBMC foundation. Setting this to False will result in a much
quicker function BUT there is no guarantee this will work on future
versions of Kodi and it may even cause corruption in future versions.
Setting to False is NOT recommended and you should ONLY use this if
you 100% understand the risks that you could break multiple setups.
exclude_list - Send through a list of any add-on id's you do not
want to be included in this command.
new_only - By default this is set to True so only newly extracted
add-on folders will be enabled/disabled. This means that any existing
add-ons which have deliberately been disabled by the end user are
not affected.
refresh - By default this is set to True, it will refresh the
current container and also force a local update on your add-ons db.
update_status - When running this function it needs to disable the
auto-update of add-ons by Kodi otherwise it risks crashing. This
update_status paramater is the state you want Kodi to revert back to
once the toggle of add-ons has completed. By default this is set to 0
which is auto-update. You can also choose 1 (notify of updates) or 2
(disable auto updates).
EXAMPLE CODE:
from systemtools import Refresh
xbmc.executebuiltin('ActivateWindow(Videos, addons://sources/video/)')
xbmc.sleep(2000)
dialog.ok('DISABLE YOUTUBE','We will now disable YouTube (if installed)')
koding.Toggle_Addons(addon='plugin.video.youtube', enable=False, safe_mode=True, exclude_list=[], new_only=False)
koding.Refresh('container')
xbmc.sleep(2000)
dialog.ok('ENABLE YOUTUBE','When you click OK we will enable YouTube (if installed)')
koding.Toggle_Addons(addon='plugin.video.youtube', enable=True, safe_mode=True, exclude_list=[], new_only=False)
koding.Refresh('container')
~"""
from __init__ import dolog
from filetools import DB_Path_Check, Get_Contents
from database import DB_Query
from systemtools import Last_Error, Refresh, Set_Setting, Sleep_If_Function_Active, Timestamp
from vartools import Data_Type
Set_Setting('general.addonupdates', 'kodi_setting', '2')
dolog('disabled auto updates for add-ons')
kodi_ver = int(float(xbmc.getInfoLabel("System.BuildVersion")[:2]))
addons_db = DB_Path_Check('addons')
data_type = Data_Type(addon)
state = int(bool(enable))
enabled_list = []
disabled_list = []
if kodi_ver >= 17:
on_system = DB_Query(addons_db,'SELECT addonID, enabled from installed')
# Create a list of enabled and disabled add-ons already on system
enabled_list = Addon_List(enabled=True)
disabled_list = Addon_List(enabled=False)
# If addon has been sent through as a string we add into a list
if data_type == 'unicode':
addon = addon.encode('utf8')
data_type = Data_Type(addon)
if data_type == 'str' and addon!= 'all':
addon = [addon]
# Grab all the add-on ids from addons folder
if addon == 'all':
addon = []
ADDONS = xbmc.translatePath('special://home/addons')
my_addons = Get_Contents(path=ADDONS, exclude_list=['packages','temp'])
for item in my_addons:
addon_id = Get_Addon_ID(item)
addon.append(addon_id)
# Find out what is and isn't enabled in the addons*.db
temp_list = []
for addon_id in addon:
if not addon_id in exclude_list and addon_id != '':
if addon_id in disabled_list and not new_only and enable:
temp_list.append(addon_id)
elif addon_id not in disabled_list and addon_id not in enabled_list:
temp_list.append(addon_id)
elif addon_id in enabled_list and not enable:
temp_list.append(addon_id)
elif addon_id in disabled_list and enable:
temp_list.append(addon_id)
addon = temp_list
# If you want to bypass the JSON-RPC mode and directly modify the db (READ WARNING ABOVE!!!)
if not safe_mode and kodi_ver >= 17:
installedtime = Timestamp('date_time')
insert_query = 'INSERT or IGNORE into installed (addonID , enabled, installDate) VALUES (?,?,?)'
update_query = 'UPDATE installed SET enabled = ? WHERE addonID = ? '
insert_values = [addon, state, installedtime]
try:
for item in addon:
DB_Query(addons_db, insert_query, [item, state, installedtime])
DB_Query(addons_db, update_query, [state, item])
except:
dolog(Last_Error())
if refresh:
Refresh()
# Using the safe_mode (JSON-RPC)
else:
mydeps = []
final_enabled = []
if state:
my_value = 'true'
log_value = 'ENABLED'
final_addons = []
else:
my_value = 'false'
log_value = 'DISABLED'
final_addons = addon
for my_addon in addon:
# If enabling the add-on then we also check for dependencies and enable them first
if state:
dependencies = Dependency_Check(addon_id=my_addon, recursive=True)
mydeps.append(dependencies)
# if enable selected we traverse through the dependencies enabling addons with lowest amount of deps to highest
if state:
mydeps = sorted(mydeps, key=len)
for dep in mydeps:
counter = 0
for item in dep:
enable_dep = True
if counter == 0:
final_addons.append(item)
enable_dep = False
elif item in final_enabled:
enable_dep = False
else:
enable_dep = True
if enable_dep:
if not item in exclude_list and not item in final_enabled and not item in enabled_list:
if Set_Setting(setting_type='addon_enable', setting=item, value = 'true'):
final_enabled.append(item)
counter += 1
# Now the dependencies are enabled we need to enable the actual main add-ons
for my_addon in final_addons:
if not my_addon in final_enabled:
if Set_Setting(setting_type='addon_enable', setting=my_addon, value = my_value):
final_enabled.append(addon)
if refresh:
Refresh(['addons','container'])
Set_Setting('general.addonupdates', 'kodi_setting', '%s'%update_status)
#----------------------------------------------------------------

View File

@@ -0,0 +1,93 @@
# -*- coding: utf-8 -*-
# script.module.python.koding.aio
# Python Koding AIO (c) by TOTALREVOLUTION LTD (support@trmc.freshdesk.com)
# Python Koding AIO is licensed under a
# Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License.
# You should have received a copy of the license along with this
# work. If not, see http://creativecommons.org/licenses/by-nc-nd/4.0.
# Please make sure you've read and understood the license, this code can NOT be used commercially
# and it can NOT be modified and redistributed. If you're found to be in breach of this license
# then any affected add-ons will be blacklisted and will not be able to work on the same system
# as any other add-ons which use this code. Thank you for your cooperation.
import xbmc
import subprocess
#----------------------------------------------------------------
# TUTORIAL #
def App_Settings(apk_id):
"""
Open up the settings for an installed Android app.
CODE: App_Settings(apk_id)
AVAILABLE PARAMS:
(*) apk_id - The id of the app you want to open the settings for.
EXAMPLE CODE:
my_apps = koding.My_Apps()
choice = dialog.select('CHOOSE AN APK', my_apps)
koding.App_Settings(apk_id=my_apps[choice])
~"""
xbmc.executebuiltin('StartAndroidActivity("","android.settings.APPLICATION_DETAILS_SETTINGS","","package:%s")' % apk_id)
#----------------------------------------------------------------
# TUTORIAL #
def My_Apps():
"""
Return a list of apk id's installed on system
CODE: My_Apps()
EXAMPLE CODE:
my_apps = koding.My_Apps()
choice = dialog.select('CHOOSE AN APK', my_apps)
if choice >= 0:
koding.App_Settings(apk_id=my_apps[choice])
~"""
Installed_APK = []
if xbmc.getCondVisibility('system.platform.android'):
try:
Installed_APK = subprocess.Popen(['exec ''/system/bin/pm list packages -3'''], executable='/system/bin/sh', shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT).communicate()[0].rstrip('\n').splitlines()
except Exception as e:
xbmc.log('Failed to grab installed app details: %s' % e)
Installed_APK = []
for i in range(len(Installed_APK)):
Installed_APK[i] = Installed_APK[i].partition(':')[2]
return Installed_APK
#----------------------------------------------------------------
# TUTORIAL #
def Start_App(apk_id):
"""
Open an Android application
CODE: Start_App(apk_id)
AVAILABLE PARAMS:
(*) apk_id - The id of the app you want to open.
EXAMPLE CODE:
dialog.ok('OPEN FACEBOOK','Presuming you have Facebook installed and this is an Android system we will now open that apk')
koding.Start_App(apk_id='com.facebook.katana')
~"""
xbmc.executebuiltin('StartAndroidActivity(%s)' % apk_id)
#----------------------------------------------------------------
# TUTORIAL #
def Uninstall_APK(apk_id):
"""
Uninstall and Android app
CODE: Uninstall_APK(apk_id)
EXAMPLE CODE:
if dialog.yesno('UNINSTALL FACEBOOK','Would you like to uninstall the Facebook app from your system?'):
koding.Uninstall_APK(apk_id='com.facebook.katana')
~"""
xbmc.executebuiltin('StartAndroidActivity("","android.intent.action.DELETE","","package:%s")' % apk_id)
#----------------------------------------------------------------

View File

@@ -0,0 +1,458 @@
# -*- coding: utf-8 -*-
# script.module.python.koding.aio
# Python Koding AIO (c) by TOTALREVOLUTION LTD (support@trmc.freshdesk.com)
# Python Koding AIO is licensed under a
# Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License.
# You should have received a copy of the license along with this
# work. If not, see http://creativecommons.org/licenses/by-nc-nd/4.0.
# Please make sure you've read and understood the license, this code can NOT be used commercially
# and it can NOT be modified and redistributed. If you're found to be in breach of this license
# then any affected add-ons will be blacklisted and will not be able to work on the same system
# as any other add-ons which use this code. Thank you for your cooperation.
import os
import sys
try: from sqlite3 import dbapi2 as database
except: from pysqlite2 import dbapi2 as database
import xbmc
import xbmcaddon
import xbmcgui
import xbmcvfs
from __init__ import Caller
from filetools import Physical_Path
# Put this in a try statement, when called from a service it will throw an error otherwise
try:
try:
ADDON_ID = xbmcaddon.Addon().getAddonInfo('id')
except:
ADDON_ID = Caller()
AddonVersion = xbmcaddon.Addon(id=ADDON_ID).getAddonInfo('version')
profile_path = xbmcaddon.Addon(id=ADDON_ID).getAddonInfo('profile')
addon_db_path = Physical_Path(os.path.join(profile_path,'database.db'))
except:
pass
dbcur, dbcon = None, None
dialog = xbmcgui.Dialog()
#----------------------------------------------------------------
def _connect_to_db():
""" internal command ~"""
def dict_factory(cursor, row):
d = {}
for idx, col in enumerate(cursor.description):
d[col[0]] = row[idx]
return d
xbmcvfs.mkdirs(profile_path)
db_location = os.path.join(profile_path.decode('utf-8'),'database.db')
db_location = Physical_Path(db_location)
dbcon = database.connect(db_location)
dbcon.row_factory = dict_factory
dbcur = dbcon.cursor()
return (dbcur, dbcon)
#----------------------------------------------------------------
def _execute_db_string(sql_string, commit = True):
""" internal command ~"""
global dbcur, dbcon
if dbcur is None or dbcon is None:
dbcur, dbcon = _connect_to_db()
dbcur.execute(sql_string)
if commit:
dbcon.commit()
results = []
for result in dbcur:
results.append(result)
return results
#----------------------------------------------------------------
# TUTORIAL #
def Add_To_Table(table, spec, abort_on_error=False):
"""
Add a row to the table in /userdata/addon_data/<your_addon_id>/database.db
CODE: Add_To_Table(table, spec)
AVAILABLE PARAMS:
(*) table - The table name you want to query
(*) spec - Sent through as a dictionary this is the colums and constraints.
abort_on_error - Default is set to False but set to True if you want to abort
the process when it hits an error.
EXAMPLE CODE:
create_specs = {"columns":{"name":"TEXT", "id":"TEXT"}}
koding.Create_Table("test_table", create_specs)
add_specs1 = {"name":"YouTube", "id":"plugin.video.youtube"}
add_specs2 = {"name":"vimeo","id":"plugin.video.vimeo"}
koding.Add_To_Table("test_table", add_specs1)
koding.Add_To_Table("test_table", add_specs2)
results = koding.Get_All_From_Table("test_table")
final_results = ''
for item in results:
final_results += 'ID: %s | Name: %s\n'%(item["id"], item["name"])
koding.Text_Box('DB RESULTS', final_results)
koding.Remove_Table('test_table')
~"""
global dbcon
sql_string = "INSERT INTO %s (" % table
keys = []
values = []
if type(spec) != list:
spec = [spec]
for item in spec:
for key in item.keys():
keys.append(key)
values.append(item[key])
for key in keys:
sql_string += "%s, " % key
sql_string = sql_string[:-2]
sql_string += ") Values ("
for value in values:
sql_string += "\"%s\", " % value
sql_string = sql_string[:-2]
sql_string += ")"
try:
_execute_db_string(sql_string, commit=False)
except:
if abort_on_error:
dbcon.rollback()
raise Exception()
continue
dbcon.commit()
#----------------------------------------------------------------
# TUTORIAL #
def Add_Multiple_To_Table(table, keys=[], values=[]):
"""
This will allow you to add multiple rows to a table in one big (fast) bulk command
The db file is: /userdata/addon_data/<your_addon_id>/database.db
CODE: Add_To_Table(table, spec)
AVAILABLE PARAMS:
(*) table - The table name you want to query
(*) keys - Send through a list of keys you want to add to
(*) values - A list of values you want to add, this needs to be
a list of lists (see example below)
EXAMPLE CODE:
create_specs = {"columns":{"name":"TEXT", "id":"TEXT"}}
koding.Create_Table("test_table", create_specs)
dialog.ok('ADD TO TABLE','Lets add the details of 3 add-ons to "test_table" in our database.')
mykeys = ["name","id"]
myvalues = [("YouTube","plugin.video.youtube"), ("vimeo","plugin.video.vimeo"), ("test2","plugin.video.test2")]
koding.Add_Multiple_To_Table(table="test_table", keys=mykeys, values=myvalues)
results = koding.Get_All_From_Table("test_table")
final_results = ''
for item in results:
final_results += 'ID: %s | Name: %s\n'%(item["id"], item["name"])
koding.Text_Box('DB RESULTS', 'Below are details of the items pulled from our db:\n\n%s'%final_results)
koding.Remove_Table('test_table')
~"""
dbcur, dbcon = _connect_to_db()
sql_string = "INSERT INTO %s (" % table
sql_2 = ''
if type(keys) != list:
keys = [keys]
if type(values) != list:
values = [values]
for item in keys:
if not item.startswith('`'):
item = r'`'+item
if not item.endswith('`'):
item = item+r'`'
xbmc.log('ITEM: %s'%item,2)
sql_string += "%s, " % item
sql_2 += "?,"
sql_string = "%s) VALUES (%s)"%(sql_string[:-2], sql_2[:-1])
dbcur.executemany(sql_string, values)
dbcon.commit()
#----------------------------------------------------------------
# TUTORIAL #
def Create_Table(table, spec):
"""
Create a new table in the database at /userdata/addon_data/<your_addon_id>/database.db
CODE: Create_Table(table, spec)
AVAILABLE PARAMS:
(*) table - The table name you want to query
(*) spec - Sent through as a dictionary this is the colums and constraints.
EXAMPLE CODE:
create_specs = { "columns":{"name":"TEXT", "id":"TEXT"}, "constraints":{"unique":"id"} }
koding.Create_Table("test_table", create_specs)
dialog.ok('TABLE CREATED','A new table has been created in your database and the id column has been set as UNIQUE.')
my_specs = {"name":"YouTube", "id":"plugin.video.youtube"}
try:
koding.Add_To_Table("test_table", my_specs)
koding.Add_To_Table("test_table", my_specs)
except:
dialog.ok('FAILED TO ADD','Could not add duplicate items because the the column "id" is set to be UNIQUE')
results = koding.Get_All_From_Table("test_table")
final_results = ''
for item in results:
final_results += 'ID: %s | Name: %s\n'%(item["id"], item["name"])
koding.Text_Box('DB RESULTS', final_results)
koding.Remove_Table('test_table')
~"""
sql_string = "CREATE TABLE IF NOT EXISTS %s (" % table
columns = spec.get("columns", {})
constraints = spec.get("constraints", {})
for key in columns.keys():
if not columns[key]:
columns[key] = "TEXT"
sql_string += "%s %s, " % (key, columns[key])
for key in constraints.keys():
sql_string += "%s(%s), " % (key, constraints[key])
sql_string = sql_string[:-2]
sql_string += ");"
_execute_db_string(sql_string)
#----------------------------------------------------------------
# TUTORIAL #
def DB_Query(db_path, query, values=''):
"""
Open a database and either return an array of results with the SELECT SQL command or perform an action such as INSERT, UPDATE, CREATE.
CODE: DB_Query(db_path, query, [values])
AVAILABLE PARAMS:
(*) db_path - the full path to the database file you want to access.
(*) query - this is the actual db query you want to process, use question marks for values
values - a list of values, even if there's only one value it must be sent through as a list item.
IMPORTANT: Directly accessing databases which are outside of your add-ons domain is very much frowned
upon. If you need to access a built-in kodi database (as shown in example below) you should always use
the JSON-RPC commands where possible.
EXAMPLE CODE:
import filetools
dbpath = filetools.DB_Path_Check('addons')
db_table = 'addon'
kodi_version = int(float(xbmc.getInfoLabel("System.BuildVersion")[:2]))
if kodi_version >= 17:
db_table = 'addons'
db_query = koding.DB_Query(db_path=dbpath, query='SELECT * FROM %s WHERE addonID LIKE ? AND addonID NOT LIKE ?'%db_table, values=['%youtube%','%script.module%'])
koding.Text_Box('DB SEARCH RESULTS',str(db_query))
~"""
db_dict = []
db_path = Physical_Path(db_path)
con = database.connect(db_path)
cur = con.cursor()
if query.upper().startswith('SELECT'):
if values == '':
cur.execute(query)
else:
cur.execute(query, values)
names = list(map(lambda x: x[0], cur.description))
for rows in iter(cur.fetchmany, []):
for row in rows:
temp_dict = {}
for idx, col in enumerate(cur.description):
temp_dict[col[0]] = row[idx]
db_dict.append(temp_dict)
return db_dict
elif query.upper().startswith('CREATE'):
cur.execute(query)
con.commit()
# ANY NON SELECT QUERY (UPDATE, INSERT ETC.)
else:
try:
if values == '':
cur.executemany(query)
con.commit()
else:
cur.executemany(query, values)
con.commit()
except:
if values == '':
cur.execute(query)
con.commit()
else:
cur.execute(query, values)
con.commit()
cur.close()
#----------------------------------------------------------------
# TUTORIAL #
def Get_All_From_Table(table):
"""
Return a list of all entries from a specific table in /userdata/addon_data/<your_addon_id>/database.db
CODE: Get_All_From_Table(table)
AVAILABLE PARAMS:
(*) table - The table name you want to query
EXAMPLE CODE:
create_specs = {"columns":{"name":"TEXT", "id":"TEXT"}}
koding.Create_Table("test_table", create_specs)
add_specs1 = {"name":"YouTube", "id":"plugin.video.youtube"}
add_specs2 = {"name":"vimeo","id":"plugin.video.vimeo"}
koding.Add_To_Table("test_table", add_specs1)
koding.Add_To_Table("test_table", add_specs2)
results = koding.Get_All_From_Table("test_table")
final_results = ''
for item in results:
final_results += 'ID: %s | Name: %s\n'%(item["id"], item["name"])
koding.Text_Box('DB RESULTS', final_results)
koding.Remove_Table('test_table')
~"""
try:
return _execute_db_string("SELECT * FROM %s" % table)
except:
return []
#----------------------------------------------------------------
# TUTORIAL #
def Get_From_Table(table, spec=None, default_compare_operator="="):
"""
Return a list of all entries matching a specific criteria from the
database stored at: /userdata/addon_data/<your_addon_id>/database.db
CODE: Get_From_Table(table, spec, compare_operator)
AVAILABLE PARAMS:
(*) table - The table name you want to query
spec - This is the query value, sent through as a dictionary.
default_compare_operator - By default this is set to '=' but could be any
other SQL query string such as 'LIKE', 'NOT LIKE', '!=' etc.
EXAMPLE CODE:
create_specs = {"columns":{"name":"TEXT", "id":"TEXT"}}
koding.Create_Table("test_table", create_specs)
add_specs1 = {"name":"YouTube", "id":"plugin.video.youtube"}
add_specs2 = {"name":"vimeo","id":"plugin.video.vimeo"}
koding.Add_To_Table("test_table", add_specs1)
koding.Add_To_Table("test_table", add_specs2)
results = koding.Get_From_Table(table="test_table", spec={"name":"%vim%"}, default_compare_operator="LIKE")
final_results = ''
for item in results:
final_results += 'ID: %s | Name: %s\n'%(item["id"], item["name"])
koding.Text_Box('DB CONTENTS', final_results)
koding.Remove_Table('test_table')
~"""
if spec == None:
return Get_All_From_Table()
sql_string = "SELECT * FROM %s WHERE " % table
for key in spec.keys():
if type(spec[key]) == dict:
value = spec[key]["value"]
column_compare_operator = spec[key].get("compare_operator", default_compare_operator)
else:
value = spec[key]
column_compare_operator = default_compare_operator
sql_string += "%s %s \"%s\" AND " % (key, column_compare_operator, value)
sql_string = sql_string[:-5]
try:
return _execute_db_string(sql_string, commit=False)
except:
return []
#----------------------------------------------------------------
# TUTORIAL #
def Remove_From_Table(table, spec, default_compare_operator="=", abort_on_error=False):
"""
Remove entries in the db table at /userdata/addon_data/<your_addon_id>/database.db
CODE: Remove_From_Table(table, spec, [compare_operator])
AVAILABLE PARAMS:
(*) table - The table name you want to query
spec - This is the query value, sent through as a dictionary.
default_compare_operator - By default this is set to '=' but could be any
other SQL query string such as 'LIKE', 'NOT LIKE', '!=' etc.
EXAMPLE CODE:
create_specs = {"columns":{"name":"TEXT", "id":"TEXT"}}
koding.Create_Table(table="test_table", spec=create_specs)
add_specs1 = {"name":"YouTube", "id":"plugin.video.youtube"}
add_specs2 = {"name":"vimeo","id":"plugin.video.vimeo"}
koding.Add_To_Table(table="test_table", spec=add_specs1)
koding.Add_To_Table(table="test_table", spec=add_specs2)
results = koding.Get_All_From_Table(table="test_table")
final_results = ''
for item in results:
final_results += 'ID: %s | Name: %s\n'%(item["id"], item["name"])
koding.Text_Box('DB CONTENTS', final_results)
dialog.ok('REMOVE ITEM','We will now remove vimeo from the table, lets see if it worked...')
koding.Remove_From_Table(table="test_table", spec={"name":"vimeo"})
results = koding.Get_All_From_Table(table="test_table")
final_results = ''
for item in results:
final_results += 'ID: %s | Name: %s\n'%(item["id"], item["name"])
koding.Text_Box('NEW DB CONTENTS', final_results)
koding.Remove_Table('test_table')
~"""
global dbcon
sql_string = "DELETE FROM %s WHERE " % table
if type(spec) != list:
spec = [spec]
for item in spec:
for key in item.keys():
if type(item[key]) == dict:
value = item[key]["value"]
column_compare_operator = item[key].get("compare_operator", default_compare_operator)
else:
value = item[key]
column_compare_operator = default_compare_operator
sql_string += "%s %s \"%s\" AND " % (key, column_compare_operator, value)
sql_string = sql_string[:-4]
try:
_execute_db_string(sql_string, commit=False)
except:
if abort_on_error:
dbcon.rollback()
raise Exception()
continue
dbcon.commit()
#----------------------------------------------------------------
# TUTORIAL #
def Remove_Table(table):
"""
Use with caution, this will completely remove a database table and
all of it's contents. The only database you can access with this command
is your add-ons own db file called database.db
CODE: Remove_Table(table)
AVAILABLE PARAMS:
(*) table - This is the name of the table you want to permanently delete.
EXAMPLE CODE:
dialog.ok('REMOVE TABLE','It\'s a bit pointless doing this as you can\'t physically see what\'s happening so you\'ll just have to take our word it works!')
koding.Remove_Table('test_table')
~"""
sql_string = "DROP TABLE IF EXISTS %s;" % table
_execute_db_string(sql_string)
#----------------------------------------------------------------
def reset_db():
global dbcon, dbcur
dbcur, dbcon = None, None

View File

@@ -0,0 +1,225 @@
# -*- coding: utf-8 -*-
# script.module.python.koding.aio
# Python Koding AIO (c) by TOTALREVOLUTION LTD (support@trmc.freshdesk.com)
# Python Koding AIO is licensed under a
# Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License.
# You should have received a copy of the license along with this
# work. If not, see http://creativecommons.org/licenses/by-nc-nd/4.0.
# Please make sure you've read and understood the license, this code can NOT be used commercially
# and it can NOT be modified and redistributed. If you're found to be in breach of this license
# then any affected add-ons will be blacklisted and will not be able to work on the same system
# as any other add-ons which use this code. Thank you for your cooperation.
import sys
import urllib
import xbmc
import xbmcaddon
import xbmcgui
import xbmcplugin
dialog = xbmcgui.Dialog()
mode = ''
#----------------------------------------------------------------
# TUTORIAL #
def Add_Dir(name, url='', mode='', folder=False, icon='', fanart='', description='', info_labels={}, set_art={}, set_property={}, content_type='', context_items=None, context_override=False, playable=False):
"""
This allows you to create a list item/folder inside your add-on.
Please take a look at your addon default.py comments for more information
(presuming you created one at http://totalrevolution.tv)
TOP TIP: If you want to send multiple variables through to a function just
send through as a dictionary encapsulated in quotation marks. In the function
you can then use the following code to access them:
params = eval(url)
^ That will then give you a dictionary where you can just pull each variable and value from.
CODE: Add_Dir(name, url, mode, [folder, icon, fanart, description, info_labels, content_type, context_items, context_override, playable])
AVAILABLE PARAMS:
(*) name - This is the name you want to show for the list item
url - If the route (mode) you're calling requires extra paramaters
to be sent through then this is where you add them. If the function is
only expecting one item then you can send through as a simple string.
Unlike many other Add_Dir functions Python Koding does allow for multiple
params to be sent through in the form of a dictionary so let's say your
function is expecting the 2 params my_time & my_date. You would send this info
through as a dictionary like this:
url={'my_time':'10:00', 'my_date':'01.01.1970'}
If you send through a url starting with plugin:// the item will open up into
that plugin path so for example:
url='plugin://plugin.video.youtube/play/?video_id=FTI16i7APhU'
mode - The mode you want to open when this item is clicked, this is set
in your master_modes dictionary (see template add-on linked above)
folder - This is an optional boolean, by default it's set to False.
True will open into a folder rather than an executable command
icon - The path to the thumbnail you want to use for this list item
fanart - The path to the fanart you want to use for this list item
description - A description of your list item, it's skin dependant but this
usually appears below the thumbnail
info_labels - You can send through any number of info_labels via this option.
For full details on the infolabels available please check the pydocs here:
http://mirrors.kodi.tv/docs/python-docs/16.x-jarvis/xbmcgui.html#ListItem-setInfo
When passing through infolabels you need to use a dictionary in this format:
{"genre":"comedy", "title":"test video"}
set_art - Using the same format as info_labels you can set your artwork via
a dictionary here. Full details can be found here:
http://mirrors.kodi.tv/docs/python-docs/16.x-jarvis/xbmcgui.html#ListItem-setArt
set_property - Using the same format as info_labels you can set your artwork via
a dictionary here. Full details can be found here:
http://kodi.wiki/view/InfoLabels#ListItem
content_type - By default this will set the content_type for kodi to a blank string
which is what Kodi expects for generic category listings. There are plenty of different
types though and when set Kodi will perform different actions (such as access the
database looking for season/episode information for the list item).
WARNING: Setting the wrong content type for your listing can cause the system to
log thousands of error reports in your log, cause the system to lag and make
thousands of unnecessary db calls - sometimes resulting in a crash. You can find
details on the content_types available here: http://forum.kodi.tv/showthread.php?tid=299107
context_items - Add context items to your directory. The params you need to send through
need to be in a list format of [(label, action,),] look at the example code below for
more details.
context_override - By default your context items will be added to the global context
menu items but you can override this by setting this to True and then only your
context menu items will show.
playable - By default this is set to False but if set to True kodi will just try
and play this item natively with no extra fancy functions.
EXAMPLE:
my_context = [('Music','xbmc.executebuiltin("ActivateWindow(music)")'),('Programs','xbmc.executebuiltin("ActivateWindow(programs)")')]
# ^ This is our two basic context menu items (music and programs)
Add_Dir(name='TEST DIRECTORY', url='', mode='test_directory', folder=True, context_items=my_context, context_override=True)
# ^ This will add a folder AND a context menu item for when bring up the menu (when focused on this directory).
# ^^ The context_override is set to True which means it will override the default Kodi context menu items.
Add_Dir(name='TEST ITEM', url='', mode='test_item', folder=False, context_items=my_context, context_override=False)
# ^ This will add an item to the list AND a context menu item for when bring up the menu (when focused on this item).
# ^^ The context_override is set to False which means the new items will appear alongside the default Kodi context menu items.
~"""
from vartools import Convert_Special, Data_Type
module_id = 'script.module.python.koding.aio'
this_module = xbmcaddon.Addon(id=module_id)
addon_handle = int(sys.argv[1])
# Check we're in an appropriate section for the content type set
song_only_modes = ['songs','artist','album','song','music']
video_only_modes = ['sets','tvshows','seasons','actors','directors','unknown','video','set','movie','tvshow','season','episode']
if xbmc.getInfoLabel('Window.Property(xmlfile)') == 'MyVideoNav.xml' and content_type in song_only_modes:
content_type = ''
if xbmc.getInfoLabel('Window.Property(xmlfile)') == 'MyMusicNav.xml' and content_type in video_only_modes:
content_type = ''
if description == '':
description = this_module.getLocalizedString(30837)
if Data_Type(url) == 'dict':
url = repr(url)
if Data_Type(info_labels) != 'dict':
dialog.ok('WRONG INFO LABELS', 'Please check documentation, these should be sent through as a dictionary.')
if Data_Type(set_art) != 'dict':
dialog.ok('WRONG SET_ART', 'Please check documentation, these should be sent through as a dictionary.')
if Data_Type(set_property) != 'dict':
dialog.ok('WRONG SET_PROPERTY', 'Please check documentation, these should be sent through as a dictionary.')
# Set the default title, filename and plot if not sent through already via info_labels
try:
title = info_labels["Title"]
if title == '':
info_labels["Title"] = name
except:
info_labels["Title"] = name
try:
filename = info_labels["FileName"]
# if filename == '':
# info_labels["FileName"] = name
except:
info_labels["FileName"] = name
try:
plot = info_labels["plot"]
if plot == '':
info_labels["plot"] = description
except:
info_labels["plot"] = description
# Set default thumbnail image used for listing (if not sent through via set_art)
try:
set_art["icon"]
except:
set_art["icon"] = icon
# Set default Fanart if not already sent through via set_property
try:
set_property["Fanart_Image"] = fanart
except:
set_property["Fanart_Image"]
# Set the main listitem properties
liz = xbmcgui.ListItem(label=str(name), iconImage=str(icon), thumbnailImage=str(icon))
# Set the infolabels
liz.setInfo(type=content_type, infoLabels=info_labels)
# Set the artwork
liz.setArt(set_art)
# Loop through the set_property list and set each item in there
for item in set_property.items():
liz.setProperty(item[0], item[1])
# Add a context item (if details for context items are sent through)
if context_items:
liz.addContextMenuItems(context_items, context_override)
u = sys.argv[0]
u += "?mode=" +str(mode)
u += "&url=" +Convert_Special(url,string=True)
u += "&name=" +urllib.quote_plus(name)
u += "&iconimage=" +urllib.quote_plus(icon)
u += "&fanart=" +urllib.quote_plus(fanart)
u += "&description=" +urllib.quote_plus(description)
if url.startswith('plugin://'):
xbmcplugin.addDirectoryItem(handle=addon_handle,url=url,listitem=liz,isFolder=True)
elif folder:
xbmcplugin.addDirectoryItem(handle=addon_handle,url=u,listitem=liz,isFolder=True)
elif playable:
liz.setProperty('IsPlayable', 'true')
xbmcplugin.addDirectoryItem(handle=addon_handle,url=url,listitem=liz,isFolder=False)
else:
xbmcplugin.addDirectoryItem(handle=addon_handle,url=u,listitem=liz,isFolder=False)
#----------------------------------------------------------------
def Default_Mode():
""" internal command ~"""
dialog = xbmcgui.Dialog()
dialog.ok('MODE ERROR','You\'ve tried to call Add_Dir() without a valid mode, check you\'ve added the mode into the master_modes dictionary')
#----------------------------------------------------------------

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,796 @@
# -*- coding: utf-8 -*-
# script.module.python.koding.aio
# Python Koding AIO (c) by TOTALREVOLUTION LTD (support@trmc.freshdesk.com)
# Python Koding AIO is licensed under a
# Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License.
# You should have received a copy of the license along with this
# work. If not, see http://creativecommons.org/licenses/by-nc-nd/4.0.
# Please make sure you've read and understood the license, this code can NOT be used commercially
# and it can NOT be modified and redistributed. If you're found to be in breach of this license
# then any affected add-ons will be blacklisted and will not be able to work on the same system
# as any other add-ons which use this code. Thank you for your cooperation.
import os
import sys
import xbmc
import xbmcgui
import xbmcvfs
from systemtools import Last_Error
from filetools import Physical_Path
dialog = xbmcgui.Dialog()
koding_path = Physical_Path("special://home/addons/script.module.python.koding.aio")
#----------------------------------------------------------------
# TUTORIAL #
def Browse_To_Folder(header='Select the folder you want to use', path = 'special://home'):
"""
As the title suggests this will bring up a dialog that allows the user to browse to a folder
and the path is then returned.
IMPORTANT: Do not confuse this with the Browse_To_File function
CODE: Browse_To_Folder(header, path)
AVAILABLE PARAMS:
header - As the name suggests this is a string to be used for the header/title
of the window. The default is "Select the folder you want to use".
path - Optionally you can add a default path for the browse start folder.
The default start position is the Kodi HOME folder.
EXAMPLE CODE:
folder = koding.Browse_To_Folder(header='Choose a folder you want to use', path='special://home/userdata')
dialog.ok('FOLDER DETAILS','Folder path: [COLOR=dodgerblue]%s[/COLOR]'%folder)
~"""
text = dialog.browse(type=3, heading=header, shares='files', useThumbs=False, treatAsFolder=False, defaultt=path)
return text
#----------------------------------------------------------------
# TUTORIAL #
def Browse_To_File(header='Select the file you want to use', path='special://home/addons/', extension='', browse_in_archives=False):
"""
This will allow the user to browse to a specific file and return the path.
IMPORTANT: Do not confuse this with the Browse_To_Folder function
CODE: koding.Browse_To_File([header, path, extension, browse_in_archives])
AVAILABLE PARAMS:
header - As the name suggests this is a string to be used for the header/title
of the window. The default is "Select the file you want to use".
path - Optionally you can add a default path for the browse start folder.
The default start position is the Kodi HOME folder.
extension - Optionally set extensions to filter by, let's say you only wanted
zip and txt files to show you would send through '.zip|.txt'
browse_in_archives - Set to true if you want to be able to browse inside zips and
other archive files. By default this is set to False.
EXAMPLE CODE:
dialog.ok('BROWSE TO FILE 1','We will now browse to your addons folder with browse_in_archives set to [COLOR dodgerblue]False[/COLOR]. Try clicking on a zip file if you can find one (check packages folder).')
folder = koding.Browse_To_File(header='Choose a file you want to use', path='special://home/addons')
dialog.ok('FOLDER DETAILS','File path: [COLOR=dodgerblue]%s[/COLOR]'%folder)
dialog.ok('BROWSE TO FILE 2','We will now browse to your addons folder with browse_in_archives set to [COLOR dodgerblue]True[/COLOR]. Try clicking on a zip file if you can find one (check packages folder).')
folder = koding.Browse_To_File(header='Choose a file you want to use', path='special://home/addons', browse_in_archives=True)
dialog.ok('FOLDER DETAILS','File path: [COLOR=dodgerblue]%s[/COLOR]'%folder)
~"""
if not path.endswith(os.sep):
path += os.sep
try:
text = dialog.browse(type=1, heading=header, shares='myprograms', mask=extension, useThumbs=False, treatAsFolder=browse_in_archives, defaultt=path)
except:
text = dialog.browse(type=1, heading=header, s_shares='myprograms', mask=extension, useThumbs=False,
treatAsFolder=browse_in_archives, defaultt=path)
return text
#----------------------------------------------------------------
# TUTORIAL #
def Countdown(title='COUNTDOWN STARTED', message='A quick simple countdown example.', update_msg='Please wait, %s seconds remaining.', wait_time=10, allow_cancel=True, cancel_msg='[COLOR=gold]Sorry, this process cannot be cancelled[/COLOR]'):
"""
Bring up a countdown timer and return true if waited or false if cancelled.
CODE: Countdown(title, message, update_msg, wait_time, allow_cancel, cancel_msg):
AVAILABLE PARAMS:
title - The header string in the dialog window, the default is:
'COUNTDOWN STARTED'
message - A short line of info which will show on the first line
of the dialog window just below the title. Default is:
'A quick simple countdown example.'
update_msg - The message you want to update during the countdown.
This must contain a %s which will be replaced by the current amount
of seconds that have passed. The default is:
'Please wait, %s seconds remaining.'
wait_time - This is the amount of seconds you want the countdown to
run for. The default is 10.
allow_cancel - By default this is set to true and the user can cancel
which will result in False being returned. If this is set to True
they will be unable to cancel.
cancel_msg - If allow_cancel is set to False you can add a custom
message when the user tries to cancel. The default string is:
'[COLOR=gold]Sorry, this process cannot be cancelled[/COLOR]'
EXAMPLE CODE:
dialog.ok('COUNTDOWN EXAMPLE', 'Press OK to bring up a countdown timer', '', 'Try cancelling the process.')
my_return = koding.Countdown(title='COUNTDOWN EXAMPLE', message='Quick simple countdown message (cancel enabled).', update_msg='%s seconds remaining', wait_time=5)
if my_return:
dialog.ok('SUCCESS!','Congratulations you actually waited through the countdown timer without cancelling!')
else:
dialog.ok('BORED MUCH?','What happened, did you get bored waiting?', '', '[COLOR=dodgerblue]Let\'s set off another countdown you CANNOT cancel...[/COLOR]')
koding.Countdown(title='COUNTDOWN EXAMPLE', message='Quick simple countdown message (cancel disabled).', update_msg='%s seconds remaining', wait_time=5, allow_cancel=False, cancel_msg='[COLOR=gold]Sorry, this process cannot be cancelled[/COLOR]')
~"""
dp = xbmcgui.DialogProgress()
current = 0
increment = 100 / wait_time
cancelled = False
dp.create(title)
while current <= wait_time:
if (dp.iscanceled()):
if allow_cancel:
cancelled = True
break
else:
dp.create(title,cancel_msg)
if current != 0:
xbmc.sleep(1000)
remaining = wait_time - current
if remaining == 0:
percent = 100
else:
percent = increment * current
remaining_display = update_msg % remaining
dp.update(percent, message, remaining_display)
current += 1
if cancelled == True:
return False
else:
return True
#----------------------------------------------------------------
# TUTORIAL #
def Custom_Dialog(pos='center', dialog='Text', size='700x500', button_width=200, icon='', fanart='',\
header='Disclaimer', main_content='Add some text here', buttons=['Decline','Agree'],\
header_color='gold', text_color='white', background='000000', transparency=100,\
highlight_color='gold', button_color_focused='4e91cf', button_trans_focused=100,\
button_color_nonfocused='586381', button_trans_nonfocused=50):
"""
A fully customisable dialog where you can have as many buttons as you want.
Similar behaviour to the standard Kodi yesno dialog but this allows as many buttons
as you want, as much text as you want (with a slider) as well as fully configurable
sizing and positioning.
CODE: Custom_Dialog([pos, dialog, size, button_width, header, main_content, buttons,\
header_color, text_color, background, transparency, highlight_color, button_color_focused,\
button_trans_focused, button_color_nonfocused, button_trans_nonfocused])
AVAILABLE PARAMS:
pos - This is the co-ordinates of where on the screen you want the
dialog to appear. This needs to be sent through as a string so for
example if you want the dialog top left corner to be 20px in and
10px down you would use pos='20x10'. By default this is set to 'center'
which will center the dialog on the screen.
dialog - By default this is set to 'Text'. Currently that is the
only custom dialog available but there are plans to improve upon this
and allow for image and even video dialogs.
size - Sent through as a string this is the dimensions you want the
dialog to be, by default it's set to '700x500' but you can set to any
size you want using that same format. Setting to 'fullscreen' will
use 1280x720 (fullscreen).
button_width - This is sent through as an integer and is the width you
want your buttons to be. By default this is set to 200 which is quite large
but looks quite nice if using only 2 or 3 buttons.
icon - If sent through this will be shown in the top right corner of your dialog,
make sure your first few lines of text aren't too long or they will overlap on top
of the image which is 150x150 pixels.
fanart - If sent through this will be the background image of your custom dialog.
Ideal if you want to only show an image, any text sent through will be overlayed
on top of this fanart.
header - Sent through as a string this is the header shown in the dialog.
The default is 'Disclaimer'.
header_color - Set the text colour, by default it's 'gold'
text_color - Set the text colour, by default it's 'white'
main_content - This is sent through as a string and is the main message text
you want to show in your dialog. When the ability to add videos, images etc.
is added there may well be new options added to this param but it will remain
backwards compatible.
buttons - Sent through as a list (tuple) this is a list of all your buttons.
Make sure you do not duplicate any names otherwise it will throw off the
formatting of the dialog and you'll get false positives with the results.
background - Optionally set the background colour (hex colour codes required).
The default is '000000' (black).
transparency - Set the percentage of transparency as an integer. By default
it's set to 100 which is a solid colour.
highlight_color - Set the highlighted text colour, by default it's 'gold'
button_color_focused - Using the same format as background you can set the
colour to use for a button when it's focused.
button_trans_focused - Using the same format as transparency you can set the
transparency amount to use on the button when in focus.
button_color_nonfocused - Using the same format as background you can set the
colour to use for buttons when they are not in focus.
button_trans_nonfocused - Using the same format as transparency you can set the
transparency amount to use on the buttons when not in focus.
EXAMPLE CODE:
main_text = 'This is my main text.\n\nYou can add anything you want in here and the slider will allow you to see all the contents.\n\nThis example shows using a blue background colour and a transparency of 90%.\n\nWe have also changed the highlighted_color to yellow.'
my_buttons = ['button 1', 'button 2', 'button 3']
my_choice = koding.Custom_Dialog(main_content=main_text,pos='center',buttons=my_buttons,background='213749',transparency=90,highlight_color='yellow')
dialog.ok('CUSTOM DIALOG 1','You selected option %s'%my_choice,'The value of this is: [COLOR=dodgerblue]%s[/COLOR]'%my_buttons[my_choice])
main_text = 'This is example 2 with no fancy colours, just a fullscreen and a working scrollbar.\n\nYou\'ll notice there are also a few more buttons on this one.\n\nline 1\nline 2\nline 3\nline 4\nline 5\nline 6\nline 7\nline 8\nline 9\nline 10\nline 11\nline 12\nline 13\nline 14\nline 15\nline 16\nline 17\nline 18\nline 19\nline 20\n\nYou get the idea we\'ll stop there!'
my_buttons = ['button 1', 'button 2', 'button 3','button 4', 'button 5', 'button 6','button 7', 'button 8', 'button 9','button 10', 'button 11', 'button 12', 'button 13','button 14', 'button 15', 'button 16','button 17', 'button 18', 'button 19','button 20']
my_choice = koding.Custom_Dialog(main_content=main_text,pos='center',size='fullscreen',buttons=my_buttons)
dialog.ok('CUSTOM DIALOG 2','You selected option %s'%my_choice,'The value of this is: [COLOR=dodgerblue]%s[/COLOR]'%my_buttons[my_choice])
~"""
skin_path = os.path.join(koding_path,"resources","skins","Default","720p")
ACTION = -1
# Convert the transparency percentage to hex
transparency = float(transparency) / 100 * 255
transparency = hex(int(transparency)).split('x')[1]
button_trans_focused = float(button_trans_focused) / 100 * 255
button_trans_focused = hex(int(button_trans_focused)).split('x')[1]
button_trans_nonfocused = float(button_trans_nonfocused) / 100 * 255
button_trans_nonfocused = hex(int(button_trans_nonfocused)).split('x')[1]
# Work out the dialog dimensions
if size == 'fullscreen':
dialog_width = '1280'
dialog_height = '720'
else:
dialog_width, dialog_height = size.split('x')
# Set the background to black image if not set otherwise remove background/transparency
if fanart != '' and xbmcvfs.exists(fanart):
background = ''
transparency = ''
else:
fanart = 'DialogBack.png'
button_count = len(buttons)
buttons_per_row = (int(dialog_width)-25) / (button_width+25)
if buttons_per_row > button_count:
buttons_per_row = button_count
# work out the number of rows, round up if a float
button_rows = int(button_count/buttons_per_row) + (button_count % buttons_per_row > 0)
# Work out the positioning of the dialog
if pos == 'center':
posx = str( (1280 - int(dialog_width)) / 2)
posy = str( (720 - int(dialog_height)) / 2)
else:
posx, posy = pos.split(',')
# Work out the text area size
text_width = str( int(dialog_width)-80 )
text_height = str( (int(dialog_height)-(50*(button_rows+1)))-70 )
scroll_pos = str( int(text_width)+32 )
button_max = int(dialog_height)-30
iconx = str(int(text_width)-150)
# Work out the button positions
if dialog == 'Text':
button_spacing = ( int(dialog_width)-(buttons_per_row*button_width) ) / (buttons_per_row+1)
buttons_dict = {}
counter = 1
row = 1
# Create a dictionary of button positioning
for button in buttons:
if counter > buttons_per_row:
counter = 1
row += 1
# If starting a new line reset the values
if counter > buttons_per_row or counter == 1:
current_pos = button_spacing
counter += 1
else:
current_pos = current_pos+button_width+button_spacing
counter += 1
buttons_dict[button] = [str(current_pos),row]
# Set the dialog template name and new temporary "live" XML
dialog_type = dialog.capitalize()+'.xml'
dialog_new = 'temp.xml'
dialog_path = os.path.join(skin_path,dialog_type)
temp_path = os.path.join(skin_path,dialog_new)
button_num = 100
counter = 1
buttons_code = ''
for button in buttons:
if buttons_dict[button][1] == 1:
onup = 99
else:
onup = button_num-buttons_per_row
# If button is on the last row we set down to scrollbar
if buttons_dict[button][1] == button_rows:
ondown = 99
# Otherwise set down to the item on row below
elif buttons_dict[button][1] != button_rows:
ondown = button_num+buttons_per_row
# Set the vertical position (y) of the buttons
button_y = str( int(text_height)+(buttons_dict[button][1]*50)+40 )
if ( int(text_height) < 200 ) or ( int(button_y) > button_max ):
if size != 'fullscreen':
xbmcgui.Dialog().ok('WE NEED A BIGGER WINDOW!','The amount of buttons sent through do not fit in this window. Either make the button width smaller or make a bigger window')
else:
xbmcgui.Dialog().ok('SMALLER BUTTONS NEEDED!','The amount of buttons sent through do not fit in this window. Either send through less buttons or decrease their width using the button_width param.')
return
button_x = str( buttons_dict[button][0] )
buttons_code += '\
<control type="button" id="%s">\n\
<posx>%s</posx>\n\
<posy>%s</posy>\n\
<width>%s</width>\n\
<height>40</height>\n\
<label>%s</label>\n\
<texturefocus colordiffuse="%s%s">DialogBack.png</texturefocus>\n\
<texturenofocus colordiffuse="%s%s">DialogBack.png</texturenofocus>\n\
<font>font12_title</font>\n\
<textcolor>%s</textcolor>\n\
<focusedcolor>%s</focusedcolor>\n\
<align>center</align>\n\
<onleft>%s</onleft>\n\
<onright>%s</onright>\n\
<onup>%s</onup>\n\
<ondown>%s</ondown>\n\
</control>\n' % (button_num, button_x, button_y, button_width, buttons[counter-1],\
button_trans_focused, button_color_focused, button_trans_nonfocused,\
button_color_nonfocused, text_color, highlight_color, button_num-1,\
button_num+1, onup, ondown)
button_num += 1
counter += 1
# Grab contents of the template and replace with our new values
with open(dialog_path, 'r') as content_file:
content = content_file.read()
content = content.replace('dialog_width',dialog_width)\
.replace('dialog_height',dialog_height)\
.replace('text_width',text_width)\
.replace('text_height',text_height)\
.replace('pos_x',posx)\
.replace('pos_y',posy)\
.replace('PK_Icon',icon)\
.replace('PK_I_X',iconx)\
.replace('PK_Fanart',fanart)\
.replace('PK_Transparency',transparency)\
.replace('PK_Color',background)\
.replace('PK_Text_Color',text_color)\
.replace('PK_Header_Color',header_color)\
.replace('<!-- buttons -->',buttons_code)
# Create the new temp "live" XML
myfile = open(temp_path,'w')
myfile.write(content)
myfile.close()
d=MyDisclaimer(dialog_new,koding_path,header=header,main_content=main_content)
d.doModal()
ACTION = d.ACTION
del d
return ACTION
class MyDisclaimer(xbmcgui.WindowXMLDialog):
def __init__(self,*args,**kwargs):
self.header=kwargs['header']
self.main_content=kwargs['main_content']
self.WINDOW=xbmcgui.Window( 10000 )
self.WINDOW.setProperty( 'PK_Header' , self.header )
self.WINDOW.setProperty( 'PK_Main_Text' , self.main_content )
self.ACTION=-1
def onClick( self, controlID ):
if controlID>=100:
self.ACTION=(controlID-100)
self.close()
elif controlID==12:
self.close()
def onAction( self, action ):
if action in [ 5, 6, 7, 9, 10, 92, 117 ] or action.getButtonCode() in [275,257,261]:
self.close()
#----------------------------------------------------------------
# TUTORIAL #
def Keyboard(heading='',default='',hidden=False,return_false=False,autoclose=False,kb_type='alphanum'):
"""
Show an on-screen keyboard and return the string
CODE: koding.Keyboard([default, heading, hidden, return_false, autoclose, kb_type])
AVAILABLE PARAMS:
heading - Optionally enter a heading for the text box.
default - This is optional, if set this will act as the default text shown in the text box
hidden - Boolean, if set to True the text will appear as hidden (starred out)
return_false - By default this is set to False and when escaping out of the keyboard
the default text is returned (or an empty string if not set). If set to True then
you'll receive a return of False.
autoclose - By default this is set to False but if you want the keyboard to auto-close
after a period of time you can send through an integer. The value sent through needs to
be milliseconds, so for example if you want it to close after 3 seconds you'd send through
3000. The autoclose function only works with standard alphanumeric keyboard types.
kb_type - This is the type of keyboard you want to show, by default it's set to alphanum.
A list of available values are listed below:
'alphanum' - A standard on-screen keyboard containing alphanumeric characters.
'numeric' - An on-screen numerical pad.
'date' - An on-screen numerical pad formatted only for a date.
'time' - An on-screen numerical pad formatted only for a time.
'ipaddress' - An on-screen numerical pad formatted only for an IP Address.
'password' - A standard keyboard but returns value as md5 hash. When typing
the text is starred out, once you've entered the password you'll get another
keyboard pop up asking you to verify. If the 2 match then your md5 has is returned.
EXAMPLE CODE:
mytext = koding.Keyboard(heading='Type in the text you want returned',default='test text')
dialog.ok('TEXT RETURNED','You typed in:', '', '[COLOR=dodgerblue]%s[/COLOR]'%mytext)
dialog.ok('AUTOCLOSE ENABLED','This following example we\'ve set the autoclose to 3000. That\'s milliseconds which converts to 3 seconds.')
mytext = koding.Keyboard(heading='Type in the text you want returned',default='this will close in 3s',autoclose=3000)
dialog.ok('TEXT RETURNED','You typed in:', '', '[COLOR=dodgerblue]%s[/COLOR]'%mytext)
mytext = koding.Keyboard(heading='Enter a number',kb_type='numeric')
dialog.ok('NUMBER RETURNED','You typed in:', '', '[COLOR=dodgerblue]%s[/COLOR]'%mytext)
dialog.ok('RETURN FALSE ENABLED','All of the following examples have "return_false" enabled. This means if you escape out of the keyboard the return will be False.')
mytext = koding.Keyboard(heading='Enter a date',return_false=True,kb_type='date')
dialog.ok('DATE RETURNED','You typed in:', '', '[COLOR=dodgerblue]%s[/COLOR]'%mytext)
mytext = koding.Keyboard(heading='Enter a time',return_false=True,kb_type='time')
dialog.ok('TIME RETURNED','You typed in:', '', '[COLOR=dodgerblue]%s[/COLOR]'%mytext)
mytext = koding.Keyboard(heading='IP Address',return_false=True,kb_type='ipaddress',autoclose=5)
dialog.ok('IP RETURNED','You typed in:', '', '[COLOR=dodgerblue]%s[/COLOR]'%mytext)
mytext = koding.Keyboard(heading='Password',kb_type='password')
dialog.ok('MD5 RETURN','The md5 for this password is:', '', '[COLOR=dodgerblue]%s[/COLOR]'%mytext)
~"""
from vartools import Decode_String
kb_type = eval( 'xbmcgui.INPUT_%s'%kb_type.upper() )
if hidden:
hidden = eval( 'xbmcgui.%s_HIDE_INPUT'%kb_type.upper() )
keyboard = dialog.input(heading,default,kb_type,hidden,autoclose)
if keyboard != '':
return keyboard
elif not return_false:
return Decode_String(default)
else:
return False
#----------------------------------------------------------------
# TUTORIAL #
def Notify(title, message, duration=2000, icon='special://home/addons/script.module.python.koding.aio/resources/update.png'):
"""
Show a short notification for x amount of seconds
CODE: koding.Notify(title, message, [duration, icon])
AVAILABLE PARAMS:
(*) title - A short title to show on top line of notification
(*) message - A short message to show on the bottom line of notification
duration - An integer in milliseconds, the default to show the notification for is 2000
icon - The icon to show in notification bar, default is the update icon from this module.
EXAMPLE CODE:
koding.Notify(title='TEST NOTIFICATION', message='This is a quick 5 second test', duration=5000)
~"""
xbmc.executebuiltin('XBMC.Notification(%s, %s, %s, %s)' % (title , message , duration, icon))
#----------------------------------------------------------------
# TUTORIAL #
def OK_Dialog(title,message):
"""
This will bring up a short text message in a dialog.ok window.
CODE: OK_Dialog(title,message)
AVAILABLE PARAMS:
(*) title - This is title which appears in the header of the window.
(*) message - This is the main text you want to appear.
EXAMPLE CODE:
koding.OK_Dialog(title='TEST DIALOG',message='This is a test dialog ok box. Click OK to quit.')
~"""
dialog.ok(title,message)
#----------------------------------------------------------------
# TUTORIAL #
def Select_Dialog(title,options,key=True):
"""
This will bring up a selection of options to choose from. The options are
sent through as a list and only one can be selected - this is not a multi-select dialog.
CODE: Select_Dialog(title,options,[key])
AVAILABLE PARAMS:
(*) title - This is title which appears in the header of the window.
(*) options - This is a list of the options you want the user to be able to choose from.
key - By default this is set to True so you'll get a return of the item number. For example
if the user picks "option 2" and that is the second item in the list you'll receive a return of
1 (0 would be the first item in list and 1 is the second). If set to False you'll recieve a return
of the actual string associated with that key, in this example the return would be "option 2".
EXAMPLE CODE:
my_options = ['Option 1','Option 2','Option 3','Option 4','Option 5']
mychoice = koding.Select_Dialog(title='TEST DIALOG',options=my_options,key=False)
koding.OK_Dialog(title='SELECTED ITEM',message='You selected: [COLOR=dodgerblue]%s[/COLOR]\nNow let\'s try again - this time we will return a key...'%mychoice)
mychoice = koding.Select_Dialog(title='TEST DIALOG',options=my_options,key=True)
koding.OK_Dialog(title='SELECTED ITEM',message='The item you selected was position number [COLOR=dodgerblue]%s[/COLOR] in the list'%mychoice)
~"""
mychoice = dialog.select(title,options)
if key:
return mychoice
else:
return options[mychoice]
#----------------------------------------------------------------
# TUTORIAL #
def Show_Busy(status=True, sleep=0):
"""
This will show/hide a "working" symbol.
CODE: Show_Busy([status, sleep])
AVAILABLE PARAMS:
status - This optional, by default it's True which means the "working"
symbol appears. False will disable.
sleep - If set the busy symbol will appear for <sleep> amount of
milliseconds and then disappear.
EXAMPLE CODE:
dialog.ok('BUSY SYMBOL','Press OK to show a busy dialog which restricts any user interaction. We have added a sleep of 5 seconds at which point it will disable.')
koding.Show_Busy(sleep=5000)
dialog.ok('BUSY SYMBOL','We will now do the same but with slightly different code')
koding.Show_Busy(status=True)
xbmc.sleep(5000)
koding.Show_Busy(status=False)
~"""
if status:
xbmc.executebuiltin("ActivateWindow(busydialog)")
if sleep:
xbmc.sleep(sleep)
xbmc.executebuiltin("Dialog.Close(busydialog)")
else:
xbmc.executebuiltin("Dialog.Close(busydialog)")
#----------------------------------------------------------------
# TUTORIAL #
def Text_Box(header, message):
"""
This will allow you to open a blank window and fill it with some text.
CODE: koding.Text_Box(header, message)
AVAILABLE PARAMS:
(*) header - As the name suggests this is a string to be used for the header/title of the window
(*) message - Yes you've probably already gussed it, this is the main message text
EXAMPLE CODE:
koding.Text_Box('TEST HEADER','Just some random text... Use kodi tags for new lines, colours etc.')
~"""
xbmc.executebuiltin("ActivateWindow(10147)")
controller = xbmcgui.Window(10147)
xbmc.sleep(500)
controller.getControl(1).setLabel(header)
controller.getControl(5).setText(message)
#----------------------------------------------------------------
# TUTORIAL #
def Reset_Percent(property='update_percent_',window_id=10000):
"""
If using the Update_Progress function for setting percentages in skinning then this
will allow you to reset all the percent properties (1-100)
CODE: Reset_Percent([property,window_id])
AVAILABLE PARAMS:
property - the property name you want reset, this will reset all properties starting
with this string from 1-100. For example if you use the default 'update_percent_' this
will loop through and reset update_percent_1, update_percent_2 etc. all the way through
to update_percent_100.
window_id - By default this is set to 10000 but you can send any id through you want.
kwargs - Send through any other params and the respective property will be set.colours etc.')
~"""
counter = 0
while counter <= 100:
xbmcgui.Window(10000).clearProperty('update_percent_%s'%counter)
counter +=1
#----------------------------------------------------------------
# TUTORIAL #
def Update_Progress(total_items,current_item,**kwargs):
"""
This function is designed for skinners but can be used for general Python too. It will
work out the current percentage of items that have been processed and update the
"update_percent" property accordingly (1-100). You can also send through any properties
you want updated and it will loop through updating them with the relevant values.
To send through properties just send through the property name as the param and assign to a value.
Example: Update_Progress( total_items=100,current_item=56, {"myproperty1":"test1","myproperty2":"test2"} )
CODE: Update_Progress(total_items,current_item,[kwargs])
AVAILABLE PARAMS:
(*) total_items - Total amount of items in your list you're processing
(*) current_item - Current item number that's been processed.
kwargs - Send through any other params and the respective property will be set.colours etc.
~"""
Reset_Percent()
for item in kwargs:
if item.endswith('color'):
value = '0xFF'+kwargs[item]
else:
value = kwargs[item]
if value == 'false' or value == '' and not item.endswith('color'):
xbmcgui.Window(10000).clearProperty(item)
elif value:
xbmcgui.Window(10000).setProperty(item, value)
percent = 100*(current_item/(total_items*1.0))
newpercent=int(percent)
if (newpercent % 1 == 0) and (newpercent <=100):
xbmcgui.Window(10000).setProperty('update_percent',str(newpercent))
xbmcgui.Window(10000).setProperty('update_percent_%s'%newpercent,'true')
if newpercent == 100:
xbmc.executebuiltin('Action(firstpage)')
#-----------------------------------------------------------------------------
# TUTORIAL #
def Update_Screen(disable_quit=False, auto_close=True):
"""
This will create a full screen overlay showing progress of updates. You'll need to
use this in conjunction with the Update_Progress function.
CODE: Update_Screen([disable_quit, auto_close))
AVAILABLE PARAMS:
disable_quit - By default this is set to False and pressing the parent directory
button (generally esc) will allow you to close the window. Setting this to True
will mean it's not possible to close the window manually.
auto_close - By default this is set to true and when the percentage hits 100
the window will close. If you intend on then sending through some more commands
you might want to consider leaving this window open in which case you'd set this
to false. Bare in mind if you go this route the window will stay active until
you send through the kill command which is: xbmc.executebuiltin('Action(firstpage)')
EXAMPLE CODE:
mykwargs = {
"update_header" : "Downloading latest updates",\
"update_main_text" : "Your device is now downloading all the latest updates.\nThis shouldn\'t take too long, "\
"depending on your internet speed this could take anything from 2 to 10 minutes.\n\n"\
"Once downloaded the system will start to install the updates.",\
"update_bar_color" : "4e91cf",\
"update_icon" : "special://home/addons/script.module.python.koding.aio/resources/skins/Default/media/update.png",\
"update_spinner" : "true"}
Update_Screen()
counter = 1
while counter <= 60:
xbmc.sleep(300)
Update_Progress(total_items=60,current_item=counter,**mykwargs)
if counter == 30:
mykwargs = {
"update_header" : "Halfway there!",\
"update_main_text" : "We just updated the properties to show how you can change things on the fly "\
"simply by sending through some different properties. Both the icon and the "\
"background images you see here are being pulled from online.",\
"update_header_color" : "4e91cf",\
"update_percent_color" : "4e91cf",\
"update_bar_color" : "4e91cf",\
"update_background" : "http://www.planwallpaper.com/static/images/518164-backgrounds.jpg",\
"update_icon" : "http://totalrevolution.tv/img/tr_small_black_bg.jpg",\
"update_spinner" : "false"}
counter += 1
~"""
import threading
update_screen_thread = threading.Thread(target=Show_Update_Screen, args=[disable_quit, auto_close])
update_screen_thread.start()
xbmc.sleep(2000)
def Show_Update_Screen(disable_quit=False,auto_close=True):
xbmcgui.Window(10000).clearProperty('update_icon')
xbmcgui.Window(10000).clearProperty('update_percent')
xbmcgui.Window(10000).clearProperty('update_spinner')
xbmcgui.Window(10000).clearProperty('update_header')
xbmcgui.Window(10000).clearProperty('update_main_text')
xbmcgui.Window(10000).setProperty('update_background','whitebg.jpg')
xbmcgui.Window(10000).setProperty('update_percent_color','0xFF000000')
xbmcgui.Window(10000).setProperty('update_bar_color','0xFF000000')
xbmcgui.Window(10000).setProperty('update_main_color','0xFF000000')
xbmcgui.Window(10000).setProperty('update_header_color','0xFF000000')
# Set a property so we can determine if update screen is active
xbmcgui.Window(10000).setProperty('update_screen','active')
d=MyUpdateScreen('Loading.xml',koding_path,disable_quit=disable_quit,auto_close=auto_close)
d.doModal()
del d
xbmcgui.Window(10000).clearProperty('update_screen')
class MyUpdateScreen(xbmcgui.WindowXMLDialog):
def __init__(self,*args,**kwargs):
self.disable_quit=kwargs['disable_quit']
self.auto_close=kwargs['auto_close']
self.WINDOW=xbmcgui.Window( 10000 )
def onAction( self, action ):
if action in [10,7]:
if self.disable_quit:
xbmc.log("ESC and HOME Disabled",2)
else:
self.close()
if action==159 and self.auto_close:
self.close()
#----------------------------------------------------------------
# TUTORIAL #
def YesNo_Dialog(title,message,yes=None,no=None):
"""
This will bring up a short text message in a dialog.yesno window. This will
return True or False
CODE: YesNo_Dialog(title,message,[yeslabel,nolabel])
AVAILABLE PARAMS:
(*) title - This is title which appears in the header of the window.
(*) message - This is the main text you want to appear.
yes - Optionally change the default "YES" to a custom string
no - Optionally change the default "NO" to a custom string
EXAMPLE CODE:
mychoice = koding.YesNo_Dialog(title='TEST DIALOG',message='This is a yes/no dialog with custom labels.\nDo you want to see an example of a standard yes/no.',yes='Go on then',no='Nooooo!')
if mychoice:
koding.YesNo_Dialog(title='STANDARD DIALOG',message='This is an example of a standard one without sending custom yes/no params through.')
~"""
choice = dialog.yesno(title,message,yeslabel=yes,nolabel=no)
return choice
#----------------------------------------------------------------

View File

@@ -0,0 +1,140 @@
# -*- coding: utf-8 -*-
# script.module.python.koding.aio
# Python Koding AIO (c) by TOTALREVOLUTION LTD (support@trmc.freshdesk.com)
# Python Koding AIO is licensed under a
# Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License.
# You should have received a copy of the license along with this
# work. If not, see http://creativecommons.org/licenses/by-nc-nd/4.0.
# Please make sure you've read and understood the license, this code can NOT be used commercially
# and it can NOT be modified and redistributed. If you're found to be in breach of this license
# then any affected add-ons will be blacklisted and will not be able to work on the same system
# as any other add-ons which use this code. Thank you for your cooperation.
import directory
import tutorials
import video
import xbmc
import xbmcgui
dialog = xbmcgui.Dialog()
HOME = xbmc.translatePath('special://home')
master_modes = {
# Required for certain koding functions to work
"play_video": {'function': video.Play_Video, 'args': ["url"]},
"show_tutorial": {'function': tutorials.Show_Tutorial, 'args': ["url"]},
"tutorials": {'function': tutorials.Grab_Tutorials, 'args': []},
}
#----------------------------------------------------------------
# TUTORIAL #
def route(mode, args=[]):
"""
Use this to set a function in your master_modes dictionary.
This is to be used for Add_Dir() items, see the example below.
CODE: route(mode, [args])
AVAILABLE PARAMS:
(*) mode - This must be set, it needs to be a custom string.
This is the string you'd use in your Add_Dir command to call
the function.
args - This is optional but if the function you're calling
requires extra paramaters you can add them in here. Just add them
as a list of strings. Example: args=['name','artwork','description']
BELOW IS AN EXAMPLE OF HOW TO CALL THE CODE IN YOUR MAIN ADDON PY FILE:
@route(mode="test", args=["name","description"])
def Test_Function(name,description):
dialog.ok('This is a test function', name, description')
koding.Add_Dir(name='Test Dialog', url={"name":"My Test Function", "description" : "Its ALIVE!!!"}, mode='test')
koding.run()
~"""
if mode not in master_modes:
def _route(function):
master_modes[mode] = {
'function': function,
'args': args
}
return function
return _route
else:
dialog.ok('DUPLICATE MODE',
'The following mode already exists:',
'[COLOR=dodgerblue]%s[/COLOR]' % mode)
#----------------------------------------------------------------
# TUTORIAL #
def Run(default="main"):
"""
This needs to be called at the bottom of your code in the main default.py
This checks the modes called in Add_Dir and does all the clever stuff
in the background which assigns those modes to functions and sends
through the various params.
Just after this command you need to make
sure you set the endOfDirectory (as shown below).
CODE: run([default])
xbmcplugin.endOfDirectory(int(sys.argv[1]))
AVAILABLE PARAMS:
default - This is the default mode you want the add-on to open
into, it's set as "main" by default. If you have a different mode
name you want to open into just edit accordingly.
~"""
import urllib
import urlparse
import sys
from __init__ import DEBUG
from guitools import Text_Box
from systemtools import Last_Error
params = dict(urlparse.parse_qsl(sys.argv[2].replace('?', '')))
mode = params.get("mode", default)
if mode in master_modes:
evaled_args = []
# Grab the url and split up into a dictionary of args
try:
main_url = params["url"]
# Convert back from special to physical path - useful for community shares
if urllib.unquote_plus("special://home/") in main_url:
main_url = main_url.replace('special://home/',HOME)
except:
main_url = ''
try:
my_args = eval(main_url)
except:
my_args = {"url":main_url}
for arg in master_modes[mode]["args"]:
try:
evaled_args.append(my_args[arg])
except:
if DEBUG == 'true':
dialog.ok('ERROR IN CODE','Your Add_Dir function is expecting the [COLOR=gold][B]%s[/B][/COLOR] paramater to be sent through. This does not exist, please check your Add_Dir function.'%arg)
xbmc.log(Last_Error(),2)
return
try:
master_modes[mode]["function"](*evaled_args)
except:
if DEBUG == 'true':
Text_Box('ERROR IN CODE', Last_Error())
xbmc.log(Last_Error(),2)
else:
pass
else:
dialog.ok('MODE DOES NOT EXIST',
'The following mode does not exist in your\
master_modes dictionary:',
'[COLOR=dodgerblue]%s[/COLOR]' % mode)

View File

@@ -0,0 +1,908 @@
# -*- coding: utf-8 -*-
# script.module.python.koding.aio
# Python Koding AIO (c) by TOTALREVOLUTION LTD (support@trmc.freshdesk.com)
# Python Koding AIO is licensed under a
# Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License.
# You should have received a copy of the license along with this
# work. If not, see http://creativecommons.org/licenses/by-nc-nd/4.0.
# Please make sure you've read and understood the license, this code can NOT be used commercially
# and it can NOT be modified and redistributed. If you're found to be in breach of this license
# then any affected add-ons will be blacklisted and will not be able to work on the same system
# as any other add-ons which use this code. Thank you for your cooperation.
import datetime
import os
import sys
import shutil
import xbmc
import xbmcaddon
import xbmcgui
import xbmcvfs
try:
from vartools import Data_Type
except:
pass
#----------------------------------------------------------------
# TUTORIAL #
def Cleanup_Textures(frequency=14,use_count=10):
"""
This will check for any cached artwork and wipe if it's not been accessed more than 10 times in the past x amount of days.
CODE: Cleanup_Textures([frequency, use_count])
AVAILABLE PARAMS:
frequency - This is an optional integer, be default it checks for any
images not accessed in 14 days but you can use any amount of days here.
use_count - This is an optional integer, be default it checks for any
images not accessed more than 10 times. If you want to be more ruthless
and remove all images not accessed in the past x amount of days then set this very high.
EXAMPLE CODE:
dialog.ok('Clean Textures','We are going to clear any old cached images not accessed at least 10 times in the past 5 days')
koding.Cleanup_Textures(frequency=5)
~"""
try: from sqlite3 import dbapi2 as database
except: from pysqlite2 import dbapi2 as database
from filetools import DB_Path_Check
db = DB_Path_Check('Textures')
xbmc.log('### DB_PATH: %s' % db)
conn = database.connect(db, timeout = 10, detect_types=database.PARSE_DECLTYPES, check_same_thread = False)
conn.row_factory = database.Row
c = conn.cursor()
# Set paramaters to check in db, cull = the datetime (we've set it to 14 days) and useCount is the amount of times the file has been accessed
cull = datetime.datetime.today() - datetime.timedelta(days = frequency)
# Create an array to store paths for images and ids for database
ids = []
images = []
c.execute("SELECT idtexture FROM sizes WHERE usecount < ? AND lastusetime < ?", (use_count, str(cull)))
for row in c:
ids.append(row["idtexture"])
for id in ids:
c.execute("SELECT cachedurl FROM texture WHERE id = ?", (id,))
for row in c:
images.append(row["cachedurl"])
# Clean up database
for id in ids:
c.execute("DELETE FROM sizes WHERE idtexture = ?", (id,))
c.execute("DELETE FROM texture WHERE id = ?", (id,))
c.execute("VACUUM")
conn.commit()
c.close()
xbmc.log("### Automatic Cache Removal: %d Old Textures removed" % len(images))
# Delete files
thumbfolder = 'special://home/userdata/Thumbnails'
for image in images:
path = os.path.join(thumbfolder, image)
try:
xbmcvfs.delete(path)
except:
xbmc.log(Last_Error())
#----------------------------------------------------------------
# TUTORIAL #
def Current_Profile():
"""
This will return the current running profile, it's only one line of code but this is for my benefit as much as
anyone else's. I use this function quite a lot and keep forgetting the code so figured it would be easier to
just write a simple function for it :)
CODE: Current_Profile()
EXAMPLE CODE:
profile = koding.Current_Profile()
dialog.ok('CURRENT PROFILE','Your current running profile is:','[COLOR=dodgerblue]%s[/COLOR]' % profile)
~"""
return xbmc.getInfoLabel('System.ProfileName')
#----------------------------------------------------------------
# TUTORIAL #
def Force_Close():
"""
Force close Kodi, should only be used in extreme circumstances.
CODE: Force_Close()
EXAMPLE CODE:
if dialog.yesno('FORCE CLOSE','Are you sure you want to forcably close Kodi? This could potentially cause corruption if system tasks are taking place in background.'):
koding.Force_Close()
~"""
os._exit(1)
#----------------------------------------------------------------
# TUTORIAL #
def Get_ID(setid=False):
"""
A simple function to set user id and group id to the current running App
for system commands. For example if you're using the subprocess command
you could send through the preexec_fn paramater as koding.Get_ID(setid=True).
This function will also return the uid and gid in form of a dictionary.
CODE: Get_ID([setid])
AVAILABLE PARAMS:
(*) setid - By default this is set to False but if set to True it
will set the ids (to be used for subprocess commands)
EXAMPLE CODE:
ids = Get_ID(setid=False)
if ids:
uid = ids['uid']
gid = ids['gid']
dialog.ok('USER & GROUP ID','User ID: %s'%uid, 'Group ID: %s'%gid)
else:
dialog.ok('USER & GROUP ID','This function is not applicable to your system. We\'ve been sent back a return of False to indicate this function does not exist on your os.')
~"""
try:
uid = os.getuid()
gid = os.getgid()
if setid:
os.setgid(uid)
os.setuid(gid)
if not setid:
return {"uid":uid,"gid":gid}
except:
return False
#----------------------------------------------------------------
def Get_Mac(protocol = 'eth'):
cont = False
vpn_check = False
counter = 0
mac = ''
while len(mac)!=17 and counter < 5:
if sys.platform == 'win32':
for line in os.popen("ipconfig /all"):
if protocol == 'wifi':
if line.startswith('Wireless LAN adapter Wi'):
cont = True
if line.lstrip().startswith('Physical Address') and cont:
mac = line.split(':')[1].strip().replace('-',':').replace(' ','')
break
else:
if line.lstrip().startswith('Description'):
if not 'VPN' in line:
vpn_check = True
if line.lstrip().startswith('Physical Address') and vpn_check:
mac = line.split(':')[1].strip().replace('-',':').replace(' ','')
vpn_check = False
break
elif sys.platform == 'darwin':
if protocol == 'wifi':
for line in os.popen("ifconfig en0 | grep ether"):
if line.lstrip().startswith('ether'):
mac = line.split('ether')[1].strip().replace('-',':').replace(' ','')
break
else:
for line in os.popen("ifconfig en1 | grep ether"):
if line.lstrip().startswith('ether'):
mac = line.split('ether')[1].strip().replace('-',':').replace(' ','')
break
elif xbmc.getCondVisibility('System.Platform.Android'):
try:
if protocol == 'wifi':
readfile = open('/sys/class/net/wlan0/address', mode='r')
if protocol != 'wifi':
readfile = open('/sys/class/net/eth0/address', mode='r')
mac = readfile.read()
readfile.close()
mac = mac.strip()
mac = mac.replace(' ','')
mac = mac[:17]
except:
mac = ''
else:
mac = ''
if protocol == 'wifi':
for line in os.popen("/sbin/ifconfig"):
if line.find('wlan0') > -1:
mac = line.split()[4].strip()
break
elif line.startswith('en'):
if 'Ethernet'in line and 'HWaddr' in line:
mac = line.split('HWaddr')[1].strip()
break
else:
for line in os.popen("/sbin/ifconfig"):
if line.find('eth0') > -1:
mac = line.split()[4].strip()
break
elif line.startswith('wl'):
if 'Ethernet'in line and 'HWaddr' in line:
mac = line.split('HWaddr')[1].strip()
break
counter += 1
xbmc.log('attempt no.%s %s mac: %s'%(counter,protocol,mac),2)
if len(mac) != 17:
counter = 0
while counter < 5 and len(mac) != 17:
mac = xbmc.getInfoLabel('Network.MacAddress')
xbmc.log('MAC (backup): %s'%mac,2)
xbmc.sleep(100)
counter += 1
if len(mac) != 17:
return 'Unknown'
else:
return mac
#-----------------------------------------------------------------------------
# TUTORIAL #
def Grab_Log(log_type = 'std', formatting = 'original', sort_order = 'reverse'):
"""
This will grab the log file contents, works on all systems even forked kodi.
CODE: Grab_Log([log_type, formatting, sort_order])
AVAILABLE PARAMS:
log_type - This is optional, if not set you will get the current log.
If you would prefer the old log set this to 'old'
formatting - By default you'll just get a default log but you can set
this to 'warnings', 'notices', 'errors' to filter by only those error types.
Notices will return in blue, warnings in gold and errors in red.
You can use as many of the formatting values as you want, just separate by an
underscore such as 'warnings_errors'. If using anything other than the
default in here your log will returned in order of newest log activity first
(reversed order). You can also use 'clean' as an option and that will just
return the full log but with clean text formatting and in reverse order.
sort_order - This will only work if you've sent through an argument other
than 'original' for the formatting. By default the log will be shown in
'reverse' order but you can set this to 'original' if you prefer ascending
timestamp ordering like a normal log.
EXAMPLE CODE:
my_log = koding.Grab_Log()
dialog.ok('KODI LOG LOOP','Press OK to see various logging options, every 5 seconds it will show a new log style.')
koding.Text_Box('CURRENT LOG FILE (ORIGINAL)',my_log)
xbmc.sleep(5000)
my_log = koding.Grab_Log(formatting='clean', sort_order='reverse')
koding.Text_Box('CURRENT LOG FILE (clean in reverse order)',my_log)
xbmc.sleep(5000)
my_log = koding.Grab_Log(formatting='errors_warnings', sort_order='reverse')
koding.Text_Box('CURRENT LOG FILE (erros & warnings only - reversed)',my_log)
xbmc.sleep(5000)
old_log = koding.Grab_Log(log_type='old')
koding.Text_Box('OLD LOG FILE',old_log)
~"""
from filetools import Physical_Path, Text_File
log_path = Physical_Path('special://logpath/')
logfilepath = os.listdir(log_path)
finalfile = 0
for item in logfilepath:
cont = False
if item.endswith('.log') and not item.endswith('.old.log') and log_type == 'std':
mylog = os.path.join(log_path,item)
cont = True
elif item.endswith('.old.log') and log_type == 'old':
mylog = os.path.join(log_path,item)
cont = True
if cont:
lastmodified = xbmcvfs.Stat(mylog).st_mtime()
if lastmodified>finalfile:
finalfile = lastmodified
logfile = mylog
logtext = Text_File(logfile, 'r')
if formatting != 'original':
logtext_final = ''
with open(logfile) as f:
log_array = f.readlines()
log_array = [line.strip() for line in log_array]
if sort_order == 'reverse':
log_array = reversed(log_array)
for line in log_array:
if ('warnings' in formatting or 'clean' in formatting) and 'WARNING:' in line:
logtext_final += line.replace('WARNING:', '[COLOR=gold]WARNING:[/COLOR]')+'\n'
if ('errors' in formatting or 'clean' in formatting) and 'ERROR:' in line:
logtext_final += line.replace('ERROR:', '[COLOR=red]ERROR:[/COLOR]')+'\n'
if ('notices' in formatting or 'clean' in formatting) and 'NOTICE:' in line:
logtext_final += line.replace('NOTICE:', '[COLOR=dodgerblue]NOTICE:[/COLOR]')+'\n'
logtext = logtext_final
return logtext
#----------------------------------------------------------------
# TUTORIAL #
def Last_Error():
"""
Return details of the last error produced, perfect for try/except statements
CODE: Last_Error()
EXAMPLE CODE:
try:
xbmc.log(this_should_error)
except:
koding.Text_Box('ERROR MESSAGE',Last_Error())
~"""
import traceback
error = traceback.format_exc()
return error
#----------------------------------------------------------------
# TUTORIAL #
def Network_Settings():
"""
Attempt to open the WiFi/network settings for the current running operating system.
I have no access to any iOS based systems so if anybody wants to add support for
that and you know the working code please contact me at info@totalrevolution.tv
The Linux one is also currently untested and of course there are many different
distros so if you know of any improved code please do pass on. Thank you.
CODE: Network_Settings()
EXAMPLE CODE:
koding.Network_Settings()
~"""
content = Grab_Log()
if xbmc.getCondVisibility('System.Platform.Android'):
xbmc.executebuiltin('StartAndroidActivity(,android.settings.WIFI_SETTINGS)')
elif xbmc.getCondVisibility('System.Platform.OSX'):
os.system('open /System/Library/PreferencePanes/Network.prefPane/')
elif xbmc.getCondVisibility('System.Platform.Windows'):
os.system('ncpa.cpl')
elif 'Running on OpenELEC' in content or 'Running on LibreELEC' in content:
if xbmc.getCondVisibility("System.HasAddon(service.openelec.settings)") or xbmc.getCondVisibility("System.HasAddon(service.libreelec.settings)"):
if xbmc.getCondVisibility("System.HasAddon(service.openelec.settings)"):
xbmcaddon.Addon(id='service.openelec.settings').getAddonInfo('name')
xbmc.executebuiltin('RunAddon(service.openelec.settings)')
elif xbmc.getCondVisibility("System.HasAddon(service.libreelec.settings)"):
xbmcaddon.Addon(id='service.libreelec.settings').getAddonInfo('name')
xbmc.executebuiltin('RunAddon(service.libreelec.settings)')
xbmc.sleep(1500)
xbmc.executebuiltin('Control.SetFocus(1000,2)')
xbmc.sleep(500)
xbmc.executebuiltin('Control.SetFocus(1200,0)')
elif xbmc.getCondVisibility('System.Platform.Linux'):
os.system('nm-connection-editor')
#----------------------------------------------------------------
# TUTORIAL #
def Python_Version():
"""
Return the current version of Python as a string. Very useful if you need
to find out whether or not to return https links (Python 2.6 is not SSL friendly).
CODE: Python_Version()
EXAMPLE CODE:
py_version = koding.Python_Version()
dialog.ok('PYTHON VERSION','You are currently running:','Python v.%s'%py_version)
~"""
py_version = '%s.%s'%(sys.version_info[0],sys.version_info[1])
return py_version
#----------------------------------------------------------------
# TUTORIAL #
def Refresh(r_mode=['addons', 'repos'], profile_name='default'):
"""
Refresh a number of items in kodi, choose the order they are
executed in by putting first in your r_mode. For example if you
want to refresh addons then repo and then the profile you would
send through a list in the order you want them to be executed.
CODE: Refresh(r_mode, [profile])
AVAILABLE PARAMS:
r_mode - This is the types of "refresh you want to perform",
you can send through just one item or a list of items from the
list below. If you want a sleep between each action just put a
'~' followed by amount of milliseconds after the r_mode. For example
r_mode=['addons~3000', 'repos~2000', 'profile']. This would refresh
the addons, wait 2 seconds then refresh the repos, wait 3 seconds then
reload the profile. The default is set to do a force refresh on
addons and repositories - ['addons', 'repos'].
'addons': This will perform the 'UpdateLocalAddons' command.
'container': This will refresh the contents of the page.
'profile': This will refresh the current profile or if
the profile_name param is set it will load that.
'repos': This will perform the 'UpdateAddonRepos' command.
'skin': This will perform the 'ReloadSkin' command.
profile_name - If you're sending through the option to refresh
a profile it will reload the current running profile by default
but you can pass through a profile name here.
EXAMPLE CODE:
dialog.ok('RELOAD SKIN','We will now attempt to update the addons, pause 3s, update repos and pause 2s then reload the skin. Press OK to continue.')
koding.Refresh(r_mode=['addons~3000', 'repos~2000', 'skin'])
xbmc.sleep(2000)
dialog.ok('COMPLETE','Ok that wasn\'t the best test to perform as you can\'t physically see any visible changes other than the skin refreshing so you\'ll just have to trust us that it worked!')
~"""
from vartools import Data_Type
if profile_name == 'default':
profile_name = Current_Profile()
data_type = Data_Type(r_mode)
if data_type == 'str':
r_mode = [r_mode]
for item in r_mode:
sleeper = 0
if '~' in item:
item, sleeper = item.split('~')
sleeper = int(sleeper)
if item =='addons':
xbmc.executebuiltin('UpdateLocalAddons')
if item =='repos':
xbmc.executebuiltin('UpdateAddonRepos')
if item =='container':
xbmc.executebuiltin('Container.Refresh')
if item =='skin':
xbmc.executebuiltin('ReloadSkin')
if item =='profile':
xbmc.executebuiltin('LoadProfile(%s)' % profile_name)
if sleeper:
xbmc.sleep(sleeper)
#----------------------------------------------------------------
# TUTORIAL #
def Requirements(dependency):
"""
Return the min and max versions of built-in kodi dependencies required by
the running version of Kodi (xbmc.gui, xbmc.python etc.), The return will
be a dictionary with the keys 'min' and 'max'.
CODE: Requirements(dependency)
AVAILABLE PARAMS:
(*) dependency - This is the dependency you want to check.
You can check any built-in dependency which has backwards-compatibility
but the most commonly used are xbmc.gui and xbmc.python.
EXAMPLE CODE:
xbmc_gui = Requirements('xbmc.gui')
xbmc_python = Requirements('xbmc.python')
dialog.ok('DEPENDENCIES','[COLOR=dodgerblue]xbmc.gui[/COLOR] Min: %s Max: %s'%(xbmc_gui['min'],xbmc_gui['max']),'[COLOR=dodgerblue]xbmc.python[/COLOR] Min: %s Max: %s'%(xbmc_python['min'],xbmc_python['max']))
~"""
from filetools import Physical_Path,Text_File
from vartools import Find_In_Text
kodi_ver = xbmc.getInfoLabel("System.BuildVersion")[:2]
# Dictionary used for fallback if local file not accessible (AFTV for example)
defaults = {'15':{'xbmc.gui':['5.3.0','5.9.0'], 'xbmc.python':['2.1.0','2.20.0']}, '16':{'xbmc.gui':['5.10.0','5.10.0'], 'xbmc.python':['2.1.0','2.24.0']}, '17':{'xbmc.gui':['5.12.0','5.12.0'], 'xbmc.python':['2.1.0','2.25.0']}}
root = 'special://xbmc/addons'
dep_path = os.path.join(root,dependency,'addon.xml')
content = Text_File(dep_path,'r')
try:
max_ver = Find_In_Text(content=content,start='version="',end='"')[1]
min_ver = Find_In_Text(content=content,start='abi="',end='"')[0]
except:
xbmc.log(repr(defaults[kodi_ver]),2)
try:
max_ver = defaults[kodi_ver][dependency][1]
min_ver = defaults[kodi_ver][dependency][0]
except:
max_ver = 'unknown'
min_ver = 'unknown'
xbmc.log('%s min: %s'%(dependency,min_ver),2)
xbmc.log('%s max: %s'%(dependency,max_ver),2)
return {'min':min_ver,"max":max_ver}
#----------------------------------------------------------------
# TUTORIAL #
def Running_App():
"""
Return the Kodi app name you're running, useful for fork compatibility
CODE: Running_App()
EXAMPLE CODE:
my_kodi = koding.Running_App()
kodi_ver = xbmc.getInfoLabel("System.BuildVersion")
dialog.ok('KODI VERSION','You are running:','[COLOR=dodgerblue]%s[/COLOR] - v.%s' % (my_kodi, kodi_ver))
~"""
root_folder = xbmc.translatePath('special://xbmc')
xbmc.log(root_folder)
if '/cache' in root_folder:
root_folder = root_folder.split('/cache')[0]
root_folder = root_folder.split('/')
if root_folder[len(root_folder)-1] == '':
root_folder.pop()
finalitem = len(root_folder)-1
running = root_folder[finalitem]
return running
#----------------------------------------------------------------
# TUTORIAL #
def Set_Setting(setting, setting_type='kodi_setting', value = 'true'):
"""
Use this to set built-in kodi settings via JSON or set skin settings.
CODE: Set_Setting(setting, [setting_type, value])
AVAILABLE PARAMS:
setting_type - The type of setting type you want to change. By default
it's set to 'kodi_setting', see below for more info.
AVAILALE VALUES:
'string' : sets a skin string, requires a value.
'bool_true' : sets a skin boolean to true, no value required.
'bool_false' sets a skin boolean to false, no value required.
'kodi_setting' : sets values found in guisettings.xml. Requires
a string of 'true' or 'false' for the value paramater.
'addon_enable' : enables/disables an addon. Requires a string of
'true' (enable) or 'false' (disable) as the value. You will get a
return of True/False on whether successul. Depending on your requirements
you may prefer to use the Toggle_Addons function.
'json' : WIP - setitng = method, value = params, see documentation on
JSON-RPC API here: http://kodi.wiki/view/JSON-RPC_API)
setting - This is the name of the setting you want to change, it could be a
setting from the kodi settings or a skin based setting. If you're wanting
to enable/disable an add-on this is set as the add-on id.
value: This is the value you want to change the setting to. By default this
is set to 'true'.
EXAMPLE CODE:
if dialog.yesno('RSS FEEDS','Would you like to enable or disable your RSS feeds?',yeslabel='ENABLE',nolabel='DISABLE'):
koding.Set_Setting(setting_type='kodi_setting', setting='lookandfeel.enablerssfeeds', value='true')
else:
koding.Set_Setting(setting_type='kodi_setting', setting='lookandfeel.enablerssfeeds', value='false')
~"""
try: import simplejson as json
except: import json
try:
# If the setting_type is kodi_setting we run the command to set the relevant values in guisettings.xml
if setting_type == 'kodi_setting':
setting = '"%s"' % setting
value = '"%s"' % value
query = '{"jsonrpc":"2.0", "method":"Settings.SetSettingValue","params":{"setting":%s,"value":%s}, "id":1}' % (setting, value)
response = xbmc.executeJSONRPC(query)
if 'error' in str(response):
query = '{"jsonrpc":"2.0", "method":"Settings.SetSettingValue","params":{"setting":%s,"value":%s}, "id":1}' % (setting, value.replace('"',''))
response = xbmc.executeJSONRPC(query)
if 'error' in str(response):
xbmc.log('### Error With Setting: %s' % response, 2)
return False
else:
return True
else:
return True
# Set a skin string to <value>
elif setting_type == 'string':
xbmc.executebuiltin('Skin.SetString(%s,%s)' % (setting, value))
# Set a skin setting to true
elif setting_type == 'bool_true':
xbmc.executebuiltin('Skin.SetBool(%s)' % setting)
# Set a skin setting to false
elif setting_type == 'bool_false':
xbmc.executebuiltin('Skin.Reset(%s)' % setting)
# If we're enabling/disabling an addon
elif setting_type == 'addon_enable':
if setting != '':
query = '{"jsonrpc":"2.0", "method":"Addons.SetAddonEnabled","params":{"addonid":"%s", "enabled":%s}, "id":1}' % (setting, value)
response = xbmc.executeJSONRPC(query)
if 'error' in str(response):
xbmc.log('### Error in json: %s'%query,2)
xbmc.log('^ %s' % response, 2)
return False
else:
return True
# If it's none of the above then it must be a json command so we use the setting_type as the method in json
elif setting_type == 'json':
query = '{"jsonrpc":"2.0", "method":"%s","params":{%s}, "id":1}' % (setting, value)
response = xbmc.executeJSONRPC(query)
if 'error' in str(response):
xbmc.log('### Error With Setting: %s' % response,2)
return False
else:
return True
except:
xbmc.log(Last_Error())
#----------------------------------------------------------------
# TUTORIAL #
def Sleep_If_Function_Active(function, args=[], kill_time=30, show_busy=True):
"""
This will allow you to pause code while a specific function is
running in the background.
CODE: Sleep_If_Function_Active(function, args, kill_time, show_busy)
AVAILABLE PARAMS:
function - This is the function you want to run. This does
not require brackets, you only need the function name.
args - These are the arguments you want to send through to
the function, these need to be sent through as a list.
kill_time - By default this is set to 30. This is the maximum
time in seconds you want to wait for a response. If the max.
time is reached before the function completes you will get
a response of False.
show_busy - By default this is set to True so you'll get a busy
working dialog appear while the function is running. Set to
false if you'd rather not have this.
EXAMPLE CODE:
def Open_Test_URL(url):
koding.Open_URL(url)
dialog.ok('SLEEP IF FUNCTION ACTIVE','We will now attempt to read a 20MB zip and then give up after 10 seconds.','Press OK to continue.')
koding.Sleep_If_Function_Active(function=Open_Test_URL, args=['http://download.thinkbroadband.com/20MB.zip'], kill_time=10, show_busy=True)
dialog.ok('FUNCTION COMPLETE','Of course we cannot read that file in just 10 seconds so we\'ve given up!')
~"""
from guitools import Show_Busy
import threading
if show_busy:
Show_Busy(True)
my_thread = threading.Thread(target=function, args=args)
my_thread.start()
thread_alive = True
counter = 0
while thread_alive and counter <= kill_time:
xbmc.sleep(1000)
thread_alive = my_thread.isAlive()
counter += 1
if show_busy:
Show_Busy(False)
return thread_alive
#----------------------------------------------------------------
# TUTORIAL #
def Sleep_If_Window_Active(window_type=10147):
"""
This will allow you to pause code while a specific window is open.
CODE: Sleep_If_Window_Active(window_type)
AVAILABLE PARAMS:
window_type - This is the window xml name you want to check for, if it's
active then the code will sleep until it becomes inactive. By default this
is set to the custom text box (10147). You can find a list of window ID's
here: http://kodi.wiki/view/Window_IDs
EXAMPLE CODE:
koding.Text_Box('EXAMPLE TEXT','This is just an example, normally a text box would not pause code and the next command would automatically run immediately over the top of this.')
koding.Sleep_If_Window_Active(10147) # This is the window id for the text box
dialog.ok('WINDOW CLOSED','The window has now been closed so this dialog code has now been initiated')
~"""
from __init__ import dolog
windowactive = False
counter = 0
if window_type == 'yesnodialog' or window_type == 10100:
count = 30
else:
count = 10
okwindow = False
# Do not get stuck in an infinite loop. Check x amount of times and if condition isn't met after x amount it quits
while not okwindow and counter < count:
xbmc.sleep(100)
okwindow = xbmc.getCondVisibility('Window.IsActive(%s)' % window_type)
counter += 1
# Window is active
while okwindow:
okwindow = xbmc.getCondVisibility('Window.IsActive(%s)' % window_type)
xbmc.sleep(250)
return okwindow
#----------------------------------------------------------------
# TUTORIAL #
def System(command, function=''):
"""
This is just a simplified method of grabbing certain Kodi infolabels, paths
and booleans as well as performing some basic built in kodi functions.
We have a number of regularly used functions added to a dictionary which can
quickly be called via this function or you can use this function to easily
run a command not currently in the dictionary. Just use one of the
many infolabels, builtin commands or conditional visibilities available:
info: http://kodi.wiki/view/InfoLabels
bool: http://kodi.wiki/view/List_of_boolean_conditions
CODE: System(command, [function])
AVAILABLE PARAMS:
(*) command - This is the command you want to perform, below is a list
of all the default commands you can choose from, however you can of course
send through your own custom command if using the function option (details
at bottom of page)
AVAILABLE VALUES:
'addonid' : Returns the FOLDER id of the current add-on. Please note could differ from real add-on id.
'addonname' : Returns the current name of the add-on
'builddate' : Return the build date for the current running version of Kodi
'cpu' : Returns the CPU usage as a percentage
'cputemp' : Returns the CPU temperature in farenheit or celcius depending on system settings
'currentlabel' : Returns the current label of the item in focus
'currenticon' : Returns the name of the current icon
'currentpos' : Returns the current list position of focused item
'currentpath' : Returns the url called by Kodi for the focused item
'currentrepo' : Returns the repo of the current focused item
'currentskin' : Returns the FOLDER id of the skin. Please note could differ from actual add-on id
'date' : Returns the date (Tuesday, April 11, 2017)
'debug' : Toggles debug mode on/off
'freeram' : Returns the amount of free memory available (in MB)
'freespace' : Returns amount of free space on storage in this format: 10848 MB Free
'hibernate' : Hibernate system, please note not all systems are capable of waking from hibernation
'internetstate' : Returns True or False on whether device is connected to internet
'ip' : Return the current LOCAL IP address (not your public IP)
'kernel' : Return details of the system kernel
'language' : Return the language currently in use
'mac' : Return the mac address, will only return the mac currently in use (Wi-Fi OR ethernet, not both)
'numitems' : Return the total amount of list items curently in focus
'profile' : Return the currently running profile name
'quit' : Quit Kodi
'reboot' : Reboot the system
'restart' : Restart Kodi (Windows/Linux only)
'shutdown' : Shutdown the system
'sortmethod' : Return the current list sort method
'sortorder' : Return the current list sort order
'systemname' : Return a clean friendly name for the system
'time' : Return the current time in this format: 2:05 PM
'usedspace' : Return the amount of used space on the storage in this format: 74982 MB Used
'version' : Return the current version of Kodi, this may need cleaning up as it contains full file details
'viewmode' : Return the current list viewmode
'weatheraddon' : Return the current plugin being used for weather
function - This is optional and default is set to a blank string which will
allow you to use the commands listed above but if set you can use your own
custom commands by setting this to one of the values below.
AVAILABLE VALUES:
'bool' : This will allow you to send through a xbmc.getCondVisibility() command
'info' : This will allow you to send through a xbmc.getInfoLabel() command
'exec' : This will allow you to send through a xbmc.executebuiltin() command
EXAMPLE CODE:
current_time = koding.System(command='time')
current_label = koding.System(command='currentlabel')
is_folder = koding.System(command='ListItem.IsFolder', function='bool')
dialog.ok('PULLED DETAILS','The current time is %s' % current_time, 'Folder status of list item [COLOR=dodgerblue]%s[/COLOR]: %s' % (current_label, is_folder),'^ A zero means False, as in it\'s not a folder.')
~"""
params = {
'addonid' :'xbmc.getInfoLabel("Container.PluginName")',
'addonname' :'xbmc.getInfoLabel("Container.FolderName")',
'builddate' :'xbmc.getInfoLabel("System.BuildDate")',
'cpu' :'xbmc.getInfoLabel("System.CpuUsage")',
'cputemp' :'xbmc.getInfoLabel("System.CPUTemperature")',
'currentlabel' :'xbmc.getInfoLabel("System.CurrentControl")',
'currenticon' :'xbmc.getInfoLabel("ListItem.Icon")',
'currentpos' :'xbmc.getInfoLabel("Container.CurrentItem")',
'currentpath' :'xbmc.getInfoLabel("Container.FolderPath")',
'currentrepo' :'xbmc.getInfoLabel("Container.Property(reponame)")',
'currentskin' :'xbmc.getSkinDir()',
'date' :'xbmc.getInfoLabel("System.Date")',
'debug' :'xbmc.executebuiltin("ToggleDebug")',
'freeram' :'xbmc.getFreeMem()',
'freespace' :'xbmc.getInfoLabel("System.FreeSpace")',
'hibernate' :'xbmc.executebuiltin("Hibernate")',
'internetstate' :'xbmc.getInfoLabel("System.InternetState")',
'ip' :'xbmc.getIPAddress()',
'kernel' :'xbmc.getInfoLabel("System.KernelVersion")',
'language' :'xbmc.getInfoLabel("System.Language")',
'mac' :'xbmc.getInfoLabel("Network.MacAddress")',
'numitems' :'xbmc.getInfoLabel("Container.NumItems")',
'profile' :'xbmc.getInfoLabel("System.ProfileName")',
'quit' :'xbmc.executebuiltin("Quit")',
'reboot' :'xbmc.executebuiltin("Reboot")',
'restart' :'xbmc.restart()', # Windows/Linux only
'shutdown' :'xbmc.shutdown()',
'sortmethod' :'xbmc.getInfoLabel("Container.SortMethod")',
'sortorder' :'xbmc.getInfoLabel("Container.SortOrder")',
'systemname' :'xbmc.getInfoLabel("System.FriendlyName")',
'time' :'xbmc.getInfoLabel("System.Time")',
'usedspace' :'xbmc.getInfoLabel("System.UsedSpace")',
'version' :'xbmc.getInfoLabel("System.BuildVersion")',
'viewmode' :'xbmc.getInfoLabel("Container.Viewmode")',
'weatheraddon' :'xbmc.getInfoLabel("Weather.plugin")',
}
if function == '': newcommand = params[command]
elif function == 'info': newcommand = 'xbmc.getInfoLabel("%s")' % command
elif function == 'bool': newcommand = 'xbmc.getCondVisibility("%s")' % command
elif function == 'exec': newcommand = 'xbmc.getCondVisibility("%s")' % command
else:
dialog.ok('INCORRECT PARAMS','The following command has been called:','koding.System(%s,[COLOR=dodgerblue]%s[/COLOR])'%(command, function),'^ The wrong function has been sent through, please double check the section highlighted in blue.')
try:
return eval(newcommand)
except:
return 'error'
#----------------------------------------------------------------
# TUTORIAL #
def Timestamp(mode = 'integer'):
"""
This will return the timestamp in various formats. By default it returns as "integer" mode but other options are listed below:
CODE: Timestamp(mode)
mode is optional, by default it's set as integer
AVAILABLE VALUES:
'integer' - An integer which is nice and easy to work with in Python (especially for
finding out human readable diffs). The format returned is [year][month][day][hour][minutes][seconds].
'epoch' - Unix Epoch format (calculated in seconds passed since 12:00 1st Jan 1970).
'clean' - A clean user friendly time format: Tue Jan 13 10:17:09 2009
'date_time' - A clean interger style date with time at end: 2017-04-07 10:17:09
EXAMPLE CODE:
integer_time = koding.Timestamp('integer')
epoch_time = koding.Timestamp('epoch')
clean_time = koding.Timestamp('clean')
date_time = koding.Timestamp('date_time')
import datetime
installedtime = str(datetime.datetime.now())[:-7]
dialog.ok('CURRENT TIME','Integer: %s' % integer_time, 'Epoch: %s' % epoch_time, 'Clean: %s' % clean_time)
~"""
import time
import datetime
now = time.time()
try:
localtime = time.localtime(now)
except:
localtime = str(datetime.datetime.now())[:-7]
localtime = localtime.replace('-','').replace(':','')
if mode == 'date_time':
return time.strftime('%Y-%m-%d %H:%M:%S', localtime)
if mode == 'integer':
return time.strftime('%Y%m%d%H%M%S', localtime)
if mode == 'clean':
return time.asctime(localtime)
if mode == 'epoch':
return now
#----------------------------------------------------------------

View File

@@ -0,0 +1,169 @@
# -*- coding: utf-8 -*-
# script.module.python.koding.aio
# Python Koding AIO (c) by TOTALREVOLUTION LTD (support@trmc.freshdesk.com)
# Python Koding AIO is licensed under a
# Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License.
# You should have received a copy of the license along with this
# work. If not, see http://creativecommons.org/licenses/by-nc-nd/4.0.
# Please make sure you've read and understood the license, this code can NOT be used commercially
# and it can NOT be modified and redistributed. If you're found to be in breach of this license
# then any affected add-ons will be blacklisted and will not be able to work on the same system
# as any other add-ons which use this code. Thank you for your cooperation.
import os
import re
import sys
import urllib
import xbmc
import xbmcaddon
import xbmcgui
import xbmcplugin
import xbmcvfs
from directory import Add_Dir
from filetools import Text_File
from vartools import Find_In_Text
from guitools import Text_Box, Show_Busy, Keyboard
from systemtools import Sleep_If_Window_Active
from video import Play_Video
from web import Open_URL
dialog = xbmcgui.Dialog()
py_path = 'special://home/addons/script.module.python.koding.aio/lib/koding'
video_base = 'http://totalrevolution.tv/videos/python_koding/'
#----------------------------------------------------------------
def Grab_Tutorials():
""" internal command ~"""
import re
full_array = []
dirs,files = xbmcvfs.listdir(py_path)
# Check all the modules for functions with tutorial info
for file in files:
file_path = os.path.join(py_path,file)
if file.endswith('.py') and file != 'tutorials.py':
content = Text_File(file_path,'r').replace('\r','')
# content_array = re.compile('# TUTORIAL #\ndef (.+?)\(').findall(content)
content_array = Find_In_Text(content=content, start='# TUTORIAL #\ndef ', end='\(', show_errors=False)
if content_array:
for item in content_array:
item = item.strip()
full_array.append('%s~%s'%(item,file_path))
content_array = Find_In_Text(content=content, start='# TUTORIAL #\nclass ', end='\(', show_errors=False)
if content_array:
for item in content_array:
item = item.strip()
full_array.append('%s~%s'%(item,file_path))
# Return a list of tutorials
Add_Dir('[COLOR=gold]CREATE YOUR FIRST ADD-ON[/COLOR]',video_base+'Create_Addon.mov','play_video', folder=False, icon='', fanart='', description='How to create your own add-on using the Python Koding framework.')
for item in sorted(full_array,key=str.lower):
name, filepath = item.split('~')
filepath = urllib.quote(filepath)
Add_Dir(name=name.upper().replace('_',' '), url='%s~%s'%(name,filepath), mode='show_tutorial', folder=False, icon='', fanart='', description='Instructions for how to use the %s function.'%name)
#----------------------------------------------------------------
def Show_Tutorial(url):
""" internal command ~"""
name, filepath = url.split('~')
filepath = urllib.unquote(filepath)
readfile = Text_File(filepath,'r').replace('\r','')
try:
raw_find = Find_In_Text(content=readfile, start='# TUTORIAL #\ndef %s' % name,end='~"""')[0]
except:
raw_find = Find_In_Text(content=readfile, start='# TUTORIAL #\nclass %s' % name,end='~"""')[0]
# Check if an example code segment exists in the comments
if 'EXAMPLE CODE:' in raw_find:
code = re.findall(r'(?<=EXAMPLE CODE:)(?s)(.*$)', raw_find)[0]
code = code.replace('script.module.python.koding.aio','temp_replace_string')
code = code.replace('koding.','').strip()
code = code.replace('temp_replace_string','script.module.python.koding.aio')
else:
code = None
# Check if a video exists in the comments
internetstate = xbmc.getInfoLabel('System.InternetState')
if internetstate:
video_page = Open_URL(video_base)
extension = Find_In_Text(video_page, name, '"', False)
if extension != '' and extension != None:
video = video_base+name+extension[0]
else:
video = None
else:
video = None
counter = 0
removal_string = ''
final_header = ''
newline = ''
temp_raw = raw_find.splitlines()
for line in temp_raw:
if counter == 0:
removal_string += line
if '[' in line:
replace_file = Find_In_Text(content=line,start='\[',end='\]')
for item in replace_file:
line = line.replace(item,'')
if ',' in line:
header_extension = line.split(',')
for item in header_extension:
if '=' in item:
item = item.split('=')[0]
final_header += item+','
final_header = 'koding.'+name+final_header[:-2]+')'
else:
final_header = 'koding.'+name+line[:-1]
else:
removal_string += '\n'+line
counter += 1
if counter == 2:
break
if final_header.endswith('))'):
final_header = final_header[:-1]
if final_header.startswith('koding.User_Info'):
final_header = 'koding.User_Info()'
full_text = raw_find.replace(removal_string,'').strip()
# Initialise the dialog select
dialog_array = ['Documentation']
if code:
dialog_array.append('Run Example Code')
if video:
dialog_array.append('Watch Video')
# If there's more than one item we show a dialog select otherwise we just load up the text window
if len(dialog_array) > 1:
choice = dialog.select(name, dialog_array)
if choice >= 0:
choice = dialog_array[choice]
if choice == 'Documentation':
Text_Box(final_header,full_text
.replace('AVAILABLE PARAMS:','[COLOR=dodgerblue]AVAILABLE PARAMS:[/COLOR]')
.replace('EXAMPLE CODE:','[COLOR=dodgerblue]EXAMPLE CODE:[/COLOR]')
.replace('IMPORTANT:','[COLOR=gold]IMPORTANT:[/COLOR]')
.replace('CODE:','[COLOR=dodgerblue]CODE:[/COLOR]')
.replace('AVAILABLE VALUES:','[COLOR=dodgerblue]AVAILABLE VALUES:[/COLOR]')
.replace('WARNING:','[COLOR=red]WARNING:[/COLOR]'))
elif choice == 'Run Example Code':
codefile = filepath.split(os.sep)
codefile = codefile[len(codefile)-1].replace('.py','')
exec('from %s import *' % codefile)
# exec('from %s import %s' % (codefile, params["name"]))
exec(code)
elif choice == 'Watch Video':
Play_Video(video)
if choice < 0:
return
else:
Text_Box(final_header,full_text
.replace('AVAILABLE PARAMS:','[COLOR=dodgerblue]AVAILABLE PARAMS:[/COLOR]')
.replace('EXAMPLE CODE:','[COLOR=dodgerblue]EXAMPLE CODE:[/COLOR]')
.replace('IMPORTANT:','[COLOR=gold]IMPORTANT:[/COLOR]')
.replace('CODE:','[COLOR=dodgerblue]CODE:[/COLOR]')
.replace('AVAILABLE VALUES:','[COLOR=dodgerblue]AVAILABLE VALUES:[/COLOR]')
.replace('WARNING:','[COLOR=red]WARNING:[/COLOR]'))

View File

@@ -0,0 +1,930 @@
# -*- coding: utf-8 -*-
# script.module.python.koding.aio
# Python Koding AIO (c) by TOTALREVOLUTION LTD (support@trmc.freshdesk.com)
# Python Koding AIO is licensed under a
# Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License.
# You should have received a copy of the license along with this
# work. If not, see http://creativecommons.org/licenses/by-nc-nd/4.0.
# Please make sure you've read and understood the license, this code can NOT be used commercially
# and it can NOT be modified and redistributed. If you're found to be in breach of this license
# then any affected add-ons will be blacklisted and will not be able to work on the same system
# as any other add-ons which use this code. Thank you for your cooperation.
import datetime
import os
import sys
import shutil
import xbmc
import xbmcaddon
import xbmcgui
from filetools import Physical_Path
HOME = Physical_Path('special://home')
#----------------------------------------------------------------
# TUTORIAL #
def ASCII_Check(sourcefile=HOME, dp=False):
"""
Return a list of files found containing non ASCII characters in the filename.
CODE: ASCII_Check([sourcefile, dp])
AVAILABLE PARAMS:
sourcefile - The folder you want to scan, by default it's set to the
Kodi home folder.
dp - Optional DialogProgress, by default this is False. If you want
to show a dp make sure you initiate an instance of xbmcgui.DialogProgress()
and send through as the param.
EXAMPLE CODE:
home = koding.Physical_Path('special://home')
progress = xbmcgui.DialogProgress()
progress.create('ASCII CHECK')
my_return = ASCII_Check(sourcefile=home, dp=progress)
if len(my_return) > 0:
dialog.select('NON ASCII FILES', my_return)
else:
dialog.ok('ASCII CHECK CLEAN','Congratulations!','There weren\'t any non-ASCII files found on this system.')
~"""
rootlen = len(sourcefile)
for_progress = []
final_array = []
ITEM = []
for base, dirs, files in os.walk(sourcefile):
for file in files:
ITEM.append(file)
N_ITEM =len(ITEM)
for base, dirs, files in os.walk(sourcefile):
dirs[:] = [d for d in dirs]
files[:] = [f for f in files]
for file in files:
for_progress.append(file)
progress = len(for_progress) / float(N_ITEM) * 100
if dp:
dp.update(0,"Checking for non ASCII files",'[COLOR yellow]%s[/COLOR]'%d, 'Please Wait')
try:
file.encode('ascii')
except UnicodeDecodeError:
badfile = (str(base)+'/'+str(file)).replace('\\','/').replace(':/',':\\')
final_array.append(badfile)
return final_array
#----------------------------------------------------------------
# TUTORIAL #
def Cleanup_String(my_string):
"""
Clean a string, removes whitespaces and common buggy formatting when pulling from websites
CODE: Cleanup_String(my_string)
AVAILABLE PARAMS:
(*) my_string - This is the main text you want cleaned up.
EXAMPLE CODE:
current_text = '" This is a string of text which should be cleaned up /'
dialog.ok('ORIGINAL STRING', '[COLOR dodgerblue]%s[/COLOR]\n\nPress OK to view the cleaned up version.'%current_text)
clean_text = koding.Cleanup_String(current_text)
dialog.ok('CLEAN STRING', '[COLOR dodgerblue]%s[/COLOR]'%clean_text)
~"""
import urllib
bad_chars = ['/','\\',':',';','"',"'"]
try:
my_string = my_string.encode('utf8')
except:
pass
my_string = urllib.unquote_plus(my_string)
my_string = my_string.replace('<br>','').replace('<br />','').replace('<br/>','')
my_string = my_string.replace('</p>','').replace('</div>','').replace('</class>','')
my_string = my_string.replace('&amp;','&')
if len(my_string) > 4:
if my_string[-4] == '.':
my_string = my_string[:-4]
my_string = my_string.strip()
while my_string[0] in bad_chars or my_string[-1] in bad_chars:
if my_string[-1] in bad_chars:
my_string = my_string[:-1]
if my_string[0] in bad_chars:
my_string = my_string[1:]
my_string = my_string.strip()
return my_string
#----------------------------------------------------------------
# TUTORIAL #
def Colour_Text(text, colour1='dodgerblue',colour2='white'):
"""
Capitalize a string and make the first colour of each string blue and the rest of text white
That's the default colours but you can change to whatever colours you want.
CODE: Colour_Text(text, [color1, color2])
AVAILABLE PARAMS:
(*) text - This is the main text you want to change
colour1 - This is optional and is set as dodgerblue by default.
This is the first letter of each word in the string
colour2 - This is optional and is set as white by default.
This is the colour of the text
IMPORTANT: I use the Queens English so please note the word "colour" has a 'u' in it!
EXAMPLE CODE:
current_text = 'This is a string of text which should be changed to dodgerblue and white with every first letter capitalised'
mytext = koding.Colour_Text(text=current_text, colour1='dodgerblue', colour2='white')
xbmc.log(current_text)
xbmc.log(mytext)
dialog.ok('CURRENT TEXT', current_text)
dialog.ok('NEW TEXT', mytext)
~"""
if text.startswith('[COLOR') and text.endswith('/COLOR]'):
return text
colour_clean = 0
if ' ' in text:
newname = ''
text = text.split(' ')
for item in text:
if len(item)==1 and item == '&':
newname += ' &'
if '[/COLOR]' in item:
newname += ' '+item
elif not item.startswith('[COLOR=') and not colour_clean:
if item.startswith('(') or item.startswith('['):
newname += '[COLOR=yellow] '+item
colour_clean = 1
else:
if item.isupper():
newname += '[COLOR=%s] %s[/COLOR]' % (colour1, item)
else:
try:
newname += '[COLOR=%s] %s[/COLOR][COLOR=%s]%s[/COLOR]' % (colour1, item[0].upper(), colour2, item[1:])
except:
try:
newname += '[COLOR=%s] %s[/COLOR][COLOR=%s][/COLOR]' % (colour1, item[0], colour2, item[1:])
except:
pass
elif item.endswith(')') or item.endswith(']'):
newname += ' '+item+'[/COLOR]'
colour_clean = 0
else:
newname += ' '+item
else:
if text[0] == '(':
newname = '[COLOR=%s]%s[/COLOR][COLOR=%s]%s[/COLOR][COLOR=%s]%s[/COLOR]' % (colour2, text[0], colour1, text[1].upper(), colour2, text[2:])
else:
newname = '[COLOR=%s]%s[/COLOR][COLOR=%s]%s[/COLOR]' % (colour1, text[0], colour2, text[1:])
success = 0
while success != 1:
if newname.startswith(' '):
newname = newname[1:]
success = 1
if newname.startswith('[COLOR=%s] ' % colour1):
newname = '[COLOR=%s]%s' % (colour1, newname[19:])
return newname
#----------------------------------------------------------------
# TUTORIAL #
def Convert_Special(filepath=HOME, string=False, quoted=True):
"""
Convert physcial paths stored in text files to their special:// equivalent or
replace instances of physical paths to special in a string sent through.
CODE: Convert_Special([filepath, string])
AVAILABLE PARAMS:
filepath - This is the path you want to scan, by default it's set to the Kodi HOME directory.
string - By default this is set to False which means it will convert all instances found of
the physical paths to their special equivalent. The scan will convert all instances in all filenames
ending in ini, xml, hash, properties. If you set this value to True you will get a return of your
'filepath' string and no files will be altered.
quoted - By default this is set to true, this means the return you get will be converted
with urllib.quote_plus(). This is ideal if you need to get a string you can send
through as a path for routing.
EXAMPLE CODE:
path = koding.Physical_Path('special://profile')
dialog.ok('ORIGINAL PATH','Let\'s convert this path to it\'s special equivalent:\n[COLOR dodgerblue]%s[/COLOR]'%path)
path = Convert_Special(filepath=path,string=True,quoted=False)
dialog.ok('CONVERTED PATH','This is the converted path:\n[COLOR dodgerblue]%s[/COLOR]'%path)
if dialog.yesno('CONVERT PHYSICAL PATHS','We will now run through your Kodi folder converting all physical paths to their special:// equivalent in xml/hash/properties/ini files.\nDo you want to continue?'):
koding.Convert_Special()
dialog.ok('SUCCESS','Congratulations, all references to your physical paths have been converted to special:// paths.')
~"""
import urllib
from filetools import Text_File
if not string:
for root, dirs, files in os.walk(filepath):
for file in files:
if file.endswith(".xml") or file.endswith(".hash") or file.endswith("properies") or file.endswith(".ini"):
contents = Text_File(os.path.join(root,file), 'r')
encodedpath = urllib.quote(HOME)
encodedpath2 = encodedpath.replace('%3A','%3a').replace('%5C','%5c')
newfile = contents.replace(HOME, 'special://home/').replace(encodedpath, 'special://home/').replace(encodedpath2, 'special://home/')
Text_File(os.path.join(root, file), 'w', newfile)
else:
encodedpath = urllib.quote(HOME)
encodedpath2 = encodedpath.replace('%3A','%3a').replace('%5C','%5c')
newstring = filepath.replace(HOME, 'special://home/').replace(encodedpath, 'special://home/').replace(encodedpath2, 'special://home/')
if quoted:
newstring = urllib.quote_plus(newstring)
return newstring
#----------------------------------------------------------------
# TUTORIAL #
def Data_Type(data):
"""
This will return whether the item received is a dictionary, list, string, integer etc.
CODE: Data_Type(data)
AVAILABLE PARAMS:
(*) data - This is the variable you want to check.
RETURN VALUES:
list, dict, str, int, float, bool
EXAMPLE CODE:
test1 = ['this','is','a','list']
test2 = {"a" : "1", "b" : "2", "c" : 3}
test3 = 'this is a test string'
test4 = 12
test5 = 4.3
test6 = True
my_return = '[COLOR dodgerblue]%s[/COLOR] : %s\n' % (test1, koding.Data_Type(test1))
my_return += '[COLOR dodgerblue]%s[/COLOR] : %s\n' % (test2, koding.Data_Type(test2))
my_return += '[COLOR dodgerblue]%s[/COLOR] : %s\n' % (test3, koding.Data_Type(test3))
my_return += '[COLOR dodgerblue]%s[/COLOR] : %s\n' % (test4, koding.Data_Type(test4))
my_return += '[COLOR dodgerblue]%s[/COLOR] : %s\n' % (test5, koding.Data_Type(test5))
my_return += '[COLOR dodgerblue]%s[/COLOR] : %s\n' % (test6, koding.Data_Type(test6))
koding.Text_Box('TEST RESULTS', my_return)
~"""
data_type = type(data).__name__
return data_type
#----------------------------------------------------------------
# TUTORIAL #
def Decode_String(string):
"""
This will allow you to send a string which contains a variety of special characters (including
non ascii, unicode etc.) and it will convert into a nice clean string which plays nicely
with Python and Kodi.
CODE: Decode_String(string)
AVAILABLE PARAMS:
(*) string - This is the string you want to convert
EXAMPLE CODE:
my_string = 'symbols like [COLOR dodgerblue]¥¨˚∆ƒπø“¬∂≈óõřĖė[/COLOR] can cause errors \nnormal chars like [COLOR dodgerblue]asfasdf[/COLOR] are fine'
dialog.ok('ORIGINAL TEXT',my_string)
my_string = koding.Decode_String(my_string)
dialog.ok('DECODED/STRIPPED',my_string)
~"""
try:
string = string.encode('ascii', 'ignore')
except:
string = string.decode('utf-8').encode('ascii', 'ignore')
return string
#----------------------------------------------------------------
# TUTORIAL #
def Find_In_Text(content, start, end, show_errors = False):
"""
Regex through some text and return a list of matches.
Please note this will return a LIST so even if only one item is found
you will still need to access it as a list, see example below.
CODE: Find_In_Text(content, start, end, [show_errors])
AVAILABLE PARAMS:
(*) content - This is the string to search
(*) start - The start search string
(*) end - The end search string
show_errors - Default is False, if set to True the code will show help
dialogs for bad code.
EXAMPLE CODE:
textsearch = 'This is some text so lets have a look and see if we can find the words "lets have a look"'
dialog.ok('ORIGINAL TEXT','Below is the text we\'re going to use for our search:','[COLOR dodgerblue]%s[/COLOR]'%textsearch)
search_result = koding.Find_In_Text(textsearch, 'text so ', ' and see')
dialog.ok('SEARCH RESULT','You searched for the start string of "text so " and the end string of " and see".','','Your result is: [COLOR dodgerblue]%s[/COLOR]' % search_result[0])
# Please note: we know for a fact there is only one result which is why we're only accessing list item zero.
# If we were expecting more than one return we would probably do something more useful and loop through in a for loop.
~"""
import re
if content == None or content == False:
if show_errors:
dialog.ok('ERROR WITH REGEX','No content sent through - there\'s nothing to scrape. Please check the website address is still active (details at bottom of log).')
xbmc.log(content)
return
if end != '':
links = re.findall('%s([\s\S]*?)%s' % (start, end), content)
if len(links)>0:
return links
else:
if show_errors:
xbmc.log(content)
dialog.ok('ERROR WITH REGEX','Please check your regex, there was content sent through to search but there are no matches for the regex supplied. The raw content has now been printed to the log')
return None
#----------------------------------------------------------------
# TUTORIAL #
def Fuzzy_Search(search_string, search_list, replace_strings=[]):
"""
Send through a list of items and try to match against the search string.
This will match where the search_string exists in the list or an item in
the list exists in the search_string.
CODE: Fuzzy_Search(search_string, search_list, [strip])
AVAILABLE PARAMS:
(*) search_string - This is the string to search for
(*) search_list - The list of items to search through
replace_strings - Optionally send through a list of strings you want to
replace. For example you may want to search for "West Ham United" but in
the list you've sent through they've abbreviated it to "West Ham Utd FC". In
this case we might want to send through a replace_strings list of:
(["united","utd"], ["fc",""])
This will remove any instances of "FC" from the search and it will replace
instances of "united" to "utd". The code will convert everythig to lowercase
so it doesn't matter what case you use in these searches.
EXAMPLE CODE:
my_search = 'west ham utd'
my_list = ['west ham united', 'west ham utd', 'rangers fc', 'Man City', 'West Ham United FC', 'Fulham FC', 'West Ham f.c']
my_replace = (["united","utd"], ["fc",""], ["f.c",""])
dialog.ok('FUZZY SEARCH','Let\'s search for matches similar to "west ham utd" in the list:\n\n%s'%my_list)
search_result = koding.Fuzzy_Search(my_search, my_list, my_replace)
good = ', '.join(search_result)
bad = ''
for item in my_list:
if item not in search_result:
bad += item+', '
dialog.ok('RESULTS FOUND','[COLOR=dodgerblue]SEARCH:[/COLOR] %s\n[COLOR=lime]GOOD:[/COLOR] %s\n[COLOR=cyan]BAD:[/COLOR] %s'%(my_search,good,bad))
~"""
final_array = []
newsearch = search_string.lower().strip().replace(' ','')
for item in replace_strings:
newsearch = newsearch.replace(item[0],item[1])
xbmc.log('newsearch: %s'%newsearch,2)
for item in search_list:
newitem = item.lower().strip().replace(' ','')
for rep in replace_strings:
newitem = newitem.replace(rep[0],rep[1])
xbmc.log('list_item: %s'%newitem,2)
if (newsearch in newitem) or (newitem in newsearch):
final_array.append(item)
if len(final_array)>0:
return final_array
else:
return False
#----------------------------------------------------------------
# TUTORIAL #
def Highest_Version(content=[],start_point='',end_point=''):
"""
Send through a list of strings which all have a common naming structure,
the one with the highest version number will be returned.
CODE: Highest_Version(content,[start_point,end_point])
AVAILABLE PARAMS:
(*) content - This is the list of filenames you want to check.
start_point - If your filenames have a common character/string immediately
before the version number enter that here. For example if you're looking at
online repository/add-on files you would use '-' as the start_point. The version
numbers always appear after the final '-' with add-ons residing on repo's.
end_point - If your version number is followed by a common string (e.g. '.zip')
then enter it in here.
EXAMPLE CODE:
mylist = ['plugin.test-1.0.zip','plugin.test-0.7.zip','plugin.test-1.1.zip','plugin.test-0.9.zip']
dialog.ok('OUR LIST OF FILES', '[COLOR=dodgerblue]%s[/COLOR]\n[COLOR=powderblue]%s[/COLOR]\n[COLOR=dodgerblue]%s[/COLOR]\n[COLOR=powderblue]%s[/COLOR]'%(mylist[0],mylist[1],mylist[2],mylist[3]))
highest = Highest_Version(content=mylist,start_point='-',end_point='.zip')
dialog.ok('HIGHEST VERSION', 'The highest version number of your files is:','[COLOR=dodgerblue]%s[/COLOR]'%highest)
~"""
highest = 0
highest_ver = ''
for item in content:
version = item.replace(end_point,'')
version = version.split(start_point)
version = version[len(version)-1]
if version > highest:
highest = version
highest_ver = item
return highest_ver
#----------------------------------------------------------------
# TUTORIAL #
def ID_Generator(size=15):
"""
This will generate a random string made up of uppercase & lowercase ASCII
characters and digits - it does not contain special characters.
CODE: ID_Generator([size])
size is an optional paramater.
AVAILABLE PARAMS:
size - just send through an integer, this is the length of the string you'll get returned.
So if you want a password generated that's 20 characters long just use ID_Generator(20). The default is 15.
EXAMPLE CODE:
my_password = koding.ID_Generator(20)
dialog.ok('ID GENERATOR','Password generated:', '', '[COLOR=dodgerblue]%s[/COLOR]' % my_password)
~"""
import string
import random
chars=string.ascii_uppercase + string.digits + string.ascii_lowercase
return ''.join(random.choice(chars) for _ in range(size))
#---------------------------------------------------------------------------------------------------
# TUTORIAL #
def List_From_Dict(mydict={},use_key=True):
"""
Send through a dictionary and return a list of either the keys or values.
Please note: The returned list will be sorted in alphabetical order.
CODE: List_From_Dict(mydict,[use_key])
AVAILABLE PARAMS:
(*) mydict - This is the dictionary (original data) you want to traverse through.
use_key - By default this is set to True and a list of all your dictionary keys
will be returned. Set to False if you'd prefer to have a list of the values returned.
EXAMPLE CODE:
raw_data = {'test1':'one','test2':'two','test3':'three','test4':'four','test5':'five'}
mylist1 = koding.List_From_Dict(mydict=raw_data)
mylist2 = koding.List_From_Dict(mydict=raw_data,use_key=False)
koding.Text_Box('LIST_FROM_DICT','Original dictionary: [COLOR dodgerblue]%s[/COLOR][CR][CR]Returned List (use_key=True): [COLOR dodgerblue]%s[/COLOR][CR]Returned List (use_key=False): [COLOR dodgerblue]%s[/COLOR]'%(raw_data,mylist1,mylist2))
~"""
pos = 1
if use_key:
pos = 0
final_list = []
for item in mydict.items():
if item[0]!='' and item[1]!='':
final_list.append(item[pos])
return sorted(final_list)
#---------------------------------------------------------------------------------------------------
# TUTORIAL #
def md5_check(src,string=False):
"""
Return the md5 value of string/file/directory, this will return just one unique value.
CODE: md5_check(src,[string])
AVAILABLE PARAMS:
(*) src - This is the source you want the md5 value of.
This can be a string, path of a file or path to a folder.
string - By default this is set to False but if you want to send
through a string rather than a path set this to True.
EXAMPLE CODE:
home = koding.Physical_Path('special://home')
home_md5 = koding.md5_check(home)
dialog.ok('md5 Check', 'The md5 of your home folder is:', '[COLOR=dodgerblue]%s[/COLOR]'%home_md5)
guisettings = xbmc.translatePath('special://profile/guisettings.xml')
guisettings_md5 = koding.md5_check(guisettings)
dialog.ok('md5 Check', 'The md5 of your guisettings.xml:', '[COLOR=dodgerblue]%s[/COLOR]'%guisettings_md5)
mystring = 'This is just a random text string we\'ll get the md5 value of'
myvalue = koding.md5_check(src=mystring,string=True)
dialog.ok('md5 String Check', 'String to get md5 value of:', '[COLOR=dodgerblue]%s[/COLOR]'%mystring)
dialog.ok('md5 String Check', 'The md5 value of your string:', '[COLOR=dodgerblue]%s[/COLOR]'%myvalue)
~"""
import hashlib
import os
SHAhash = hashlib.md5()
if not os.path.exists(src) and not string:
return -1
# If source is a file
if string:
return hashlib.md5(src).hexdigest()
# If source is a file
elif not os.path.isdir(src):
return hashlib.md5(open(src,'rb').read()).hexdigest()
# If source is a directory
else:
try:
for root, dirs, files in os.walk(src):
for names in files:
filepath = os.path.join(root,names)
try:
f1 = open(filepath, 'rb')
except:
f1.close()
continue
while 1:
# Read file in as little chunks
buf = f1.read(4096)
if not buf : break
SHAhash.update(hashlib.md5(buf).hexdigest())
f1.close()
except:
return -2
return SHAhash.hexdigest()
#----------------------------------------------------------------
# TUTORIAL #
def Merge_Dicts(*dict_args):
"""
Send through any number of dictionaries and get a return of one merged dictionary.
Please note: If you have duplicate keys the value will be overwritten by the final
dictionary to be checked. So if you send through dicts a-f and the same key exists
in dicts a,e,f the final value for that key would be whatever is set in 'f'.
CODE: Merge_Dicts(*dict_args)
AVAILABLE PARAMS:
(*) *dict_args - Enter as many dictionaries as you want, these will be merged
into one final dictionary. Please send each dictionary through as a new paramater.
EXAMPLE CODE:
dict1 = {'1':'one','2':'two'}
dict2 = {'3':'three','4':'four','5':'five'}
dict3 = {'6':'six','7':'seven'}
dict4 = {'1':'three','8':'eight'}
mytext = 'Original Dicts:\ndict1 = %s\ndict2 = %s\ndict3 = %s\ndict4 = %s\n\n'%(repr(dict1),repr(dict2),repr(dict3),repr(dict4))
mytext += 'Merged dictionaries (1-3): %s\n\n'%repr(koding.Merge_Dicts(dict1,dict2,dict3))
mytext += 'Merged dictionaries (1-4): %s\n\n'%repr(koding.Merge_Dicts(dict1,dict2,dict3,dict4))
mytext += "[COLOR = gold]IMPORTANT:[/COLOR]\nNotice how on the last run the key '1'now has a value of three.\nThis is because dict4 also contains that same key."
Text_Box('Merge_Dicts',mytext)
~"""
result = {}
for dictionary in dict_args:
if Data_Type(dictionary)=='dict':
result.update(dictionary)
return result
#----------------------------------------------------------------
# TUTORIAL #
def Parse_XML(source, block, tags):
"""
Send through the contents of an XML file and pull out a list of matching
items in the form of dictionaries. When checking your results you should
allow for lists to be returned, by default each tag found in the xml will
be returned as a string but if multiple entries of the same tag exists your
dictionary item will be a list. Although this can be used for many uses this
was predominantly added for support of XML's which contain multiple links to video
files using things like <sublink>. When checking to see if a string or list has been
returned you can use the Data_Type function from Koding which will return 'str' or 'list'.
CODE: Parse_XML(source, block, tags)
AVAILABLE PARAMS:
source - This is the original source file, this must already be read into
memory as a string so made sure you've either used Open_URL or Text_File to
read the contents before sending through.
block - This is the master tag you want to use for creating a dictionary of items.
For example if you have an xml which contains multiple tags called <item> and you wanted
to create a dictionary of sub items found in each of these you would just use 'item'.
tags - This is a list of tags you want to return in your dictionary, so lets say each <item>
section contains <link>, <title> and <thumb> tags you can return a dictionary of all those
items by sending through ['link','title','thumb']
EXAMPLE CODE:
dialog.ok('DICTIONARY OF ITEMS','We will now attempt to return a list of the source details pulled from the official Kodi repository addon.xml')
xml_file = koding.Physical_Path('special://xbmc/addons/repository.xbmc.org/addon.xml')
xml_file = koding.Text_File(xml_file,'r')
xbmc.log(xml_file,2)
repo_details = koding.Parse_XML(source=xml_file, block='extension', tags=['info','checksum','datadir'])
counter = 0
for item in repo_details:
dialog.ok( 'REPO %s'%(counter+1),'info path: [COLOR dodgerblue]%s[/COLOR]\nchecksum path: [COLOR dodgerblue]%s[/COLOR]\ndatadir: [COLOR dodgerblue]%s[/COLOR]' % (repo_details[counter]['info'],repo_details[counter]['checksum'],repo_details[counter]['datadir']) )
counter += 1
~"""
from BeautifulSoup import BeautifulSoup
soup = BeautifulSoup(source)
my_return = []
# Grab all the blocks of xml to search
for myblock in soup.findAll(block):
if myblock:
my_dict = {}
for tag in tags:
newsoup = BeautifulSoup(str(myblock))
newtag = newsoup.findAll(tag)
if newtag:
xbmc.log(repr(newtag),2)
# If only one instance is found we add to dict as a plain string
if len(newtag)==1:
newtag = str(newtag).split(r'>')[1]
newtag = newtag.split(r'<')[0]
# Otherwise we add to dict as a list
else:
tag_array = []
for item in newtag:
mynewtag = str(item).split(r'>')[1]
mynewtag = mynewtag.split(r'<')[0]
tag_array.append(mynewtag)
newtag = tag_array
my_dict[tag] = newtag
my_return.append(my_dict)
return my_return
#----------------------------------------------------------------
# TUTORIAL #
def Table_Convert(url, contents={}, table=0):
"""
Open a web page which a table and pull out the contents of that table
into a list of dictionaries with your own custom keys.
CODE: Table_Convert(url, contents, table)
AVAILABLE PARAMS:
url - The url you want to open and pull data from
contents - Send through a dictionary of the keys you want to assign to
each of the cells. The format would be: {my_key : position}
You can pull out as many cells as you want, so if your table has 10 columns
but you only wanted to pull data for cells 2,4,5,6,8 then you could do so
by setting contents to the following params:
contents = {"name 1":2, "name 2":4, "name 3":5, "name 4":6, "name 5":8}
table - By default this is set to zero, this is to be used if there's
multiple tables on the page you're accessing. Remeber to start at zero,
so if you want to access the 2nd table on the page it will be table=1.
EXAMPLE CODE:
dialog.ok('TABLE DATA','Let\'s pull some details from the proxy list table found at:\nhttps://free-proxy-list.net.')
proxies = koding.Table_Convert(url='https://free-proxy-list.net', contents={"ip":0,"port":1}, table=0)
mytext = '[COLOR dodgerblue]Here are some proxies:[/COLOR]\n'
for item in proxies:
mytext += '\nIP: %s\nPort: %s\n[COLOR steelblue]----------------[/COLOR]'%(item['ip'],item['port'])
koding.Text_Box('MASTER PROXY LIST',mytext)
~"""
from web import Open_URL
from BeautifulSoup import BeautifulSoup
table_list=[]
content = Open_URL(url)
if content:
rawdata = Parse_XML(content,'table',['td'])
# Work out the amount of columns in the table
soup = BeautifulSoup(content)
my_return = []
mytable = soup.findAll('table')[table]
if mytable:
newsoup = BeautifulSoup(str(mytable))
newtag = str( newsoup.find('tr') )
if '<th' in newtag:
count_tag = '<th'
else:
count_tag = '<td'
cells = newtag.count(count_tag)
rows = [rawdata[table]['td'][x:x+cells] for x in range(0, len(rawdata[0]['td']), cells)]
for row in rows:
my_dict = {}
for cell_name in contents:
my_dict[cell_name] = row[contents[cell_name]]
table_list.append(my_dict)
return table_list
else:
return {}
#----------------------------------------------------------------
# TUTORIAL #
def Split_Lines(raw_string, size):
"""
Splits up a piece of text into a list of lines x amount of chars in length.
CODE: koding.Split_Lines(raw_string, size)
AVAILABLE PARAMS:
(*) raw_string - This is the text you want split up into lines
(*) size - This is the maximum size you want the line length to be (in characters)
EXAMPLE CODE:
raw_string = 'This is some test code, let\'s take a look and see what happens if we split this up into lines of 20 chars per line'
dialog.ok('ORIGINAL TEXT',raw_string)
my_list = koding.Split_Lines(raw_string,20)
koding.Text_Box('List of lines',str(my_list))
~"""
final_list=[""]
for i in raw_string:
length = len(final_list)-1
if len(final_list[length]) < size:
final_list[length]+=i
else:
final_list += [i]
return final_list
#----------------------------------------------------------------
# TUTORIAL #
def Split_List(source, split_point, include='all'):
"""
Send through a list and split it up into multiple lists. You can choose to create
lists of every x amount of items or you can split at every nth item and only include
specific items in your new list.
CODE: Split_List(source, split_point, include)
AVAILABLE PARAMS:
source - This is the original list you want split
split_point - This is the postition you want to split your list at. For example:
original list: [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15]
Lets say we want to split it at every 5 items the split point would be 5
include - You have 3 options here:
'all' - This will add all items to your lists, so in the example above you will
get a return of ([1,2,3,4,5],[6,7,8,9,10],[11,12,13,14,15])
[] - Send through a list of positions you want to include, based on the example
above and using include=[0,1,3] you will get a return of ([1,2,4],[6,7,9],[11,12,14])
int - Send through an integer and it will return everything up to that position,
based on the example above and using include=3 you will get a return of
([1,2,3],[6,7,8],[11,12,13])
EXAMPLE CODE:
my_list = ['one','two','three','four','five','six','seven','eight','nine','ten','eleven','twelve','thirteen','fourteen','fifteen']
dialog.ok('SPLIT LIST 1','We will now attempt to split the following list into blocks of 5:\n%s'%my_list)
newlist = koding.Split_List(source=my_list, split_point=5)
dialog.ok('RESULTS','Our returned var:\n%s'%newlist)
dialog.ok('SPLIT LIST 2','We will now attempt to split the same list at every 5th item position and only show items [0,1,3]')
newlist = koding.Split_List(source=my_list, split_point=5, include=[0,1,3])
dialog.ok('RESULTS','Our returned var:\n%s'%newlist)
dialog.ok('SPLIT LIST 3','We will now attempt to split the same list at every 5th item position and only show the first 3 items.')
newlist = koding.Split_List(source=my_list, split_point=5, include=3)
dialog.ok('RESULTS','Our returned var:\n%s'%newlist)
~"""
if include == 'all':
return [source[x:x+split_point] for x in range(0, len(source), split_point)]
elif Data_Type(include) == 'list':
mylists = [source[x:x+split_point] for x in range(0, len(source), split_point)]
return_list = []
for item in mylists:
mylist = []
for keep in include:
mylist.append(item[keep])
return_list.append(mylist)
return return_list
elif Data_Type(include) == 'int':
return [source[x:x+include] for x in range(0, len(source), split_point)]
#----------------------------------------------------------------
# TUTORIAL #
def String(code='', source=''):
"""
This will return the relevant language skin as set in the
resources/language folder for your add-on. By default you'll get
the language string returned from your current running add-on
but if you send through another add-on id you can grab from
any add-on or even the built-in kodi language strings.
CODE: String(code, [source])
AVAILABLE PARAMS:
(*) code - This is the language string code set in your strings.po file.
source - By default this is set to a blank string and will
use your current add-on id. However if you want to pull the string
from another add-on just enter the add-on id in here. If you'd prefer
to pull from the built-in kodi resources files just set as 'system'.
EXAMPLE CODE:
kodi_string = koding.String(code=10140, source='system')
koding_string = koding.String(code=30825, source='script.module.python.koding.aio')
dialog.ok('SYSTEM STRING','The string [COLOR=dodgerblue]10140[/COLOR] pulled from the default system language resources is:','[COLOR=gold]%s[/COLOR]' % kodi_string)
dialog.ok('PYTHON KODING STRING','The string [COLOR=dodgerblue]30825[/COLOR] pulled from the Python Koding language resources is:','[COLOR=gold]%s[/COLOR]' % koding_string)
~"""
import xbmcaddon
from addons import Caller
if source == '':
source = Caller()
if source != 'system':
addon_id = xbmcaddon.Addon(id=source)
mystring = addon_id.getLocalizedString(code)
else:
mystring = xbmc.getLocalizedString(code)
return mystring
#----------------------------------------------------------------
# TUTORIAL #
def Remove_Formatting(string, color=True, bold=True, italic=True, spaces=True, dots=True, dashes=True):
"""
This will cleanup a Kodi string, it can remove color, bold and italic tags as well as
preceding spaces, dots and dashes. Particularly useful if you want to show the names of
add-ons in alphabetical order where add-on names have deliberately had certain formatting
added to them to get them to always show at the top of lists.
CODE: Remove_Formatting(string, [color, bold, italic, spaces, dots, dashes])
AVAILABLE PARAMS:
(*) string - This is string you want to remove formatting from.
color - By default this is set to true and all references to the color tag
will be removed, set this to false if you don't want color formatting removed.
bold - By default this is set to true and all references to the bold tag
will be removed, set this to false if you don't want bold formatting removed.
italic - By default this is set to true and all references to the italic tag
will be removed, set this to false if you don't want italic formatting removed.
spaces - By default this is set to true and any spaces at the start of the text
will be removed, set this to false if you don't want the spaces removed.
dots - By default this is set to true and any dots (.) at the start of the text
will be removed, set this to false if you don't want the dots removed.
dashes - By default this is set to true and any dashes (-) at the start of the text
will be removed, set this to false if you don't want the dashes removed.
EXAMPLE CODE:
mystring = '...-- [I]This[/I] is the [COLOR dodgerblue]ORIGINAL[/COLOR] [B][COLOR cyan]TEXT[/COLOR][/B]'
dialog.ok('ORIGINAL TEXT','Below is the original text we\'re going to try and clean up:[CR]%s'%mystring)
dialog.ok('DOTS REMOVED','[COLOR gold]Original:[/COLOR][CR]%s[CR][COLOR gold]This is with only dots set to True:[/COLOR][CR]%s'%(mystring,koding.Remove_Formatting(mystring, color=False, bold=False, italic=False, spaces=False, dots=True, dashes=False)))
dialog.ok('DOTS & DASHES REMOVED','[COLOR gold]Original:[/COLOR][CR]%s[CR][COLOR gold]This is with dots & dashes set to True:[/COLOR][CR]%s'%(mystring,koding.Remove_Formatting(mystring, color=False, bold=False, italic=False, spaces=False, dots=True, dashes=True)))
dialog.ok('DOTS, DASHES & SPACES REMOVED','[COLOR gold]Original:[/COLOR][CR]%s[CR][COLOR gold]This is with dots, dashes & spaces set to True:[/COLOR][CR]%s'%(mystring,koding.Remove_Formatting(mystring, color=False, bold=False, italic=False, spaces=True, dots=True, dashes=True)))
dialog.ok('ALL FORMATTING REMOVED','[COLOR gold]Original:[/COLOR][CR]%s[CR][COLOR gold]This is with all options set to True:[/COLOR][CR]%s'%(mystring,koding.Remove_Formatting(mystring)))
~"""
import re
if color:
if '[COLOR' in string:
string = string.replace('[/COLOR]','')
colorlist = re.compile(r'\[COLOR(.+?)\]').findall(string)
for colors in colorlist:
string = string.replace('[COLOR%s]'%colors,'')
if spaces:
string = string.strip()
if bold:
string = string.replace('[B]','').replace('[/B]','')
if spaces:
string = string.strip()
if italic:
string = string.replace('[I]','').replace('[/I]','')
if spaces:
string = string.strip()
if dots:
while string.startswith('.'):
string = string[1:]
if spaces:
string = string.strip()
if dashes:
while string.startswith('-'):
string = string[1:]
if spaces:
string = string.strip()
if spaces:
string = string.strip()
return string

View File

@@ -0,0 +1,711 @@
# -*- coding: utf-8 -*-
# script.module.python.koding.aio
# Python Koding AIO (c) by TOTALREVOLUTION LTD (support@trmc.freshdesk.com)
# Python Koding AIO is licensed under a
# Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License.
# You should have received a copy of the license along with this
# work. If not, see http://creativecommons.org/licenses/by-nc-nd/4.0.
# Please make sure you've read and understood the license, this code can NOT be used commercially
# and it can NOT be modified and redistributed. If you're found to be in breach of this license
# then any affected add-ons will be blacklisted and will not be able to work on the same system
# as any other add-ons which use this code. Thank you for your cooperation.
import os
import requests
import shutil
import xbmc
import xbmcgui
from guitools import Show_Busy
from systemtools import Last_Error
dp = xbmcgui.DialogProgress()
check_started = xbmc.translatePath('special://profile/addon_data/script.module.python.koding.aio/temp/playback_in_progress')
#----------------------------------------------------------------
# TUTORIAL #
def Check_Playback(ignore_dp=False,timeout=10):
"""
This function will return true or false based on video playback. Simply start a stream
(whether via an add-on, direct link to URL or local storage doesn't matter), the code will
then work out if playback is successful. This uses a number of checks and should take into
account all potential glitches which can occur during playback. The return should happen
within a second or two of playback being successful (or not).
CODE: Check_Playback()
AVAILABLE PARAMS:
ignore_dp - By default this is set to True but if set to False
this will ignore the DialogProgress window. If you use a DP while
waiting for the stream to start then you'll want to set this True.
Please bare in mind the reason this check is in place and enabled
by default is because some streams do bring up a DialogProgress
when initiated (such as f4m proxy links) and disabling this check
in those circumstances can cause false positives.
timeout - This is the amount of time you want to allow for playback
to start before sending back a response of False. Please note if
ignore_dp is set to True then it will also add a potential 10s extra
to this amount if a DialogProgress window is open. The default setting
for this is 10s.
EXAMPLE CODE:
xbmc.Player().play('http://totalrevolution.tv/videos/python_koding/Browse_To_Folder.mov')
isplaying = koding.Check_Playback()
if isplaying:
dialog.ok('PLAYBACK SUCCESSFUL','Congratulations, playback was successful')
xbmc.Player().stop()
else:
dialog.ok('PLAYBACK FAILED','Sorry, playback failed :(')
~"""
if not ignore_dp:
isdialog = True
counter = 1
# Check if the progress window is active and wait for playback
while isdialog and counter < 60:
if xbmc.getCondVisibility('Window.IsActive(progressdialog)'):
try:
if dp.iscanceled():
dp.close()
break
except:
pass
xbmc.log('### Current Window: %s' % xbmc.getInfoLabel('System.CurrentWindow'))
xbmc.log('### Current XML: %s' % xbmc.getInfoLabel('Window.Property(xmlfile)'))
xbmc.log('### Progress Dialog active, sleeping for %s seconds' % counter)
xbmc.sleep(1000)
if xbmc.getCondVisibility('Window.IsActive(progressdialog)') or (xbmc.getInfoLabel('Window.Property(xmlfile)') == 'DialogProgress.xml'):
isdialog = True
else:
isdialog = False
counter += 1
xbmc.log('counter: %s' % counter)
# Given the DialogProgress 10 seconds to finish and it's still up - time to close it
if counter >= 10:
try:
xbmc.log('attempting to send click to close dp')
xbmc.executebuiltin('SendClick()')
if dp.iscanceled():
dp.close()
try:
dp.close()
except:
pass
except:
xbmc.log('### FAILED TO CLOSE DP')
try:
dp.close()
except:
pass
isplaying = xbmc.Player().isPlaying()
counter = 1
if xbmc.Player().isPlayingAudio():
return True
# If xbmc player is not yet active give it some time to initialise
while not isplaying and counter < timeout:
xbmc.sleep(1000)
isplaying = xbmc.Player().isPlaying()
xbmc.log('### XBMC Player not yet active, sleeping for %s seconds' % counter)
counter += 1
success = 0
counter = 0
# If it's playing give it time to physically start streaming then attempt to pull some info
if isplaying:
xbmc.sleep(1000)
while not success and counter < 5:
try:
if xbmc.Player().isPlayingVideo():
infotag = xbmc.Player().getVideoInfoTag()
vidtime = xbmc.Player().getTime()
if vidtime > 0:
success = 1
# If playback doesn't start automatically (buffering) we force it to play
else:
xbmc.log('### Playback active but time at zero, trying to unpause')
xbmc.executebuiltin('PlayerControl(Play)')
xbmc.sleep(2000)
vidtime = xbmc.Player().getTime()
if vidtime > 0:
success = 1
# If no infotag or time could be pulled then we assume playback failed, try and stop the xbmc.player
except:
counter += 1
xbmc.sleep(1000)
# Check if the busy dialog is still active from previous locked up playback attempt
isbusy = xbmc.getCondVisibility('Window.IsActive(busydialog)')
counter = 1
while isbusy:
xbmc.log('### Busy dialog active, sleeping for %ss' % counter)
xbmc.sleep(1000)
isbusy = xbmc.getCondVisibility('Window.IsActive(busydialog)')
counter += 1
if counter >= 5:
xbmc.executebuiltin('Dialog.Close(busydialog)')
if not success:
xbmc.executebuiltin('PlayerControl(Stop)')
xbmc.log('### Failed playback, stopped stream')
return False
else:
return True
#----------------------------------------------------------------
# TUTORIAL #
def Last_Played():
"""
Return the link of the last played (or currently playing) video.
This differs to the built in getPlayingFile command as that only shows details
of the current playing file, these details can differ to the url which was
originally sent through to initiate the stream. This Last_Played function
directly accesses the database to get the REAL link which was initiated and
will even return the plugin path if it's been played through an external add-on.
CODE: Last_Played()
EXAMPLE CODE:
if koding.Play_Video('http://totalrevolution.tv/videos/python_koding/Browse_To_Folder.mov'):
xbmc.sleep(3000)
xbmc.Player().stop()
last_vid = Last_Played()
dialog.ok('VIDEO LINK','The link we just played is:\n\n%s'%last_vid)
else:
dialog.ok('PLAYBACK FAILED','Sorry this video is no longer available, please try using a different video link.')
~"""
from database import DB_Query
from filetools import DB_Path_Check
from vartools import Decode_String
db_path = DB_Path_Check('MyVideos')
sql = "SELECT files.strFilename as mystring, path.strPath as mybase FROM files JOIN path ON files.idPath=path.idPath ORDER BY files.lastPlayed DESC LIMIT 1"
results = DB_Query(db_path, sql)
try:
if Decode_String(results[0]['mybase']).startswith('plugin://'):
return Decode_String(results[0]['mystring'])
else:
return Decode_String(results[0]['mybase']+results[0]['mystring'])
except:
return False
#----------------------------------------------------------------
# TUTORIAL #
def Link_Tester(video='', local_check=True, proxy_list=None, proxy_url='https://free-proxy-list.net/', ip_col=0, port_col=1, table=0):
"""
Send through a link and test whether or not it's playable on other devices.
Many links include items in the query string which lock the content down to your
IP only so what may open fine for you may not open for anyone else!
This function will attempt to load the page using a proxy. If when trying to access
the link via a proxy the header size and content-type match then we assume the
link will play on any device. This is not fool proof and could potentially return
false positives depending on the security used on the website being accessed.
The return you'll get is a dictionary of the following items:
'plugin_path' - This will have the path for a plugin, it means the stream was
originally passed through an add-on to get the final link. If this is not set
to None then it "should" work on any device so long as that add-on is installed
(e.g. YouTube).
'url' - This is the final resolved url which Kodi was playing, you need to check
the status though to find out whether or not that link is locked to your IP only.
'status' - This will return one of the following status codes:
good - The link should work on all IPs.
bad_link - The link was not valid, won't even play on your current Kodi setup.
proxy_fail - None of the proxies sent through worked.
locked - The url only works on this device, if this is the case consider using
the plugin_path which should generally work on all devices (although this does
depend on how the developer of that add-on coded up their add-on).
CODE: Link_Tester([proxy_list, url, ip_col, port_col, table])
AVAILABLE PARAMS:
video - This is the url of the video you want to check
local_check - By default this is set to True and this function will first of
all attempt to play the video locally with no proxy just to make sure the
link is valid in the first place. If you want to skip this step then set
this to False.
proxy_list - If you already have a list of proxies you want to test with
send them through in the form of a list of dictionaries. Use the following
format: [{"ip":"0.0.0.0","port":"80"},{"ip":"127.0.0.1","port":"8080"}]
proxy_url - If you want to scrape for online proxies and loop through until a
working one has been found you can set the url here. If using this then
proxy_list can be left as the default (None). If you open this Link_Tester
function with no params the defaults are setup to grab from:
free-proxy-list.net but there is no guarantee this will always
work, the website may well change it's layout/security over time.
ip_col - If you've sent through a proxy_url then you'll need to set a column number
for where in the table the IP address is stored. The default is 0
port_col - If you've sent through a proxy_url then you'll need to set a column number
for where in the table the port details are stored. The default is 1
table - If you've sent through a proxy_url then you'll need to set a table number.
The default is 0 - this presumes we need to use the first html table found on the
page, if you require a different table then alter accordingly - remember zero is the
first instance so if you want the 3rd table on the page you would set to 2.
EXAMPLE CODE:
vid_test = Link_Tester(video='http://totalrevolution.tv/videos/python_koding/Browse_To_Folder.mov')
if vid_test['status'] == 'bad_link':
dialog.ok('BAD LINK','The link you sent through cannot even be played on this device let alone another one!')
elif vid_test['status'] == 'proxy_fail':
dialog.ok('PROXIES EXHAUSTED','It was not possible to get any working proxies as a result it\'s not possible to fully test whether this link will work on other devices.')
elif vid_test['status'] == 'locked':
dialog.ok('NOT PLAYABLE','Although you can play this link locally the tester was unable to play it when using a proxy so this is no good.')
if vid_test['plugin_path']:
dialog.ok('THERE IS SOME GOOD NEWS!','Although the direct link for this video won\'t work on other IPs it "should" be possible to open this using the following path:\n[COLOR dodgerblue]%s[/COLOR]'%vid_test['plugin_path'])
else:
dialog.ok('WORKING!!!','Congratulations this link can be resolved and added to your playlist.')
~"""
import random
import urllib
from guitools import Notify
from vartools import Table_Convert
from systemtools import System
# xbmc.executebuiltin('RunScript(special://home/addons/script.module.python.koding.aio/lib/koding/localproxy.py)')
Notify('PLEASE WAIT','Checking Link - Step 1','5000','Video.png')
isplaying = xbmc.Player().isPlaying()
# If video not yet playing try playing it
if not isplaying:
xbmc.Player().play(video)
if Check_Playback(True):
xbmclink = xbmc.Player().getPlayingFile()
active_plugin = System(command='addonid')
plugin_path = System(command='currentpath')
vid_title = ''
title_count = 0
while vid_title == '' and title_count < 10:
vid_title = xbmc.getInfoLabel('Player.Title')
xbmc.sleep(100)
title_count += 1
xbmc.Player().stop()
video_orig = Last_Played()
xbmc.log('VIDEO: %s'%video_orig,2)
if video_orig.startswith('plugin://'):
video = xbmclink
xbmc.log('NEW VIDEO: %s'%video,2)
else:
video = video_orig
r = requests.head(url=video, timeout=5)
orig_header = r.headers
try:
orig_size = orig_header['Content-Length']
except:
orig_size = 0
try:
orig_type = orig_header['Content-Type']
except:
orig_type = ''
proxies = Table_Convert(url=proxy_url, contents={"ip":ip_col,"port":port_col}, table=table)
myproxies = []
used_proxies = []
for item in proxies:
myproxies.append({'http':'http://%s:%s'%(item['ip'],item['port']),'https':'https://%s:%s'%(item['ip'],item['port'])})
success = False
if video_orig.startswith('plugin://'):
dp.create('[COLOR gold]CHECKING PROXIES[/COLOR]','This video is being parsed through another add-on so using the plugin path should work. Now checking the final resolved link...','')
else:
dp.create('[COLOR gold]CHECKING PROXIES[/COLOR]','Please wait...','')
counter = 1
while (not success) and (len(myproxies) > 0):
dp.update(counter/len(myproxies),'Checking proxy %s'%counter)
counter += 1
proxychoice = random.choice( range(0,len(myproxies)) )
currentproxy = myproxies[proxychoice]
# Find a working proxy and play the video through it
try:
xbmc.log(repr(currentproxy),2)
r = requests.head(url=video, proxies=currentproxy, timeout=5)
headers = r.headers
try:
new_size = headers['Content-Length']
except:
new_size = 0
try:
new_type = headers['Content-Type']
except:
new_type = ''
xbmc.log('orig size: %s'%orig_size,2)
xbmc.log('new size: %s'%new_size,2)
xbmc.log('orig type: %s'%orig_type,2)
xbmc.log('new type: %s'%new_type,2)
xbmc.log('VIDEO: %s'%video,2)
if orig_size != 0 and (orig_size==new_size) and (orig_type==new_type):
dp.close()
success = True
except:
xbmc.log('failed with proxy: %s'%currentproxy,2)
myproxies.pop(proxychoice)
if dp.iscanceled():
dp.close()
break
plugin_path = None
if video_orig.startswith('plugin://'):
plugin_path = video_orig
if len(myproxies)==0 and not success:
return {"plugin_path":plugin_path, "url":video, "status":"proxy_fail"}
elif not success:
return {"plugin_path":plugin_path, "url":video, "status":"locked"}
else:
return {"plugin_path":plugin_path, "url":video, "status":"good"}
else:
return {"plugin_path":None, "url":video, "status":"bad_link"}
#----------------------------------------------------------------
# TUTORIAL #
def M3U_Selector(url,post_type='get',header='Stream Selection'):
"""
Send through an m3u/m3u8 playlist and have the contents displayed via a dialog select.
The return will be a dictionary of 'name' and 'url'. You can send through either
a locally stored filepath or an online URL.
This function will try it's best to pull out the relevant playlist details even if the
web page isn't a correctly formatted m3u playlist (e.g. an m3u playlist embedded into
a blog page).
CODE: M3U_Selector(url, [post_type, header])
AVAILABLE PARAMS:
(*) url - The location of your m3u file, this can be local or online
post_type - If you need to use POST rather than a standard query string
in your url set this to 'post', by default it's set to 'get'.
header - This is the header you want to appear at the top of your dialog
selection window, by default it's set to "Stream Selection"
EXAMPLE CODE:
dialog.ok('M3U SELECTOR','We will now call this function using the following url:','','[COLOR dodgerblue]http://totalrevolution.tv/videos/playlists/youtube.m3u[/COLOR]')
# This example uses YouTube plugin paths but any playable paths will work
vid = koding.M3U_Selector(url='http://totalrevolution.tv/videos/playlists/youtube.m3u')
# Make sure there is a valid link returned
if vid:
playback = koding.Play_Video(video=vid['url'], showbusy=False)
if playback:
dialog.ok('SUCCESS!','Congratulations the playback was successful!')
xbmc.Player().stop()
else:
dialog.ok('OOPS!','Looks like something went wrong there, the playback failed. Check the links are still valid.')
~"""
from web import Open_URL
from vartools import Cleanup_String, Find_In_Text
from filetools import Text_File
success = False
if url.startswith('http'):
content = Open_URL(url=url, post_type=post_type, timeout=10)
else:
try:
url = xbmc.translatePath(url)
except:
pass
content = Text_File(url,'r')
if content:
newcontent = content.splitlines()
name_array = []
url_array = []
name = ''
for line in newcontent:
line = line.strip()
# Grab the name of the stream
if line.startswith('#EXT'):
name = line.split(',')
name.pop(0)
name = ''.join(name)
# Grab the url(s) of the stream
if name != '' and line != '' and not line.startswith('#EXT'):
name_array.append(Cleanup_String(name))
line = line.replace('<br>','').replace('<br />','').replace('<br/>','')
line = line.replace('</p>','').replace('</div>','').replace('</class>','')
xbmc.log('line: %s'%line)
if 'm3u' in line or 'm3u8' in line:
line = 'LIST~'+line
if 'src="' in line:
line = Find_In_Text(content=line, start='src="', end='"')[0]
url_array.append(line)
name = ''
line = ''
# If there is only one entry with no names/comments just return as unknown with the link
if not '#EXT' in content:
return {'name' : 'Unknown', 'url' : line}
# If there's a list we show a dialog select of the available links
if len(name_array) > 0:
choice = xbmcgui.Dialog().select(header, name_array)
if choice >= 0:
# If the selection is a final url and not a list of multiple links
if not url_array[choice].startswith('LIST~'):
success = True
return {'name' : name_array[choice], 'url' : url_array[choice]}
# List of multiple links detected, give option of which link to play
else:
clean_link = url_array[choice].replace('LIST~','')
content = Open_URL(url=clean_link, timeout=10)
if content:
newcontent = content.splitlines()
name_array = []
url_array = []
name = ''
counter = 1
for line in newcontent:
# Show name as link 1,2,3,4 etc.
if line.startswith('#EXT'):
name = 'LINK '+str(counter)
# Grab the link(s) to the video
if name != '' and line != '' and not line.startswith('#EXT'):
name_array.append(name)
line = line.replace('<br>','').replace('<br />','').replace('<br/>','')
line = line.replace('</p>','').replace('</div>','').replace('</class>','')
url_array.append(line)
name = ''
line = ''
counter += 1
# If there is only one entry with no names/comments just return as unknown with the link
if not '#EXT' in content:
return {'name' : 'Unknown', 'url' : line}
# Give option of which link to play in case of multiple links available
if len(name_array) > 0:
choice = xbmcgui.Dialog().select(header, name_array)
if choice >= 0:
success = True
return {'name' : name_array[choice], 'url' : url_array[choice]}
if not success:
xbmcgui.Dialog().ok('NO LINKS FOUND','Sorry no valid links could be found for this stream.')
return False
#----------------------------------------------------------------
# TUTORIAL #
def Play_Video(video,showbusy=True,content='video',ignore_dp=False,timeout=10, item=None, player=xbmc.Player(), resolver=None):
"""
This will attempt to play a video and return True or False on
whether or not playback was successful. This function is similar
to Check_Playback but this actually tries a number of methods to
play the video whereas Check_Playback does not actually try to
play a video - it will just return True/False on whether or not
a video is currently playing.
If you have m3u or m3u8 playlist links please use the M3U_Selector
function to get the final resolved url.
CODE: Play_Video(video, [showbusy, content, ignore_dp, timeout, item])
AVAILABLE PARAMS:
(*) video - This is the path to the video, this can be a local
path, online path or a channel number from the PVR.
showbusy - By default this is set to True which means while the
function is attempting to playback the video the user will see the
busy dialog. Set to False if you prefer this not to appear but do
bare in mind a user may navigate to another section and try playing
something else if they think this isn't doing anything.
content - By default this is set to 'video', however if you're
passing through audio you may want to set this to 'music' so the
system can correctly set the tags for artist, song etc.
ignore_dp - By default this is set to True but if set to False
this will ignore the DialogProgress window. If you use a DP while
waiting for the stream to start then you'll want to set this True.
Please bare in mind the reason this check is in place and enabled
by default is because some streams do bring up a DialogProgress
when initiated (such as f4m proxy links) and disabling this check
in those circumstances can cause false positives.
timeout - This is the amount of time you want to allow for playback
to start before sending back a response of False. Please note if
ignore_dp is set to True then it will also add a potential 10s extra
to this amount if a DialogProgress window is open. The default setting
for this is 10s.
item - By default this is set to None and in this case the metadata
will be auto-populated from the previous Add_Dir so you'll just get the
basics like title, thumb and description. If you want to send through your
own metadata in the form of a dictionary you can do so and it will override
the auto-generation. If anything else sent through no metadata will be set,
you would use this option if you've already set metadata in a previous function.
player - By default this is set to xbmc.Player() but you can send through
a different class/function if required.
resolver - By default this is set to urlresolver but if you prefer to use
your own custom resolver then just send through that class when calling this
function and the link sent through will be resolved by your custom resolver.
EXAMPLE CODE:
isplaying = koding.Play_Video('http://totalrevolution.tv/videos/python_koding/Browse_To_Folder.mov')
if isplaying:
dialog.ok('PLAYBACK SUCCESSFUL','Congratulations, playback was successful')
xbmc.Player().stop()
else:
dialog.ok('PLAYBACK FAILED','Sorry, playback failed :(')
~"""
xbmc.log('### ORIGINAL VIDEO: %s'%video)
if not resolver:
import urlresolver
resolver = urlresolver
try: import simplejson as json
except: import json
if not item:
meta = {}
for i in ['title', 'originaltitle', 'tvshowtitle', 'year', 'season', 'episode', 'genre', 'rating', 'votes',
'director', 'writer', 'plot', 'tagline']:
try:
meta[i] = xbmc.getInfoLabel('listitem.%s' % i)
except:
pass
meta = dict((k, v) for k, v in meta.iteritems() if not v == '')
if 'title' not in meta:
meta['title'] = xbmc.getInfoLabel('listitem.label')
icon = xbmc.getInfoLabel('listitem.icon')
item = xbmcgui.ListItem(path=video, iconImage =icon, thumbnailImage=icon)
if content == "music":
try:
meta['artist'] = xbmc.getInfoLabel('listitem.artist')
item.setInfo(type='Music', infoLabels={'title': meta['title'], 'artist': meta['artist']})
except:
item.setInfo(type='Video', infoLabels=meta)
else:
item.setInfo(type='Video', infoLabels=meta)
elif type(item).__name__ == 'dict':
item.setInfo(type='Video', infoLabels=meta)
else:
pass
playback = False
if showbusy:
Show_Busy()
# if a plugin path is sent we try activate window
if video.startswith('plugin://'):
try:
xbmc.log('Attempting to play via xbmc.Player().play() method')
player.play(video)
playback = Check_Playback(ignore_dp,timeout)
except:
xbmc.log(Last_Error())
# If an XBMC action has been sent through we do an executebuiltin command
elif video.startswith('ActivateWindow') or video.startswith('RunAddon') or video.startswith('RunScript') or video.startswith('PlayMedia'):
try:
xbmc.log('Attempting to play via xbmc.executebuiltin method')
xbmc.executebuiltin('%s'%video)
playback = Check_Playback(ignore_dp,timeout)
except:
xbmc.log(Last_Error())
elif ',' in video:
# Standard xbmc.player method (a comma in url seems to throw urlresolver off)
try:
xbmc.log('Attempting to play via xbmc.Player.play() method')
player.play('%s'%video, item)
playback = Check_Playback(ignore_dp,timeout)
# Attempt to resolve via urlresolver
except:
try:
xbmc.log('Attempting to resolve via urlresolver module')
xbmc.log('video = %s'%video)
hmf = resolver.HostedMediaFile(url=video, include_disabled=False, include_universal=True)
if hmf.valid_url() == True:
video = hmf.resolve()
xbmc.log('### VALID URL, RESOLVED: %s'%video)
player.play('%s' % video, item)
playback = Check_Playback(ignore_dp,timeout)
except:
xbmc.log(Last_Error())
# Play from a db entry - untested
elif video.isdigit():
xbmc.log('### Video is digit, presuming it\'s a db item')
command = ('{"jsonrpc": "2.0", "id":"1", "method": "Player.Open","params":{"item":{"channelid":%s}}}' % url)
xbmc.executeJSONRPC(command)
playback = Check_Playback(ignore_dp,timeout)
else:
# Attempt to resolve via urlresolver
try:
xbmc.log('Attempting to resolve via urlresolver module')
xbmc.log('video = %s'%video)
hmf = resolver.HostedMediaFile(url=video, include_disabled=False, include_universal=True)
if hmf.valid_url() == True:
video = hmf.resolve()
xbmc.log('### VALID URL, RESOLVED: %s'%video)
player.play('%s' % video, item)
playback = Check_Playback(ignore_dp,timeout)
# Standard xbmc.player method
except:
try:
xbmc.log('Attempting to play via xbmc.Player.play() method')
player.play('%s' % video, item)
playback = Check_Playback(ignore_dp,timeout)
except:
xbmc.log(Last_Error())
xbmc.log('Playback status: %s' % playback)
Show_Busy(False)
counter = 1
dialogprogress = xbmc.getCondVisibility('Window.IsActive(progressdialog)')
if not ignore_dp:
while dialogprogress:
dp.create('Playback Good','Closing dialog...')
xbmc.log('Attempting to close dp #%s'%counter)
dp.close()
xbmc.sleep(1000)
counter += 1
dialogprogress = xbmc.getCondVisibility('Window.IsActive(progressdialog)')
return playback
#----------------------------------------------------------------
# TUTORIAL #
def Sleep_If_Playback_Active():
"""
This will allow you to pause code while kodi is playing audio or video
CODE: Sleep_If_Playback_Active()
EXAMPLE CODE:
dialog.ok('PLAY A VIDEO','We will now attempt to play a video, once you stop this video you should see a dialog.ok message.')
xbmc.Player().play('http://download.blender.org/peach/bigbuckbunny_movies/big_buck_bunny_720p_stereo.avi')
xbmc.sleep(3000) # Give kodi enough time to load up the video
koding.Sleep_If_Playback_Active()
dialog.ok('PLAYBACK FINISHED','The playback has now been finished so this dialog code has now been initiated')
~"""
isplaying = xbmc.Player().isPlaying()
while isplaying:
xbmc.sleep(500)
isplaying = xbmc.Player().isPlaying()

View File

@@ -0,0 +1,332 @@
# -*- coding: utf-8 -*-
# script.module.python.koding.aio
# Python Koding AIO (c) by TOTALREVOLUTION LTD (support@trmc.freshdesk.com)
# Python Koding AIO is licensed under a
# Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License.
# You should have received a copy of the license along with this
# work. If not, see http://creativecommons.org/licenses/by-nc-nd/4.0.
# Please make sure you've read and understood the license, this code can NOT be used commercially
# and it can NOT be modified and redistributed. If you're found to be in breach of this license
# then any affected add-ons will be blacklisted and will not be able to work on the same system
# as any other add-ons which use this code. Thank you for your cooperation.
import os
import time
import urllib
import xbmc
import xbmcgui
import xbmcvfs
from systemtools import Python_Version
#----------------------------------------------------------------
# TUTORIAL #
def Cleanup_URL(url):
"""
Clean a url, removes whitespaces and common buggy formatting when pulling from websites
CODE: Cleanup_URL(url)
AVAILABLE PARAMS:
(*) url - This is the main url you want cleaned up.
EXAMPLE CODE:
raw_url = '" http://test.com/video/"/'
clean_url = koding.Cleanup_URL(raw_url)
dialog.ok('CLEANUP URL', 'Orig: %s'%raw_url,'Clean: %s'%clean_url)
~"""
from HTMLParser import HTMLParser
bad_chars = ['/','\\',':',';','"',"'"]
url = url.strip()
while url[0] in bad_chars or url[-1] in bad_chars:
if url[-1] in bad_chars:
url = url[:-1]
if url[0] in bad_chars:
url = url[1:]
url = url.strip()
return HTMLParser().unescape(url)
#----------------------------------------------------------------
# TUTORIAL #
def Delete_Cookies(filename='cookiejar'):
"""
This will delete your cookies file.
CODE: Delete_Cookies([filename])
AVAILABLE PARAMS:
filename - By default this is set to the filename 'cookiejar'.
This is the default cookie filename which is created by the Open_URL
function but you can use any name you want and this function will
return True or False on whether or not it's successfully been removed.
EXAMPLE CODE:
Open_URL(url='http://google.com',cookiejar='google')
dialog.ok('GOOGLE COOKIES CREATED','We have just opened a page to google.com, if you check your addon_data folder for your add-on you should see a cookies folder and in there should be a cookie called "google". When you press OK this will be removed.')
koding.Delete_Cookies(filename='google')
~"""
from addons import Addon_Info
Addon_Version = Addon_Info(id='version')
Addon_Profile = Addon_Info(id='profile')
Cookie_File = os.path.join(Addon_Profile,'cookies',filename)
if xbmcvfs.delete(Cookie_File):
return True
else:
return False
#----------------------------------------------------------------
# TUTORIAL #
def Download(url, dest, dp=None, timeout=5):
"""
This will download a file, currently this has to be a standard download link which doesn't require cookies/login.
CODE: Download(src,dst,[dp])
dp is optional, by default it is set to false
AVAILABLE PARAMS:
(*) src - This is the source file, the URL to your download. If you attempted to download an item but it's not behaving the way you think it should (e.g. a zip file not unzipping) then change the extension of the downloaded file to .txt and open up in a text editor. You'll most likely find it's just a piece of text that was returned from the URL you gave and it should have details explaining why it failed. Could be that's the wrong URL, it requires some kind of login, it only accepts certain user-agents etc.
(*) dst - This is the destination file, make sure it's a physical path and not "special://...". Also remember you need to add the actual filename to the end of the path, so if we were downloading something to the "downloads" folder and we wanted the file to be called "test.txt" we would use this path: dst = "downloads/test.txt". Of course the downloads folder would actually need to exist otherwise it would fail and based on this poor example the downloads folder would be at root level of your device as we've not specified a path prior to that so it just uses the first level that's accessible.
dp - This is optional, if you pass through the dp function as a DialogProgress() then you'll get to see the progress of the download. If you choose not to add this paramater then you'll just get a busy spinning circle icon until it's completed. See the example below for a dp example.
timeout - By default this is set to 5. This is the max. amount of time you want to allow for checking whether or
not the url is a valid link and can be accessed via the system.
EXAMPLE CODE:
src = 'http://noobsandnerds.com/portal/Bits%20and%20bobs/Documents/user%20guide%20of%20the%20gyro%20remote.pdf'
dst = 'special://home/remote.pdf'
dp = xbmcgui.DialogProgress()
dp.create('Downloading File','Please Wait')
koding.Download(src,dst,dp)
dialog.ok('DOWNLOAD COMPLETE','Your download is complete, please check your home Kodi folder. There should be a new file called remote.pdf.')
dialog.ok('DELETE FILE','Click OK to delete the downloaded file.')
xbmcvfs.delete(dst)
~"""
from filetools import Physical_Path
dest = Physical_Path(dest)
status = Validate_Link(url,timeout)
if status >= 200 and status < 400:
if Python_Version() < 2.7 and url.startswith('https'):
url = url.replace('https','http')
start_time=time.time()
urllib.urlretrieve(url, dest, lambda nb, bs, fs: Download_Progress(nb, bs, fs, dp, start_time))
return True
else:
return False
#----------------------------------------------------------------
def Download_Progress(numblocks, blocksize, filesize, dp, start_time):
""" internal command ~"""
try:
percent = min(numblocks * blocksize * 100 / filesize, 100)
currently_downloaded = float(numblocks) * blocksize / (1024 * 1024)
kbps_speed = numblocks * blocksize / (time.time() - start_time)
if kbps_speed > 0:
eta = (filesize - numblocks * blocksize) / kbps_speed
else:
eta = 0
kbps_speed = kbps_speed / 1024
total = float(filesize) / (1024 * 1024)
mbs = '%.02f MB of %.02f MB' % (currently_downloaded, total)
e = 'Speed: %.02f Kb/s ' % kbps_speed
e += 'ETA: %02d:%02d' % divmod(eta, 60)
if dp:
dp.update(percent, mbs, e)
if dp.iscanceled():
dp.close()
except:
percent = 100
if dp:
dp.update(percent)
if dp:
if dp.iscanceled():
dp.close()
dp.close()
#----------------------------------------------------------------
# TUTORIAL #
def Get_Extension(url):
"""
Return the extension of a url
CODE: Get_Extension(url)
AVAILABLE PARAMS:
(*) url - This is the url you want to grab the extension from
EXAMPLE CODE:
dialog.ok('ONLINE FILE','We will now try and get the extension of the file found at this URL:','','[COLOR=dodgerblue]http://www.sample-videos.com/video/mp4/720/big_buck_bunny_720p_1mb.mp4[/COLOR]')
url_extension = koding.Get_Extension('http://www.sample-videos.com/video/mp4/720/big_buck_bunny_720p_1mb.mp4')
dialog.ok('FILE EXTENSION','The file extension of this Big Buck Bunny sample is:','','[COLOR=dodgerblue]%s[/COLOR]'%url_extension)
~"""
import os
import urlparse
parsed = urlparse.urlparse(url)
root, ext = os.path.splitext(parsed.path)
return ext
#----------------------------------------------------------------
# TUTORIAL #
def Open_URL(url='',post_type='get',payload={},headers={'User-Agent':'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-GB; rv:1.9.0.3) Gecko/2008092417 Firefox/3.0.3'},cookies=True,auth=None,timeout=None,cookiejar=None,proxies={}):
"""
If you need to pull the contents of a webpage it's very simple to do so by using this function.
This uses the Python Requests module, for more detailed info on how the params work
please look at the following link: http://docs.python-requests.org/en/master/user/advanced/
IMPORTANT: This function will attempt to convert a url with a query string into the
correct params for a post or get command but I highly recommend sending through your
query string as a dictionary using the payload params. It's much cleaner and is a
safer way of doing things, if you send through your url with a query string attached
then I take no responsibility if it doesn't work!
CODE: Open_URL(url,[post_type,payload,headers,cookies,auth,timeout,cookiejar])
AVAILABLE PARAMS:
url - This is the main url you want to send through. Send it through
as a query string format even if it's a post.
post_type - By default this is set to 'get' but this can be set to 'post',
if set to post the query string will be split up into a post format automatically.
payload - By default this is not used but if you just want a standard
basic Open_URL function you can add a dictionary of params here. If you
don't enter anything in here the function will just split up your url
accordingly. Make sure you read the important information at the top
of this tutorial text.
headers - Optionally send through headers in form of a dictionary.
cookies - If set to true your request will send through and store cookies.
auth - User/pass details
timeout - Optionally set a timeout for the request.
cookiejar - An name for the location to store cookies. By default it's
set to addon_data/<addon_id>/cookies/cookiejar but if you have multiple
websites you access then you may want to use a separate filename for each site.
proxies - Use a proxy for accessing the link, see requests documentation for full
information but essentially you would send through a dictionary like this:
proxies = {"http":"http://10.10.1.10:3128","htts":"https://10.10.1.10:3128"}
EXAMPLE CODE:
dialog.ok('OPEN FORUM PAGE','We will attempt to open the noobsandnerds forum page and return the contents. You will now be asked for your forum credentials.')
myurl = 'http://noobsandnerds.com/support/index.php'
username = koding.Keyboard('ENTER USERNAME')
password = koding.Keyboard('ENTER PASSWORD')
params = {"username":username,"password":password}
xbmc.log(repr(params),2)
url_contents = koding.Open_URL(url=myurl, payload=params, post_type='get')
koding.Text_Box('CONTENTS OF WEB PAGE',url_contents)
~"""
import os
import pickle
import requests
import sys
import xbmc
import xbmcaddon
from __init__ import converthex, dolog, ADDON_ID, KODI_VER
from addons import Addon_Info
from filetools import Text_File
Addon_Version = Addon_Info(id='version')
Addon_Profile = Addon_Info(id='profile')
Cookie_Folder = os.path.join(Addon_Profile,'cookies')
xbmc.log(Cookie_Folder,2)
if not xbmcvfs.exists(Cookie_Folder):
xbmcvfs.mkdirs(Cookie_Folder)
if cookiejar == None:
Cookie_Jar = os.path.join(Cookie_Folder,'cookiejar')
else:
Cookie_Jar = os.path.join(Cookie_Folder,cookiejar)
my_cookies = None
if cookies:
if xbmcvfs.exists(Cookie_Jar):
try:
openfile = xbmcvfs.File(Cookie_Jar)
f = openfile.read()
openfile.close()
my_cookies = pickle.load(f)
except:
my_cookies = None
# If the payload is empty we split the params
if len(payload) == 0:
if '?' in url:
url, args = url.split('?')
args = args.split('&')
for item in args:
var, data = item.split('=')
payload[var] = data
dolog('PAYLOAD: %s'%payload)
try:
if Python_Version() < 2.7 and url.startswith('https'):
url = url.replace('https','http')
if post_type == 'post':
r = requests.post(url, payload, headers=headers, cookies=my_cookies, auth=auth, timeout=timeout, proxies=proxies)
else:
r = requests.get(url, payload, headers=headers, cookies=my_cookies, auth=auth, timeout=timeout, proxies=proxies)
except:
dolog('Failed to pull content for %s'%url)
return False
dolog('### CODE: %s | REASON: %s' % (r.status_code, r.reason))
if r.status_code >= 200 and r.status_code < 400:
content = r.text.encode('utf-8')
dolog('content: %s'%content)
if cookies:
openfile = xbmcvfs.File(Cookie_Jar,'wb')
pickle.dump(r.cookies, openfile)
openfile.close()
return content
else:
dolog('Failed to pull content for %s'%url)
return False
#----------------------------------------------------------------
# TUTORIAL #
def Validate_Link(url='',timeout=30):
"""
Returns the code for a particular link, so for example 200 is a good link and 404 is a URL not found
CODE: Validate_Link(url,[timeout])
AVAILABLE PARAMS:
(*) url - This is url you want to check the header code for
timeout - An optional timeout integer for checking url (default is 30 seconds)
EXAMPLE CODE:
url_code = koding.Validate_Link('http://totalrevolution.tv')
if url_code == 200:
dialog.ok('WEBSITE STATUS','The website [COLOR=dodgerblue]totalrevolution.tv[/COLOR] is [COLOR=lime]ONLINE[/COLOR]')
else:
dialog.ok('WEBSITE STATUS','The website [COLOR=dodgerblue]totalrevolution.tv[/COLOR] is [COLOR=red]OFFLINE[/COLOR]')
~"""
import requests
import xbmc
if Python_Version() < 2.7 and url.startswith('https'):
url = url.replace('https','http')
try:
r = requests.get(url,timeout=timeout)
return r.status_code
except:
return 400
#----------------------------------------------------------------