Archive:Write python scripts: Difference between revisions
>Gamester17 No edit summary |
m (Karellen moved page HOW-TO:Write python scripts to Archive:Write python scripts without leaving a redirect: Outdated) |
||
(33 intermediate revisions by 10 users not shown) | |||
Line 1: | Line 1: | ||
{{outdated}} | |||
This article was originally a copy of a [http://www.nelson.monkey.org/~nelson/weblog/ python HOW-TO tutorial was prepared by Nelson Minar]. The article have since then been reformatted, restructured, modified and updated several times. | This article was originally a copy of a [http://www.nelson.monkey.org/~nelson/weblog/ python HOW-TO tutorial was prepared by Nelson Minar]. The article have since then been reformatted, restructured, modified and updated several times. | ||
=XBMC Python Scripting Tutorial= | = XBMC Python Scripting Tutorial = | ||
Note that this article was written with ease of understanding in | Note that this article was written with ease of understanding in | ||
mind so experienced python developers may be irritated by how much this article been simplified and dumbed down, but this tutorial was not written for them. | mind so experienced python developers may be irritated by how much this article been simplified and dumbed down, but this tutorial was not written for them. | ||
==Python is not a snake== | == Python is not a snake == | ||
==Some basic rules - be careful of the snake!== | ==Some basic rules - be careful of the snake!== | ||
Some features are added with time and so I really advice you to have the very latest version of XBMC or else some python | Some features are added with time and so I really advice you to have the very latest version of XBMC or else some python scripts may not work. | ||
The script launcher is based in the parameters settings of XBMC so go | The script launcher is based in the parameters settings of XBMC so go | ||
there to launch scripts. When you are launching a script the mention | there to launch scripts. When you are launching a script the mention | ||
'running' is added next to the script name. If it still running just | 'running' is added next to the script name. If it still running just | ||
click on it then it will stop. | click on it then it will stop. You can display script output in many ways (according to your keymap files) : | ||
* Keyboard : if you have ran you script return to the Scripts screen and press 'i'. If you press 'i' when the script output is displayed it clears output. Then press 'esc' to close output. | |||
* Gamepad : black button to display output (on the scripts page), white to clear. | |||
* Remote : Info button to display and clear. | |||
* Http request : Send SendKey(260) to show output, SendKey(261) to clear. Theses keys are corresponding to black/white button. | |||
You may need internet access to run some scripts so configure XBMC | You may need internet access to run some scripts so configure XBMC | ||
correctly and do not forget to edit the name server | correctly and do not forget to edit the name server | ||
(a.k.a. DNS) to resolve domain names. | (a.k.a. DNS) to resolve domain names. | ||
==Always notice the snake behavior== | == Always notice the snake behavior == | ||
You must know Python coding is based on indentation. So no need of {} | You must know Python coding is based on indentation. So no need of {} | ||
to declare the start and end of a function, class, if... Everything is | to declare the start and end of a function, class, if... Everything is | ||
Line 26: | Line 31: | ||
recommend you to read the documentation available on www.python.org | recommend you to read the documentation available on www.python.org | ||
== the real work begins == | |||
==the real work begins== | |||
There are two specific libraries for Python only available in XBMC: xbmc | There are two specific libraries for Python only available in XBMC: xbmc | ||
and xbmcgui. They are dedicated to the user interface, and keypad | and xbmcgui. They are dedicated to the user interface, and keypad | ||
Line 36: | Line 40: | ||
libraries. | libraries. | ||
===Window=== | === Window === | ||
So the first thing to do is to import libraries | So the first thing to do is to import libraries | ||
<python> | <source lang="python"> | ||
import xbmc, xbmcgui | import xbmc, xbmcgui | ||
</ | </source> | ||
After that we create a class including some functions (defined by def) | After that we create a class including some functions (defined by def) | ||
<python> | <source lang="python"> | ||
class MyClass(xbmcgui.Window): | class MyClass(xbmcgui.Window): | ||
print 'hello world' | print 'hello world' | ||
</ | </source> | ||
So after that we initialize the class object and doModal() allows to | So after that we initialize the class object and doModal() allows to | ||
Line 54: | Line 58: | ||
to have a clean code and delete the class instance. | to have a clean code and delete the class instance. | ||
<python> | <source lang="python"> | ||
mydisplay = MyClass() | mydisplay = MyClass() | ||
mydisplay .doModal() | mydisplay .doModal() | ||
del mydisplay | del mydisplay | ||
</ | </source> | ||
If you put all this code in a script (called display.py) you'll see an | If you put all this code in a script (called display.py) you'll see an | ||
empty window but as there's no code to exit the Myclass class you'll | empty window but as there's no code to exit the Myclass class you'll | ||
have to reset | have to reset XBMC. Also notice the print function only displays | ||
infos in debug mode (white button). | infos in debug mode (white button). | ||
===pad button=== | === pad button === | ||
So now we need to implant a way to exit the class. So we will use the | So now we need to implant a way to exit the class. So we will use the | ||
pad for that. Here is the full code: | pad for that. Here is the full code: | ||
<python> | <nowiki>Insert non-formatted text here</nowiki><source lang="python"> | ||
import xbmc, xbmcgui | import xbmc, xbmcgui | ||
#get actioncodes from | #get actioncodes from https://github.com/xbmc/xbmc/blob/master/xbmc/guilib/Key.h | ||
ACTION_PREVIOUS_MENU = 10 | ACTION_PREVIOUS_MENU = 10 | ||
Line 82: | Line 86: | ||
mydisplay .doModal() | mydisplay .doModal() | ||
del mydisplay | del mydisplay | ||
</ | </source> | ||
So now, each time we push the 'BACK' button, we will exit the class. | So now, each time we push the 'BACK' button, we will exit the class. | ||
Line 90: | Line 94: | ||
actions. self.close() will close the window and so the class. | actions. self.close() will close the window and so the class. | ||
===add (and remove) text label=== | === add (and remove) text label === | ||
Now it's time to display some text on the window we created. For that | Now it's time to display some text on the window we created. For that | ||
we will use the ControlLabel function. ControlLabel has some | we will use the ControlLabel function. ControlLabel has some | ||
Line 96: | Line 100: | ||
is a 'block' to enable text display on the window: | is a 'block' to enable text display on the window: | ||
<python> | <source lang="python"> | ||
self.strAction = xbmcgui.ControlLabel(300, 520, 200, 200, '', 'font14', '0xFFFFFF00') | self.strAction = xbmcgui.ControlLabel(300, 520, 200, 200, '', 'font14', '0xFFFFFF00') | ||
self.addControl(self.strAction) | self.addControl(self.strAction) | ||
self.strAction.setLabel('BACK to quit') | self.strAction.setLabel('BACK to quit') | ||
</ | </source> | ||
There's a reason to write 3 lines for strAction but we will see it later. | There's a reason to write 3 lines for strAction but we will see it later. | ||
Line 114: | Line 118: | ||
So now we add it to the previous code, and show it thanks to the A | So now we add it to the previous code, and show it thanks to the A | ||
button of the pad | button of the pad (Use 'Enter' on keyboard) | ||
<python> | <source lang="python"> | ||
import xbmc, xbmcgui | import xbmc, xbmcgui | ||
#get actioncodes from | #get actioncodes from https://github.com/xbmc/xbmc/blob/master/xbmc/guilib/Key.h | ||
ACTION_PREVIOUS_MENU = 10 | ACTION_PREVIOUS_MENU = 10 | ||
ACTION_SELECT_ITEM = 7 | ACTION_SELECT_ITEM = 7 | ||
Line 135: | Line 139: | ||
mydisplay .doModal() | mydisplay .doModal() | ||
del mydisplay | del mydisplay | ||
</ | </source> | ||
Don't forget you have to push 'BACK' to stop the script ! | Don't forget you have to push 'BACK' to stop the script ! (Use 'Esc' on keyboard) | ||
Line 144: | Line 148: | ||
We will use the B button to remove the label : | We will use the B button to remove the label : | ||
<python> | <source lang="python"> | ||
import xbmc, xbmcgui | import xbmc, xbmcgui | ||
#get actioncodes from | #get actioncodes from https://github.com/xbmc/xbmc/blob/master/xbmc/guilib/Key.h | ||
ACTION_PREVIOUS_MENU = 10 | ACTION_PREVIOUS_MENU = 10 | ||
ACTION_SELECT_ITEM = 7 | ACTION_SELECT_ITEM = 7 | ||
Line 166: | Line 170: | ||
mydisplay .doModal() | mydisplay .doModal() | ||
del mydisplay | del mydisplay | ||
</ | </source> | ||
To add text there's also another feature called ControlFadeLabel which | To add text there's also another feature called ControlFadeLabel which | ||
includes a reset function: | includes a reset function: | ||
<python> | <source lang="python"> | ||
import xbmc, xbmcgui | import xbmc, xbmcgui | ||
#get actioncodes from | #get actioncodes from https://github.com/xbmc/xbmc/blob/master/xbmc/guilib/Key.h | ||
ACTION_PREVIOUS_MENU = 10 | ACTION_PREVIOUS_MENU = 10 | ||
ACTION_SELECT_ITEM = 7 | ACTION_SELECT_ITEM = 7 | ||
Line 196: | Line 200: | ||
mydisplay .doModal() | mydisplay .doModal() | ||
del mydisplay | del mydisplay | ||
</ | </source> | ||
===init parameters=== | === init parameters === | ||
When the class 'Myclass' is launched we can add elements that will run | When the class 'Myclass' is launched we can add elements that will run | ||
when the class is initialized. So here it's perfect to put some | when the class is initialized. So here it's perfect to put some | ||
Line 205: | Line 208: | ||
add it in the init function: | add it in the init function: | ||
<python> | <source lang="python"> | ||
import xbmc, xbmcgui | import xbmc, xbmcgui | ||
#get actioncodes from | #get actioncodes from https://github.com/xbmc/xbmc/blob/master/xbmc/guilib/Key.h | ||
ACTION_PREVIOUS_MENU = 10 | ACTION_PREVIOUS_MENU = 10 | ||
ACTION_SELECT_ITEM = 7 | ACTION_SELECT_ITEM = 7 | ||
Line 232: | Line 235: | ||
mydisplay .doModal() | mydisplay .doModal() | ||
del mydisplay | del mydisplay | ||
</ | </source> | ||
We can also add a background image by including in the init function a ControlImage object: | We can also add a background image by including in the init function a ControlImage object: | ||
<python> | <source lang="python"> | ||
def __init__(self): | def __init__(self): | ||
self.addControl(xbmcgui.ControlImage(0,0,800,600, 'background.png')) | self.addControl(xbmcgui.ControlImage(0,0,800,600, 'background.png')) | ||
Line 242: | Line 245: | ||
self.addControl(self.strActionInfo) | self.addControl(self.strActionInfo) | ||
self.strActionInfo.setLabel('Push BACK to quit, A to display text and B to erase it') | self.strActionInfo.setLabel('Push BACK to quit, A to display text and B to erase it') | ||
</ | </source> | ||
Of course we display the image before the text :) | Of course we display the image before the text :) | ||
Line 248: | Line 251: | ||
===dialog box=== | === dialog box === | ||
What about a dialog box? So we will create function to add a message box : | What about a dialog box? So we will create function to add a message box : | ||
<python> | <source lang="python"> | ||
def message(self): | def message(self): | ||
dialog = xbmcgui.Dialog() | dialog = xbmcgui.Dialog() | ||
dialog.ok(" My message title", " This is a nice message ") | dialog.ok(" My message title", " This is a nice message ") | ||
</ | </source> | ||
And we will call it in another function using self.message() | And we will call it in another function using self.message() | ||
<python> | <source lang="python"> | ||
import xbmc, xbmcgui | import xbmc, xbmcgui | ||
#get actioncodes from | #get actioncodes from https://github.com/xbmc/xbmc/blob/master/xbmc/guilib/Key.h | ||
ACTION_PREVIOUS_MENU = 10 | ACTION_PREVIOUS_MENU = 10 | ||
ACTION_SELECT_ITEM = 7 | ACTION_SELECT_ITEM = 7 | ||
Line 285: | Line 288: | ||
mydisplay .doModal() | mydisplay .doModal() | ||
del mydisplay | del mydisplay | ||
</ | </source> | ||
So now if you push 'A' a dialog will appear. | So now if you push 'A' a dialog will appear. | ||
Line 291: | Line 294: | ||
You can also use the message function in a more general way: | You can also use the message function in a more general way: | ||
<python> | <source lang="python"> | ||
import xbmc, xbmcgui | import xbmc, xbmcgui | ||
#get actioncodes from | #get actioncodes from https://github.com/xbmc/xbmc/blob/master/xbmc/guilib/Key.h | ||
ACTION_PREVIOUS_MENU = 10 | ACTION_PREVIOUS_MENU = 10 | ||
ACTION_SELECT_ITEM = 7 | ACTION_SELECT_ITEM = 7 | ||
Line 318: | Line 321: | ||
mydisplay .doModal() | mydisplay .doModal() | ||
del mydisplay | del mydisplay | ||
</ | </source> | ||
So now you know how to call a function inside another function :) Be | So now you know how to call a function inside another function :) Be | ||
Line 327: | Line 330: | ||
There's also the very common yes / no dialog box : | There's also the very common yes / no dialog box : | ||
<python> | <source lang="python"> | ||
import xbmc, xbmcgui | import xbmc, xbmcgui | ||
#get actioncodes from | #get actioncodes from https://github.com/xbmc/xbmc/blob/master/xbmc/guilib/Key.h | ||
ACTION_PREVIOUS_MENU = 10 | ACTION_PREVIOUS_MENU = 10 | ||
Line 351: | Line 354: | ||
mydisplay .doModal() | mydisplay .doModal() | ||
del mydisplay | del mydisplay | ||
</ | </source> | ||
===buttons=== | === buttons === | ||
Time to see how buttons work! We have | Time to see how buttons work! We have to use the function | ||
ControlButton. It takes 5 arguments. | ControlButton. It takes 5 arguments. | ||
xbmcgui.ControlButton(350, 500, 80, 30, "HELLO") | xbmcgui.ControlButton(350, 500, 80, 30, "HELLO") | ||
Line 362: | Line 365: | ||
focus on it: | focus on it: | ||
<python> | <source lang="python"> | ||
self.button0 = xbmcgui.ControlButton(350, 500, 80, 30, "HELLO") | self.button0 = xbmcgui.ControlButton(350, 500, 80, 30, "HELLO") | ||
self.addControl(self.button0) | self.addControl(self.button0) | ||
self.setFocus(self.button0) | self.setFocus(self.button0) | ||
</ | </source> | ||
Then we have to define the action when the button is pushed: | Then we have to define the action when the button is pushed: | ||
<python> | <source lang="python"> | ||
def onControl(self, control): | def onControl(self, control): | ||
if control == self.button0: | if control == self.button0: | ||
print 'button pushed' | print 'button pushed' | ||
</ | </source> | ||
We can also remove the button of the screen using : | We can also remove the button of the screen using : | ||
<python> | <source lang="python"> | ||
self.removeControl(self.button0) | self.removeControl(self.button0) | ||
</ | </source> | ||
As usual here is the code to see Python in action: | As usual here is the code to see Python in action: | ||
<python> | <source lang="python"> | ||
import xbmc, xbmcgui | import xbmc, xbmcgui | ||
#get actioncodes from | #get actioncodes from https://github.com/xbmc/xbmc/blob/master/xbmc/guilib/Key.h | ||
ACTION_PREVIOUS_MENU = 10 | ACTION_PREVIOUS_MENU = 10 | ||
Line 414: | Line 417: | ||
mydisplay .doModal() | mydisplay .doModal() | ||
del mydisplay | del mydisplay | ||
</ | </source> | ||
Line 422: | Line 425: | ||
we have to link the direction between them. | we have to link the direction between them. | ||
<python> | <source lang="python"> | ||
import xbmc, xbmcgui | import xbmc, xbmcgui | ||
#get actioncodes from | #get actioncodes from https://github.com/xbmc/xbmc/blob/master/xbmc/guilib/Key.h | ||
ACTION_PREVIOUS_MENU = 10 | ACTION_PREVIOUS_MENU = 10 | ||
Line 464: | Line 467: | ||
mydisplay .doModal() | mydisplay .doModal() | ||
del mydisplay | del mydisplay | ||
</ | </source> | ||
===virtual keyboard=== | === virtual keyboard === | ||
Some recent changes added the possibility to input text through a | Some recent changes added the possibility to input text through a | ||
virtual keyboard. | virtual keyboard. | ||
<python> | <source lang="python"> | ||
import xbmc, xbmcgui | import xbmc, xbmcgui | ||
#get actioncodes from | #get actioncodes from https://github.com/xbmc/xbmc/blob/master/xbmc/guilib/Key.h | ||
ACTION_PREVIOUS_MENU = 10 | ACTION_PREVIOUS_MENU = 10 | ||
Line 497: | Line 500: | ||
mydisplay .doModal() | mydisplay .doModal() | ||
del mydisplay | del mydisplay | ||
</ | </source> | ||
===Lists=== | === Lists === | ||
Since early april, we can add lists to XBMC Python. No need to tell | Since early april, we can add lists to XBMC Python. No need to tell | ||
more, so here is an example : | more, so here is an example : | ||
<python> | <source lang="python"> | ||
import xbmc, xbmcgui | import xbmc, xbmcgui | ||
#get actioncodes from | #get actioncodes from https://github.com/xbmc/xbmc/blob/master/xbmc/guilib/Key.h | ||
ACTION_PREVIOUS_MENU = 10 | ACTION_PREVIOUS_MENU = 10 | ||
Line 539: | Line 542: | ||
mydisplay.doModal() | mydisplay.doModal() | ||
del mydisplay | del mydisplay | ||
</ | </source> | ||
===Screen size=== | === Screen size === | ||
As XBMC can handle many different screen sizes, it's useful to get the | As XBMC can handle many different screen sizes, it's useful to get the | ||
values of the current one. So we wil see in action getHeight() and | values of the current one. So we wil see in action getHeight() and | ||
getWidth() | getWidth() | ||
<python> | <source lang="python"> | ||
import xbmc, xbmcgui | import xbmc, xbmcgui | ||
#get actioncodes from | #get actioncodes from https://github.com/xbmc/xbmc/blob/master/xbmc/guilib/Key.h | ||
ACTION_PREVIOUS_MENU = 10 | ACTION_PREVIOUS_MENU = 10 | ||
Line 576: | Line 579: | ||
mydisplay .doModal() | mydisplay .doModal() | ||
del mydisplay | del mydisplay | ||
</ | </source> | ||
===Skin dir and localization=== | === Skin dir and localization === | ||
These infos can be useful for international scripts. | These infos can be useful for international scripts. | ||
getLocalizedString was added on april, 6th 2004. It reads infos from | getLocalizedString was added on april, 6th 2004. It reads infos from | ||
Line 588: | Line 591: | ||
from the init call are displayed first. | from the init call are displayed first. | ||
<python> | <source lang="python"> | ||
import xbmc, xbmcgui | import xbmc, xbmcgui | ||
#get actioncodes from | #get actioncodes from https://github.com/xbmc/xbmc/blob/master/xbmc/guilib/Key.h | ||
ACTION_PREVIOUS_MENU = 10 | ACTION_PREVIOUS_MENU = 10 | ||
Line 619: | Line 622: | ||
mydisplay .doModal() | mydisplay .doModal() | ||
del mydisplay | del mydisplay | ||
</ | </source> | ||
===Language, IP address, DVD state, available memory and CPU temperature functions=== | === Language, IP address, DVD state, available memory and CPU temperature functions === | ||
These are my first attempt to add functions to the XBMC Python library | These are my first attempt to add functions to the XBMC Python library | ||
and i'm proud enough of the result. | and i'm proud enough of the result. | ||
<python> | <source lang="python"> | ||
import xbmc, xbmcgui | import xbmc, xbmcgui | ||
#get actioncodes from | #get actioncodes from https://github.com/xbmc/xbmc/blob/master/xbmc/guilib/Key.h | ||
ACTION_PREVIOUS_MENU = 10 | ACTION_PREVIOUS_MENU = 10 | ||
Line 678: | Line 681: | ||
mydisplay .doModal() | mydisplay .doModal() | ||
del mydisplay | del mydisplay | ||
</ | </source> | ||
===How to add a child window?=== | === How to add a child window? === | ||
It's always useful to add a child window when we don't have enough | It's always useful to add a child window when we don't have enough | ||
space on the main screen. So we will add another class that will be | space on the main screen. So we will add another class that will be | ||
called when the button A is pressed. | called when the button A is pressed. | ||
<python> | <source lang="python"> | ||
import xbmc, xbmcgui | import xbmc, xbmcgui | ||
#get actioncodes from | #get actioncodes from https://github.com/xbmc/xbmc/blob/master/xbmc/guilib/Key.h | ||
ACTION_PREVIOUS_MENU = 10 | ACTION_PREVIOUS_MENU = 10 | ||
ACTION_SELECT_ITEM = 7 | ACTION_SELECT_ITEM = 7 | ||
Line 726: | Line 729: | ||
mydisplay .doModal() | mydisplay .doModal() | ||
del mydisplay | del mydisplay | ||
</ | </source> | ||
===Unrelated XBMC functions=== | === Unrelated XBMC functions === | ||
Here is an example on how to download a file through http. Try and | Here is an example on how to download a file through http. Try and | ||
Except are methods to test if things can be done, read the Python | Except are methods to test if things can be done, read the Python | ||
documentation for more infos. | documentation for more infos. | ||
<python> | <source lang="python"> | ||
import xbmc, xbmcgui, urllib | import xbmc, xbmcgui, urllib | ||
#get actioncodes from | #get actioncodes from https://github.com/xbmc/xbmc/blob/master/xbmc/guilib/Key.h | ||
ACTION_PREVIOUS_MENU = 10 | ACTION_PREVIOUS_MENU = 10 | ||
ACTION_SELECT_ITEM = 7 | ACTION_SELECT_ITEM = 7 | ||
Line 770: | Line 773: | ||
mydisplay .doModal() | mydisplay .doModal() | ||
del mydisplay | del mydisplay | ||
</ | </source> | ||
Line 778: | Line 781: | ||
[[ | == Plugin examples == | ||
[[ | |||
[[ | === Setting streamdetails === | ||
[[ | |||
[[ | |||
[[ | <source lang="python"> | ||
# video and audio stream details used by some skins to display flagging | |||
stream_details = { | |||
"video": { | |||
"Standard": {"codec": "h264", "aspect": 1.78, "width": 720, "height": 480}, | |||
"480p": {"codec": "h264", "aspect": 1.78, "width": 720, "height": 480}, | |||
"720p": {"codec": "h264", "aspect": 1.78, "width": 1280, "height": 720}, | |||
"1080p": {"codec": "h264", "aspect": 1.78, "width": 1920, "height": 1080} | |||
}, | |||
"audio": { | |||
"Standard": {"codec": "aac", "language": "en", "channels": 2}, | |||
"480p": {"codec": "aac", "language": "en", "channels": 2}, | |||
"720p": {"codec": "aac", "language": "en", "channels": 2}, | |||
"1080p": {"codec": "aac", "language": "en", "channels": 2} | |||
} | |||
} | |||
</source> | |||
<source lang="python"> | |||
stream_details["video"][movie[14]]["duration"] = movie[23] | |||
dirItem.listitem.addStreamInfo("video", stream_details["video"][movie[14]]) | |||
dirItem.listitem.addStreamInfo("audio", stream_details["audio"][movie[14]]) | |||
</source> | |||
=== Faster listing of items === | |||
Many plugins (for instance Google Listen) provides XBMC with a fully functioning URL to play. | |||
This is the obvious implementation, but also extremely slow. XBMC will try to get MIME data for all the links. Thus if you have a list of 50 mp3's, the user will have to wait for XBMC to connect to all 50 urls, and check that it is an mp3. | |||
For a faster implementation you can make a "start playback" function in your plugin, and give XBMC an URL to your playback url. | |||
In the YouTube plugin a video is always given the following url: | |||
<source lang="python"> | |||
plugin://plugin.video.youtube/?path=root/video&action=play_video&videoid=ID | |||
</source> | |||
And then a playback function like this. | |||
<source lang="python"> | |||
def playVideo(self, params={}): | |||
get = params.get | |||
video = self.getVideoObject(params) | |||
listitem = xbmcgui.ListItem(label=video['Title'], iconImage=video['thumbnail'], thumbnailImage=video['thumbnail'], path=video['video_url']) | |||
xbmcplugin.setResolvedUrl(handle=int(sys.argv[1]), succeeded=True, listitem=listitem) | |||
</source> | |||
Unless the plugin is using some kind of cache to remember the playable URL, they source will have to be fetched again. But this will still be a lot faster than having XBMC check MIME type of every item. | |||
It is, of course, also possible to use an URL like this. | |||
<source lang="python"> | |||
plugin://plugin.some.plugin/?path=root&action=play&url=urllib.quote(my_url) | |||
</source> | |||
Instead of using a cache or doing a second lookup. | |||
And added bonus of doing this is that the servers we are requesting from will be hammered less. So the webmasters should be happier with us. | |||
Even if I also prefer the setResolvedUrl-Method, I just wanted to add another method: | |||
To avoid xbmc scanning the mime-type you can also just set it: | |||
<source lang="python"> | |||
listitem.setProperty('mimetype', 'video/x-msvideo') | |||
</source> | |||
And also, if you use setResolvedMethod, you don't have to set all if its data (title, thumbnail, etc.) again - just add the path. XBMC will use the remaining properties from the original listitem. | |||
=== URL encode URL options === | |||
Quite a few plugins seem to have problems due to a change in XBMC's URL handling. The gist of it is that all URL options need to be URL encoded. | |||
This is so that we can parse them reliably (it's also so that the URLs are valid). | |||
i.e. | |||
<source lang="python"> | |||
plugin://plugin.foo.bar/what/ever/you/like?url_option=<url encoded option>&url_option2=<url encoded option> | |||
</source> | |||
Another example: | |||
Not so much used in python add on I've seen, but parse_qs (urlparse) and urlencode (urllib) standard python functions do the job for you ... | |||
A parse_qs(urlparse(url).query) return a dictionnary of the parameters already decoded if needed (%...), sure url may contain sys.argv[2] for a plugin (be careful, the dictionnary returned by parse_qs contain arrays of values as a http parameter may be a list of values) and in the other side, a urlencode(<python dictionnary>) return an encoded query part in the form field1=xxxx&field2=yyy%3F ... | |||
This will save time and make more readable code. | |||
This is how I do it. I figure I might as well post it here: | |||
parameters variable should be a dict: | |||
<source lang="python"> | |||
parameters = {"mode" : "show", "link": "http://Test.link/video?id=1234", "subs": "http://test.link/to/some/subs?id=text&format=raw", "art": "http://test.link/to/some/fanart?id=image&format=large"} | |||
url = sys.argv[0] + '?' + urllib.urlencode(parameters) | |||
xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]), url=url, listitem=li, isFolder=folder) | |||
</source> | |||
This would result in your url being sent back to you addon like so (sys.argv[2] contains your parameters): | |||
<source lang="python"> | |||
?art=http%3a%2f%2ftest.link%2fto%2fsome%2ffanart%3fid%3dimage%26format%3dlarge& | |||
link=http%3a%2f%2fTest.link%2fvideo%3fid%3d1234&mode=show&subs=http%3a%2f%2ftest.link%2fto%2fsome%2fsubs%3fid%3dtext%26format%3draw | |||
</source> | |||
Yes, and you get back your parameters with a simple | |||
<source lang="python"> | |||
parameters = parse_qs(urlparse(sys.argv[2]).query) -> parameters ["mode"][0], parameters ["link"][0] | |||
</source> | |||
... with above example. | |||
With two line of code, you can write your add on having "simple" dictionnaries and forget unreadable url scheme. | |||
=== Getting error: SetResolvedUrl - called with an invalid handle === | |||
This is probably going to sound mental to a lot of you, but if you're banging your head against the wall wondering why you're getting this error in the logs: | |||
<source lang="python"> | |||
18:59:54 T:2961911808 ERROR: SetResolvedUrl - called with an invalid handle. | |||
</source> | |||
It's probably because you haven't set the 'isPlayable' property on your listitem before using it as an argument to the addDirectoryItem() method | |||
<source lang="python"> | |||
listitem.setProperty('IsPlayable', true') | |||
xbmcplugin.addDirectoryItem(pluginHandle, url, listitem, isFolder=False, totalItems=totalItems) | |||
</source> | |||
It may seem obvious, but that's probably because you're better at life than me. | |||
[[Category:Development-Archived]] |
Latest revision as of 03:09, 11 July 2020
THIS PAGE IS OUTDATED:
This page or section has not been updated in a long time, no longer applies, refers to features that have been replaced/removed, and/or may not be reliable. This page is only kept for historical reasons, or in case someone wants to try updating it. |
This article was originally a copy of a python HOW-TO tutorial was prepared by Nelson Minar. The article have since then been reformatted, restructured, modified and updated several times.
XBMC Python Scripting Tutorial
Note that this article was written with ease of understanding in mind so experienced python developers may be irritated by how much this article been simplified and dumbed down, but this tutorial was not written for them.
Python is not a snake
Some basic rules - be careful of the snake!
Some features are added with time and so I really advice you to have the very latest version of XBMC or else some python scripts may not work.
The script launcher is based in the parameters settings of XBMC so go there to launch scripts. When you are launching a script the mention 'running' is added next to the script name. If it still running just click on it then it will stop. You can display script output in many ways (according to your keymap files) :
- Keyboard : if you have ran you script return to the Scripts screen and press 'i'. If you press 'i' when the script output is displayed it clears output. Then press 'esc' to close output.
- Gamepad : black button to display output (on the scripts page), white to clear.
- Remote : Info button to display and clear.
- Http request : Send SendKey(260) to show output, SendKey(261) to clear. Theses keys are corresponding to black/white button.
You may need internet access to run some scripts so configure XBMC correctly and do not forget to edit the name server (a.k.a. DNS) to resolve domain names.
Always notice the snake behavior
You must know Python coding is based on indentation. So no need of {} to declare the start and end of a function, class, if... Everything is an object. Very nice sometimes but tricky for beginners! A variable is local unless you declare it global. But this will be clearer in the tutorials. The goal of this document is not to teach Python so I just recommend you to read the documentation available on www.python.org
the real work begins
There are two specific libraries for Python only available in XBMC: xbmc and xbmcgui. They are dedicated to the user interface, and keypad management.
Python code will be colored in blue I will only talk about scripts including a graphical interface, as the console ones work without xbmc libraries.
Window
So the first thing to do is to import libraries
import xbmc, xbmcgui
After that we create a class including some functions (defined by def)
class MyClass(xbmcgui.Window): print 'hello world'
So after that we initialize the class object and doModal() allows to always display the graphical window until we exit it. The del is here to have a clean code and delete the class instance.
mydisplay = MyClass() mydisplay .doModal() del mydisplay
If you put all this code in a script (called display.py) you'll see an empty window but as there's no code to exit the Myclass class you'll have to reset XBMC. Also notice the print function only displays infos in debug mode (white button).
pad button
So now we need to implant a way to exit the class. So we will use the pad for that. Here is the full code:
Insert non-formatted text here
import xbmc, xbmcgui #get actioncodes from https://github.com/xbmc/xbmc/blob/master/xbmc/guilib/Key.h ACTION_PREVIOUS_MENU = 10 class MyClass(xbmcgui.Window): def onAction(self, action): if action == ACTION_PREVIOUS_MENU: self.close() mydisplay = MyClass() mydisplay .doModal() del mydisplay
So now, each time we push the 'BACK' button, we will exit the class. As you can notice, indentation makes everything clear and it's easy to find where everything start or end. def defines a function but here we have a specific one related to XBMC as onAction defines keypad related actions. self.close() will close the window and so the class.
add (and remove) text label
Now it's time to display some text on the window we created. For that we will use the ControlLabel function. ControlLabel has some properties including position, colour, transparency, font size. Here is a 'block' to enable text display on the window:
self.strAction = xbmcgui.ControlLabel(300, 520, 200, 200, '', 'font14', '0xFFFFFF00') self.addControl(self.strAction) self.strAction.setLabel('BACK to quit')
There's a reason to write 3 lines for strAction but we will see it later. So here : 300 is the X position 520 is the Y position 200,200 is supposed to be size of the element but it seems to not work with text 'font14' is the font, also 'font13' is available 0xFFFFFF00: so here it's the colour value and transparency coded in hexadecimal (from 00 to FF). so read this as 0xTTRRGGBB where T is the transparency value, R is red, G is green and as you guessed B is blue.
So now we add it to the previous code, and show it thanks to the A button of the pad (Use 'Enter' on keyboard)
import xbmc, xbmcgui #get actioncodes from https://github.com/xbmc/xbmc/blob/master/xbmc/guilib/Key.h ACTION_PREVIOUS_MENU = 10 ACTION_SELECT_ITEM = 7 class MyClass(xbmcgui.Window): def onAction(self, action): if action == ACTION_PREVIOUS_MENU: self.close() if action == ACTION_SELECT_ITEM: self.strAction = xbmcgui.ControlLabel(300, 200, 200, 200, '', 'font14', '0xFF00FF00') self.addControl(self.strAction) self.strAction.setLabel('Hello world') mydisplay = MyClass() mydisplay .doModal() del mydisplay
Don't forget you have to push 'BACK' to stop the script ! (Use 'Esc' on keyboard)
You have to imagine the label as an element over the window and so we
were able to add it but we can also remove it by using removeControl.
We will use the B button to remove the label :
import xbmc, xbmcgui #get actioncodes from https://github.com/xbmc/xbmc/blob/master/xbmc/guilib/Key.h ACTION_PREVIOUS_MENU = 10 ACTION_SELECT_ITEM = 7 ACTION_PARENT_DIR = 9 class MyClass(xbmcgui.Window): def onAction(self, action): if action == ACTION_PREVIOUS_MENU: self.close() if action == ACTION_SELECT_ITEM: self.strAction = xbmcgui.ControlLabel(300, 200, 200, 200, '', 'font14', '0xFF00FF00') self.addControl(self.strAction) self.strAction.setLabel('Hello world') if action == ACTION_PARENT_DIR: self.removeControl(self.strAction) mydisplay = MyClass() mydisplay .doModal() del mydisplay
To add text there's also another feature called ControlFadeLabel which includes a reset function:
import xbmc, xbmcgui #get actioncodes from https://github.com/xbmc/xbmc/blob/master/xbmc/guilib/Key.h ACTION_PREVIOUS_MENU = 10 ACTION_SELECT_ITEM = 7 class MyClass(xbmcgui.Window): def __init__(self): self.strActionInfo = xbmcgui.ControlLabel(100, 120, 200, 200, '', 'font13', '0xFFFF00FF') self.addControl(self.strActionInfo) self.strActionInfo.setLabel('Push BACK to quit - A to reset text') self.strActionFade = xbmcgui.ControlFadeLabel(200, 300, 200, 200, 'font13', '0xFFFFFF00') self.addControl(self.strActionFade) self.strActionFade.addLabel('This is a fade label') def onAction(self, action): if action == ACTION_PREVIOUS_MENU: self.close() if action == ACTION_SELECT_ITEM: self.strActionFade.reset() mydisplay = MyClass() mydisplay .doModal() del mydisplay
init parameters
When the class 'Myclass' is launched we can add elements that will run when the class is initialized. So here it's perfect to put some background image or some text that needs to be always on screen. So we add it in the init function:
import xbmc, xbmcgui #get actioncodes from https://github.com/xbmc/xbmc/blob/master/xbmc/guilib/Key.h ACTION_PREVIOUS_MENU = 10 ACTION_SELECT_ITEM = 7 ACTION_PARENT_DIR = 9 class MyClass(xbmcgui.Window): def __init__(self): self.strActionInfo = xbmcgui.ControlLabel(100, 120, 200, 200, '', 'font13', '0xFFFF00FF') self.addControl(self.strActionInfo) self.strActionInfo.setLabel('Push BACK to quit, A to display text and B to erase it') def onAction(self, action): if action == ACTION_PREVIOUS_MENU: self.close() if action == ACTION_SELECT_ITEM: self.strAction = xbmcgui.ControlLabel(300, 200, 200, 200, '', 'font14', '0xFF00FF00') self.addControl(self.strAction) self.strAction.setLabel('Hello world') if action == ACTION_PARENT_DIR: self.removeControl(self.strAction) mydisplay = MyClass() mydisplay .doModal() del mydisplay
We can also add a background image by including in the init function a ControlImage object:
def __init__(self): self.addControl(xbmcgui.ControlImage(0,0,800,600, 'background.png')) self.strActionInfo = xbmcgui.ControlLabel(100, 120, 200, 200, '', 'font13', '0xFFFF00FF') self.addControl(self.strActionInfo) self.strActionInfo.setLabel('Push BACK to quit, A to display text and B to erase it')
Of course we display the image before the text :) When it's about path in directories, we always have to replace '\' by '\\' as '\' is reserved for special characters.
dialog box
What about a dialog box? So we will create function to add a message box :
def message(self): dialog = xbmcgui.Dialog() dialog.ok(" My message title", " This is a nice message ")
And we will call it in another function using self.message()
import xbmc, xbmcgui #get actioncodes from https://github.com/xbmc/xbmc/blob/master/xbmc/guilib/Key.h ACTION_PREVIOUS_MENU = 10 ACTION_SELECT_ITEM = 7 class MyClass(xbmcgui.Window): def __init__(self): self.strActionInfo = xbmcgui.ControlLabel(100, 120, 200, 200, '', 'font13', '0xFFFF00FF') self.addControl(self.strActionInfo) self.strActionInfo.setLabel('Push BACK') def onAction(self, action): if action == ACTION_PREVIOUS_MENU: self.close() if action == ACTION_SELECT_ITEM: self.message() def message(self): dialog = xbmcgui.Dialog() dialog.ok(" My message title", " This is a nice message ") mydisplay = MyClass() mydisplay .doModal() del mydisplay
So now if you push 'A' a dialog will appear.
You can also use the message function in a more general way:
import xbmc, xbmcgui #get actioncodes from https://github.com/xbmc/xbmc/blob/master/xbmc/guilib/Key.h ACTION_PREVIOUS_MENU = 10 ACTION_SELECT_ITEM = 7 class MyClass(xbmcgui.Window): def __init__(self): self.strActionInfo = xbmcgui.ControlLabel(100, 120, 200, 200, '', 'font13', '0xFFFF00FF') self.addControl(self.strActionInfo) self.strActionInfo.setLabel('Push BACK') def onAction(self, action): if action == ACTION_PREVIOUS_MENU: self.message('goodbye') self.close() if action == ACTION_SELECT_ITEM: self.message('you pushed A') def message(self, message): dialog = xbmcgui.Dialog() dialog.ok(" My message title", message) mydisplay = MyClass() mydisplay .doModal() del mydisplay
So now you know how to call a function inside another function :) Be careful to only send strings in the message function, as to add integers and strings don't work at the same time without conversion!
There's also the very common yes / no dialog box :
import xbmc, xbmcgui #get actioncodes from https://github.com/xbmc/xbmc/blob/master/xbmc/guilib/Key.h ACTION_PREVIOUS_MENU = 10 class MyClass(xbmcgui.Window): def __init__(self): self.strActionInfo = xbmcgui.ControlLabel(100, 120, 200, 200, '', 'font13', '0xFFFF00FF') self.addControl(self.strActionInfo) self.strActionInfo.setLabel('Push BACK') def onAction(self, action): if action == ACTION_PREVIOUS_MENU: self.goodbye() def goodbye(self): dialog = xbmcgui.Dialog() if dialog.yesno("message", "do you want to leave?"): self.close() mydisplay = MyClass() mydisplay .doModal() del mydisplay
buttons
Time to see how buttons work! We have to use the function ControlButton. It takes 5 arguments. xbmcgui.ControlButton(350, 500, 80, 30, "HELLO") 350 is X postion, 500 is Y position, 80 the width, 30 is height and the last one is the text. First we have to create a button and to focus on it:
self.button0 = xbmcgui.ControlButton(350, 500, 80, 30, "HELLO") self.addControl(self.button0) self.setFocus(self.button0)
Then we have to define the action when the button is pushed:
def onControl(self, control): if control == self.button0: print 'button pushed'
We can also remove the button of the screen using :
self.removeControl(self.button0)
As usual here is the code to see Python in action:
import xbmc, xbmcgui #get actioncodes from https://github.com/xbmc/xbmc/blob/master/xbmc/guilib/Key.h ACTION_PREVIOUS_MENU = 10 class MyClass(xbmcgui.Window): def __init__(self): self.strActionInfo = xbmcgui.ControlLabel(100, 120, 200, 200, '', 'font13', '0xFFFF00FF') self.addControl(self.strActionInfo) self.strActionInfo.setLabel('Push BACK to quit') self.button0 = xbmcgui.ControlButton(350, 500, 80, 30, "HELLO") self.addControl(self.button0) self.setFocus(self.button0) def onAction(self, action): if action == ACTION_PREVIOUS_MENU: self.close() def onControl(self, control): if control == self.button0: self.message('you pushed the button') def message(self, message): dialog = xbmcgui.Dialog() dialog.ok(" My message title", message) mydisplay = MyClass() mydisplay .doModal() del mydisplay
We can also add more buttons, but so we have to define in which order
they have to react. controlDown, controlUp, controlLeft and
controlRight are here to do that. First we hav to create buttons then
we have to link the direction between them.
import xbmc, xbmcgui #get actioncodes from https://github.com/xbmc/xbmc/blob/master/xbmc/guilib/Key.h ACTION_PREVIOUS_MENU = 10 class MyClass(xbmcgui.Window): def __init__(self): self.strActionInfo = xbmcgui.ControlLabel(100, 120, 200, 200, '', 'font13', '0xFFFF00FF') self.addControl(self.strActionInfo) self.strActionInfo.setLabel('Push BACK to quit') self.button0 = xbmcgui.ControlButton(250, 100, 80, 30, "HELLO") self.addControl(self.button0) self.button1 = xbmcgui.ControlButton(250, 200, 80, 30, "HELLO2") self.addControl(self.button1) self.button2 = xbmcgui.ControlButton(450, 200, 80, 30, "HELLO3") self.addControl(self.button2) self.setFocus(self.button0) self.button0.controlDown(self.button1) self.button1.controlUp(self.button0) self.button1.controlRight(self.button2) self.button2.controlLeft(self.button1) def onAction(self, action): if action == ACTION_PREVIOUS_MENU: self.close() def onControl(self, control): if control == self.button0: self.message('you pushed the 1st button') if control == self.button1: self.message('you pushed the 2nd button') if control == self.button2: self.message('you pushed the 3rd button') def message(self, message): dialog = xbmcgui.Dialog() dialog.ok(" My message title", message) mydisplay = MyClass() mydisplay .doModal() del mydisplay
virtual keyboard
Some recent changes added the possibility to input text through a virtual keyboard.
import xbmc, xbmcgui #get actioncodes from https://github.com/xbmc/xbmc/blob/master/xbmc/guilib/Key.h ACTION_PREVIOUS_MENU = 10 class MyClass(xbmcgui.Window): def __init__(self): self.strActionInfo = xbmcgui.ControlLabel(100, 120, 200, 200, '', 'font13', '0xFFFF00FF') self.addControl(self.strActionInfo) self.strActionInfo.setLabel('Push BACK to quit') self.strActionInfo = xbmcgui.ControlLabel(100, 300, 200, 200, '', 'font13', '0xFFFFFFFF') self.addControl(self.strActionInfo) keyboard = xbmc.Keyboard('mytext') keyboard.doModal() if (keyboard.isConfirmed()): self.strActionInfo.setLabel(keyboard.getText()) else: self.strActionInfo.setLabel('user canceled') def onAction(self, action): if action == ACTION_PREVIOUS_MENU: self.close() mydisplay = MyClass() mydisplay .doModal() del mydisplay
Lists
Since early april, we can add lists to XBMC Python. No need to tell more, so here is an example :
import xbmc, xbmcgui #get actioncodes from https://github.com/xbmc/xbmc/blob/master/xbmc/guilib/Key.h ACTION_PREVIOUS_MENU = 10 class MyClass(xbmcgui.Window): def __init__(self): self.strActionInfo = xbmcgui.ControlLabel(250, 80, 200, 200, '', 'font14', '0xFFBBBBFF') self.addControl(self.strActionInfo) self.strActionInfo.setLabel('Push BACK to quit') self.list = xbmcgui.ControlList(200, 150, 300, 400) self.addControl(self.list) self.list.addItem('Item 1') self.list.addItem('Item 2') self.list.addItem('Item 3') self.setFocus(self.list) def onAction(self, action): if action == ACTION_PREVIOUS_MENU: self.close() def onControl(self, control): if control == self.list: item = self.list.getSelectedItem() self.message('You selected : ' + item.getLabel()) def message(self, message): dialog = xbmcgui.Dialog() dialog.ok(" My message title", message) mydisplay = MyClass() mydisplay.doModal() del mydisplay
Screen size
As XBMC can handle many different screen sizes, it's useful to get the values of the current one. So we wil see in action getHeight() and getWidth()
import xbmc, xbmcgui #get actioncodes from https://github.com/xbmc/xbmc/blob/master/xbmc/guilib/Key.h ACTION_PREVIOUS_MENU = 10 class MyClass(xbmcgui.Window): def __init__(self): self.strActionInfo = xbmcgui.ControlLabel(100, 120, 200, 200, '', 'font13', '0xFFFF00FF') self.addControl(self.strActionInfo) self.strActionInfo.setLabel('Push BACK to quit') screenx = self.getWidth() strscreenx = str(screenx) screeny = self.getHeight() strscreeny = str(screeny) self.strActionInfo = xbmcgui.ControlLabel(100, 200, 200, 200, '', 'font13', '0xFFFFFFFF') self.addControl(self.strActionInfo) self.strActionInfo.setLabel('screen width is ' + strscreenx) self.strActionInfo = xbmcgui.ControlLabel(100, 300, 200, 200, '', 'font13', '0xFFFFFFFF') self.addControl(self.strActionInfo) self.strActionInfo.setLabel('screen height is ' + strscreeny) def onAction(self, action): if action == ACTION_PREVIOUS_MENU: self.close() mydisplay = MyClass() mydisplay .doModal() del mydisplay
Skin dir and localization
These infos can be useful for international scripts. getLocalizedString was added on april, 6th 2004. It reads infos from the xml language file. And here is an opportunity to introduce a new way to call a function : mydisplay.localinfos() The localinfos() function is called so you may think only infos of it would be displayed... But as the class MyClass as an init function, all datas from the init call are displayed first.
import xbmc, xbmcgui #get actioncodes from https://github.com/xbmc/xbmc/blob/master/xbmc/guilib/Key.h ACTION_PREVIOUS_MENU = 10 class MyClass(xbmcgui.Window): def __init__(self): self.strActionInfo = xbmcgui.ControlLabel(250, 80, 200, 200, '', 'font14', '0xFFBBBBFF') self.addControl(self.strActionInfo) self.strActionInfo.setLabel('Push BACK to quit') def onAction(self, action): if action == ACTION_PREVIOUS_MENU: self.close() def localinfos(self): localtxt1 = xbmc.getLocalizedString(10000) localtxt2 = xbmc.getLocalizedString(10004) self.strActionInfo = xbmcgui.ControlLabel(100, 200, 200, 200, '', 'font13', '0xFFFFFFFF') self.addControl(self.strActionInfo) self.strActionInfo.setLabel('Text using your xml language file : ' + localtxt1 + ' , ' + localtxt2 ) myskin = xbmc.getSkinDir() self.strActionInfo = xbmcgui.ControlLabel(100, 300, 200, 200, '', 'font13', '0xFFFFFFFF') self.addControl(self.strActionInfo) self.strActionInfo.setLabel('Your skin dir is : /skin/' + myskin) mydisplay = MyClass() mydisplay.localinfos() mydisplay .doModal() del mydisplay
Language, IP address, DVD state, available memory and CPU temperature functions
These are my first attempt to add functions to the XBMC Python library and i'm proud enough of the result.
import xbmc, xbmcgui #get actioncodes from https://github.com/xbmc/xbmc/blob/master/xbmc/guilib/Key.h ACTION_PREVIOUS_MENU = 10 class MyClass(xbmcgui.Window): def __init__(self): self.strActionInfo = xbmcgui.ControlLabel(250, 60, 200, 200, '', 'font14', '0xFFBBBBFF') self.addControl(self.strActionInfo) self.strActionInfo.setLabel('Push BACK to quit') def onAction(self, action): if action == ACTION_PREVIOUS_MENU: self.close() def localinfos(self): myinfos1 = xbmc.getLanguage() self.strActionInfo = xbmcgui.ControlLabel(100, 150, 200, 200, '', 'font13', '0xFFFFFFFF') self.addControl(self.strActionInfo) self.strActionInfo.setLabel('Your language is : ' + myinfos1) myinfos2 = xbmc.getIPAddress() self.strActionInfo = xbmcgui.ControlLabel(100, 200, 200, 200, '', 'font13', '0xFFFFFFFF') self.addControl(self.strActionInfo) self.strActionInfo.setLabel('Your IP adress is : ' + myinfos2) myinfos3 = xbmc.getDVDState() self.strActionInfo = xbmcgui.ControlLabel(100, 250, 200, 200, '', 'font13', '0xFFFFFFFF') self.addControl(self.strActionInfo) dvdstate = '' if (myinfos3 == 1): dvdstate = 'DRIVE_NOT_READY' if (myinfos3 == 16): dvdstate = 'TRAY_OPEN' if (myinfos3 == 64): dvdstate = 'TRAY_CLOSED_NO_MEDIA' if (myinfos3 == 96): dvdstate = 'TRAY_CLOSED_MEDIA_PRESENT' self.strActionInfo.setLabel('dvd state : ' + dvdstate ) myinfos4 = xbmc.getFreeMem() self.strActionInfo = xbmcgui.ControlLabel(100, 300, 200, 200, '', 'font13', '0xFFFFFFFF') self.addControl(self.strActionInfo) self.strActionInfo.setLabel('free mem : ' + str(myinfos4) + ' Mb') myinfos5 = xbmc.getCpuTemp() self.strActionInfo = xbmcgui.ControlLabel(100, 350, 200, 200, '', 'font13', '0xFFFFFFFF') self.addControl(self.strActionInfo) self.strActionInfo.setLabel('cpu temp : ' + str(myinfos5) ) mydisplay = MyClass() mydisplay.localinfos() mydisplay .doModal() del mydisplay
How to add a child window?
It's always useful to add a child window when we don't have enough space on the main screen. So we will add another class that will be called when the button A is pressed.
import xbmc, xbmcgui #get actioncodes from https://github.com/xbmc/xbmc/blob/master/xbmc/guilib/Key.h ACTION_PREVIOUS_MENU = 10 ACTION_SELECT_ITEM = 7 class MainClass(xbmcgui.Window): def __init__(self): self.strActionInfo = xbmcgui.ControlLabel(180, 60, 200, 200, '', 'font14', '0xFFBBBBFF') self.addControl(self.strActionInfo) self.strActionInfo.setLabel('Push BACK to quit - A to open another window') self.strActionInfo = xbmcgui.ControlLabel(240, 250, 200, 200, '', 'font13', '0xFFFFFFFF') self.addControl(self.strActionInfo) self.strActionInfo.setLabel('This is the first window') def onAction(self, action): if action == ACTION_PREVIOUS_MENU: self.close() if action == ACTION_SELECT_ITEM: popup = ChildClass() popup .doModal() del popup class ChildClass(xbmcgui.Window): def __init__(self): self.addControl(xbmcgui.ControlImage(0,0,800,600, 'background.png')) self.strActionInfo = xbmcgui.ControlLabel(200, 60, 200, 200, '', 'font14', '0xFFBBFFBB') self.addControl(self.strActionInfo) self.strActionInfo.setLabel('Push BACK to return to the first window') self.strActionInfo = xbmcgui.ControlLabel(240, 200, 200, 200, '', 'font13', '0xFFFFFF99') self.addControl(self.strActionInfo) self.strActionInfo.setLabel('This is the child window') def onAction(self, action): if action == ACTION_PREVIOUS_MENU: self.close() mydisplay = MainClass() mydisplay .doModal() del mydisplay
Here is an example on how to download a file through http. Try and Except are methods to test if things can be done, read the Python documentation for more infos.
import xbmc, xbmcgui, urllib #get actioncodes from https://github.com/xbmc/xbmc/blob/master/xbmc/guilib/Key.h ACTION_PREVIOUS_MENU = 10 ACTION_SELECT_ITEM = 7 class MyClass(xbmcgui.Window): def __init__(self): self.strActionInfo = xbmcgui.ControlLabel(100, 120, 200, 200, '', 'font13', '0xFFFF00FF') self.addControl(self.strActionInfo) self.strActionInfo.setLabel('Push BACK to quit - A to download') def onAction(self, action): if action == ACTION_PREVIOUS_MENU: self.close() if action == ACTION_SELECT_ITEM: webfile = 'http://www.google.com/images/logo.gif' localfile = 'Q:\\scripts\\logo.gif' self.downloadURL(webfile,localfile) def downloadURL(self,source, destination): try: loc = urllib.URLopener() loc.retrieve(source, destination) self.message('download ok') except: self.message('download failed') def message(self, message): dialog = xbmcgui.Dialog() dialog.ok(" My message title", message) mydisplay = MyClass() mydisplay .doModal() del mydisplay
That is all, we hope this guide will help you to understand more on how Python scripting works in XBMC. Thanks you for reading, and please consider contributing to this article yourself.
Plugin examples
Setting streamdetails
# video and audio stream details used by some skins to display flagging stream_details = { "video": { "Standard": {"codec": "h264", "aspect": 1.78, "width": 720, "height": 480}, "480p": {"codec": "h264", "aspect": 1.78, "width": 720, "height": 480}, "720p": {"codec": "h264", "aspect": 1.78, "width": 1280, "height": 720}, "1080p": {"codec": "h264", "aspect": 1.78, "width": 1920, "height": 1080} }, "audio": { "Standard": {"codec": "aac", "language": "en", "channels": 2}, "480p": {"codec": "aac", "language": "en", "channels": 2}, "720p": {"codec": "aac", "language": "en", "channels": 2}, "1080p": {"codec": "aac", "language": "en", "channels": 2} } }
stream_details["video"][movie[14]]["duration"] = movie[23] dirItem.listitem.addStreamInfo("video", stream_details["video"][movie[14]]) dirItem.listitem.addStreamInfo("audio", stream_details["audio"][movie[14]])
Faster listing of items
Many plugins (for instance Google Listen) provides XBMC with a fully functioning URL to play.
This is the obvious implementation, but also extremely slow. XBMC will try to get MIME data for all the links. Thus if you have a list of 50 mp3's, the user will have to wait for XBMC to connect to all 50 urls, and check that it is an mp3.
For a faster implementation you can make a "start playback" function in your plugin, and give XBMC an URL to your playback url.
In the YouTube plugin a video is always given the following url:
plugin://plugin.video.youtube/?path=root/video&action=play_video&videoid=ID
And then a playback function like this.
def playVideo(self, params={}): get = params.get video = self.getVideoObject(params) listitem = xbmcgui.ListItem(label=video['Title'], iconImage=video['thumbnail'], thumbnailImage=video['thumbnail'], path=video['video_url']) xbmcplugin.setResolvedUrl(handle=int(sys.argv[1]), succeeded=True, listitem=listitem)
Unless the plugin is using some kind of cache to remember the playable URL, they source will have to be fetched again. But this will still be a lot faster than having XBMC check MIME type of every item.
It is, of course, also possible to use an URL like this.
plugin://plugin.some.plugin/?path=root&action=play&url=urllib.quote(my_url)
Instead of using a cache or doing a second lookup.
And added bonus of doing this is that the servers we are requesting from will be hammered less. So the webmasters should be happier with us.
Even if I also prefer the setResolvedUrl-Method, I just wanted to add another method:
To avoid xbmc scanning the mime-type you can also just set it:
listitem.setProperty('mimetype', 'video/x-msvideo')
And also, if you use setResolvedMethod, you don't have to set all if its data (title, thumbnail, etc.) again - just add the path. XBMC will use the remaining properties from the original listitem.
URL encode URL options
Quite a few plugins seem to have problems due to a change in XBMC's URL handling. The gist of it is that all URL options need to be URL encoded.
This is so that we can parse them reliably (it's also so that the URLs are valid).
i.e.
plugin://plugin.foo.bar/what/ever/you/like?url_option=<url encoded option>&url_option2=<url encoded option>
Another example: Not so much used in python add on I've seen, but parse_qs (urlparse) and urlencode (urllib) standard python functions do the job for you ...
A parse_qs(urlparse(url).query) return a dictionnary of the parameters already decoded if needed (%...), sure url may contain sys.argv[2] for a plugin (be careful, the dictionnary returned by parse_qs contain arrays of values as a http parameter may be a list of values) and in the other side, a urlencode(<python dictionnary>) return an encoded query part in the form field1=xxxx&field2=yyy%3F ...
This will save time and make more readable code.
This is how I do it. I figure I might as well post it here:
parameters variable should be a dict:
parameters = {"mode" : "show", "link": "http://Test.link/video?id=1234", "subs": "http://test.link/to/some/subs?id=text&format=raw", "art": "http://test.link/to/some/fanart?id=image&format=large"} url = sys.argv[0] + '?' + urllib.urlencode(parameters) xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]), url=url, listitem=li, isFolder=folder)
This would result in your url being sent back to you addon like so (sys.argv[2] contains your parameters):
?art=http%3a%2f%2ftest.link%2fto%2fsome%2ffanart%3fid%3dimage%26format%3dlarge& link=http%3a%2f%2fTest.link%2fvideo%3fid%3d1234&mode=show&subs=http%3a%2f%2ftest.link%2fto%2fsome%2fsubs%3fid%3dtext%26format%3draw
Yes, and you get back your parameters with a simple
parameters = parse_qs(urlparse(sys.argv[2]).query) -> parameters ["mode"][0], parameters ["link"][0]
... with above example.
With two line of code, you can write your add on having "simple" dictionnaries and forget unreadable url scheme.
Getting error: SetResolvedUrl - called with an invalid handle
This is probably going to sound mental to a lot of you, but if you're banging your head against the wall wondering why you're getting this error in the logs:
18:59:54 T:2961911808 ERROR: SetResolvedUrl - called with an invalid handle.
It's probably because you haven't set the 'isPlayable' property on your listitem before using it as an argument to the addDirectoryItem() method
listitem.setProperty('IsPlayable', true') xbmcplugin.addDirectoryItem(pluginHandle, url, listitem, isFolder=False, totalItems=totalItems)
It may seem obvious, but that's probably because you're better at life than me.