📜 ⬆️ ⬇️

Detailed anatomy of a simple XBMC plugin

Foreword


A similar article on Habré has already been published , but it focused on parsing the site with a video — the business logic of the plug-in, so to speak, and the issues of interaction with XBMC were touched on in passing. I want to talk about what turns the Python script (hereinafter - Python) into an XBMC plugin.
This plugin is designed to view video podcasts from www.cnet.com in normal or high quality, depending on the settings. The plugin has nothing to do with the CNET podcast viewer plugin available in the official XBMC repository. It is written entirely from 0 as an educational project as part of the independent study of the actual writing of plug-ins to XBMC. The plugin does not claim to be useful to a wide audience, nor to particularly outstanding software solutions. However, it is fully working, and it contains most of the elements inherent in “serious” plugins: an XML file with metadata, the main script, an external imported module, a settings panel, localization files, resource files (pictures). I hope that the plugin and this article will serve as a starting point for both beginner plugin writers and experienced Python programmers who will want to join in writing plugins for XBMC.

Fully plugin can be downloaded from here .

Note: hereinafter, the term “service” is used in relation to some files and folders. This means that XBMC searches for such folders and files without directly referring to them in the plugin code. “Service”! = “Required”, and simpler plug-ins may not contain any service files.
')

general information


To write XBMC plugins, Python is used. XBMC under Windows is compiled with version 2.7, and under Linux, as I understand it, the version of Python depends on the installed developer libraries used in the build. In any case, when writing the plug-in code, it is necessary to focus on the syntax and capabilities of Python 2.7.
Python in XBMC is full-featured: almost the entire standard library is present, except, perhaps, Tkinter / ttk / tix (which are obviously redundant here). To interact with XBMC, 5 modules have been added: xbmc, xbmcgui, xbmcplugin, xbmcaddon and xbmcvfs, which together make up the XBMC Python API. A quick reference on the modules can be found here . Unfortunately, in the help there are inaccuracies.
In addition, the print statement instead of the console writes to the XBMC log with the Notice level, which can be used when debugging, for example, to display the values ​​of variables.
A little about terminology: XBMC developers initially used the term “addon” (addon) for any additions, which, in turn, are divided into plug-ins (content sources), scripts (programs), scrappers (information downloads to media content) and others. However, over time, the concept of "addon" and "plugin" are messed up, and now they are often used to refer to any add-on.

IDE Setup


As already mentioned, XBMC plugins are written in Python. A program in this language is essentially a text file that you can work with in any text editor, starting with Windows Notepad and ending with “advanced options”, like Notepad ++. However, for writing any more or less complicated code, it is better to use IDE (Integrated Development Environment), which offers a number of conveniences: code highlighting, autocompletion of keywords, code browser, brief help on objects, etc. Many Python programmers often use a bunch Eclipse + PyDev, but personally I prefer the more lightweight PyScripter, which has almost all the functions of a full-fledged IDE, except, perhaps, code folding. True, the project has not developed since 2012, but for Python 2.x its capabilities are more than enough.
To facilitate the development of plug-ins on Python in the IDE, good people (including me) have compiled a set of Python plug-in modules that simulate real XBMC Python API modules. These modules need to be added to the project import paths (“Properties”> “PYTHONPATH”> “Source folders” in Eclipse / PyDev or “Extra Python Path ...” in PyScripter), and we get Profit - code completion and brief reference for classes start working, methods and other elements of the XBMC Python API.

Attention: these are just stub modules that do not contain useful code, and an attempt to start the program that references them directly from the IDE will lead to unpredictable results. However, those who wish can add a debugging code to these modules that simulates the behavior of real functions and XBMC Python API methods.

Plugin structure


My "educational" plugin has the following structure:
addon.xml changelog.txt default.py fanart.jpg icon.png License.txt \resources\ settings.xml \language\ \English strings.po \Russian\ strings.po \Ukrainian\ strings.po \lib\ feeds.py \thumbnails\ 

Now more about the appointment of all these files and folders.

addon.xml


addon.xml is a plugin service file from which the built-in XBMC plugin cataloger takes all the necessary information. This is the only mandatory file of the plug-in, and some types of plug-ins (scrapers, repositories, etc.) may not contain any program code or other service files at all.

File contents:
 <?xml version="1.0" encoding="UTF-8"?> <!--   --> <addon id="plugin.video.cnet" version="0.0.1" name="CNET Video Podcasts" provider-name="Roman_V_M"> <!--  --> <requires> <import addon="xbmc.python" version="2.1"/> </requires> <!--  ()  --> <extension point="xbmc.python.pluginsource" library="default.py"> <provides>video</provides> </extension> <!--  --> <extension point="xbmc.addon.metadata"> <summary lang="en">CNET Video Podcasts</summary> <summary lang="ru">- CNET</summary> <summary lang="uk">і- CNET</summary> <description lang="en">Addon for watching CNET video podcasts from XBMC.</description> <description lang="ru">   - CNET  XBMC.</description> <description lang="uk">   і-і CNET  XBMC.</description> <platform>all</platform> </extension> </addon> 


Now more about the content. To display line numbers, open the file in a text editor that supports this feature.

Lines 3-6 contain basic information about the plug-in: a unique identifier that matches the name of the plug-in folder; version; the name displayed in the XBMC interface; plugin author (s).

Lines 8-10 contain information about external dependencies — other components necessary for the operation of the plug-in. As you can see, this plugin requires only XBMC Python API version 2.1. This version is supported in XBMC 13.x (Gotham).
If your plugin requires any other plugin to work, we indicate it here in the same tag. For example, if for your plugin for some reason you need the Youtube plugin, in the section you need to add the following tag:
 <import addon="plugin.video.youtube" version="4.0.0"/> 

In theory, missing dependencies should be installed automatically when the plugin is installed.

Lines 12-14 contain information about the type of plugin. extension point="xbmc.python.pluginsource" means that this is the source plugin for the content, library="default.py" indicates the starting script of the plugin, and video means that it is a video plugin. If the plugin provides access to different content, in the tag you can specify the types of available content separated by spaces. Possible values: video, music and image. These values ​​determine in which section this plugin will be displayed: “Video”, “Music” or “Photo”. Plugin to access different types of content can be displayed in several sections.
As already mentioned, "xbmc.python.pluginsource" means that it is a plugin source of content. That is, using the terminology of the XBMC developers, this is the real plugin, unlike scripts and other types of add-ons. The main difference between source plugins and script plugins (software plugins) is that the source plugin presents the user with multimedia content in the form of multi-level lists (directories) containing folders (virtual and / or real) and files (local and / or in the network), while the script may have an arbitrary interface (within the limits of the API capabilities) or not at all. In the case of a software script, the parameter in double quotes would look like “xbmc.python.script” and in the tag
    executable, . .: 
<extension point="xbmc.python.script" library="default.py"> <provides>executable</provides> </extension>

«». - «», «» «» (video, music image ), - , runtime- ( ).

.

16—22 . XBMC. 23 , .

icon.png
icon.png — () , XBMC. — PNG 256x256 .

fanart.jpg
fanart.jpg — (), . — JPG 1280x720 . : (). , «» , / .

changelog.txt
changelog.txt .

License.txt
License.txt . , . , , XBMC.

default.py
default.py — . «default.py» , ( library= extension), default.py, — , .
- PEP 8, , ____, .

: # -*- coding: utf-8 -*- # Name: plugin.video.cnet # Licence: GPL v.3: http://www.gnu.org/copyleft/gpl.html # import sys, os, urllib2, socket, xml.dom.minidom # XBMC import xbmc, xbmcplugin, xbmcaddon, xbmcgui # _ADDON_NAME = 'plugin.video.cnet' _addon = xbmcaddon.Addon(id=_ADDON_NAME) _addon_id = int(sys.argv[1]) _addon_url = sys.argv[0] _addon_path = _addon.getAddonInfo('path').decode('utf-8') # sys.path.append(os.path.join(_addon_path, 'resources', 'lib')) import feeds # def _string(string_id): return _addon.getLocalizedString(string_id).encode('utf-8') # URL-encoded def get_feed_name(): paramstring = sys.argv[2] if paramstring: feed_name = feeds.NAMES[paramstring.replace('?feed=', '')] else: feed_name = '' return feed_name # RSS- def rss_parser(url): listing = [] try: HEADER = {'User-Agent' : 'Mozilla/5.0 (Windows NT 6.1; rv:18.0) Gecko/20100101 Firefox/20.0'} request = urllib2.Request(url, None, HEADER) rss = xml.dom.minidom.parse(urllib2.urlopen(request, None, 3)) except urllib2.URLError, socket.timeout: pass else: titles = rss.getElementsByTagName('title') links = rss.getElementsByTagName('link') for title, link in zip(titles[2:], links[2:]): title = title.toxml().replace('<title><![CDATA[', '').replace(']]></title>', '') link = link.toxml().replace('<link>', '').replace('</link>', '') listing.append([title, link]) return listing # ( ). def feed_list(addon_id, addon_url, fanart, thumbpath, feeds): names = dict.keys(feeds) for name in names: thumb = os.path.join(thumbpath, feeds[name]['thumb']) # . list_item = xbmcgui.ListItem(name, thumbnailImage=thumb) # . . list_item.setProperty('fanart_image', fanart) # URL, . url = addon_url + '?feed=' + name.replace(' ', '').replace('*', '') # . isFolder=True , (). xbmcplugin.addDirectoryItem(addon_id, url, list_item, isFolder=True) # -- , . xbmcplugin.addSortMethod(addon_id, xbmcplugin.SORT_METHOD_LABEL_IGNORE_THE) # xbmcplugin.endOfDirectory(addon_id) # . switch_view() # . def switch_view(): skin_used = xbmc.getSkinDir() if skin_used == 'skin.confluence': xbmc.executebuiltin('Container.SetViewMode(500)') # "". elif skin_used == 'skin.aeon.nox': xbmc.executebuiltin('Container.SetViewMode(512)') # "-" # . def podcast_list(addon_id, fanart, thumb, listing): for item in listing: # . list_item = xbmcgui.ListItem(item[0], thumbnailImage=thumb) # . . list_item.setProperty('fanart_image', fanart) # . isFolder=False , . xbmcplugin.addDirectoryItem(addon_id, item[1], list_item, isFolder=False) # . xbmcplugin.endOfDirectory(addon_id) def main(): # . thumbpath = os.path.join(_addon_path, 'resources', 'thumbnails') fanart = os.path.join(_addon_path, 'fanart.jpg') # . feed = get_feed_name() # XBMC ( ), . if feed: # . quality = _addon.getSetting('quality') # . listing = rss_parser(url=feeds.FEEDS[feed][quality]) if listing: # . xbmc.log('%s: Started - Opening podcasts List' % _ADDON_NAME, xbmc.LOGNOTICE) thumb = os.path.join(thumbpath, feeds.FEEDS[feed]['thumb']) # . podcast_list(addon_id=_addon_id, fanart=fanart, thumb=thumb, listing=listing) else: # ( ), xbmc.log('%s: Failed to retrieve %s feed data!' % (_ADDON_NAME, feed), xbmc.LOGERROR) # XBMC. xbmc.executebuiltin('Notification(%s,%s)' % (_string(100501), _string(100502))) else: # XBMC ( ), , xbmc.log('%s: Started - Opening feeds List' % _ADDON_NAME, xbmc.LOGNOTICE) # . feed_list(addon_id=_addon_id, addon_url=_addon_url, fanart=fanart, thumbpath=thumbpath, feeds=feeds.FEEDS) if __name__ == '__main__': main()


. .

11—15 — . , , , , PEP 8.

:

11: . , addon.xml, ( ).

12: Addon .

13: runtime- (handle). — , 2- XBMC. addDirectoryItem(). . , - - , - () , XBMC API. , . XBMC ( ) - 3 sys.argv: URL, runtime- (, ), URL-encoded . -, , , sys.argv . , runtime- , addDirectoryItem(), - . URL .

14: URL . URL ''plugin://'' + ( ) + ''/''. URL : "plugin://plugin.video.cnet/". URL ( ) , . , , , XBMC , ( — ) . , , .
(), , . , «», «» «» XBMC « », , , . - , — , — , . , (- ) — XBMC. «» , ( ) . . addDirectoryItem() , . .

15: getAddonInfo('path') , , , , . : Windows, ( , ASCII), , (exeption). , Windows . decode('utf-8'). , , Windows.
getAddonInfo('path') — ( XBMC) . os.path.dirname(__file__) , os.getcwd() . , , , XBMC.

18—19: feeds /resources/lib. cnet.com, URL (dictionary). , , — www.cnet.com/podcasts — , — , , .

22—23: . .getLocalizedString() , UTF-8. , , , , .

26—32: - URL-encoded . - , . , XBMC URL-encoded . URL (. ) URL-encoded .
. , plugin.video.acme, , , . «Action» «Movies» plugin://plugin.video.acme/?=Movies&=Action. addDirectoryItem ( ). , plugin.video.acme, sys.argv[2] «?=Movies&=Action». , , , : XBMC . XBMC (, ), , , (, «Action» «Movies»). .
: , . URL, plugin://.
: xbmcplugin.addDirectoryItem, - , isFolder=False. isFolder=True XBMC , .

35—49: RSS- . XML - . , - (, , ).

52—64: cnet.com — , . .

57: xbmcgui.ListItem. , . , , , () ().

59: . , , .

61: . . , .

63: . , sys.argv[1]. , - . , addDirectoryItem() . isFolder=True XBMC, — , . . , . url , , .

65: . , , .

67: XBMC, .

69: (). , , ( , ), , , , . Container.SetViewMode() , MyVideoNav.xml, MyMusicNav.xml MyPics.xml , .
, . — Youtube.

72—77: . . 500 «» Confluence, 512 — «-» «Aeon-Nox». .

80—89: , . , feed_list(), , URL isFolder False, . . ( ) , .
: isFolder=False , , xbmcplugin.addDirectoryItem. , ( ). xbmcplugin.addDirectoryItem .

92—119: . , . .

101: , . , , .

114: XBMC. _string(), XBMC. .

\resources
\resources — , .

settings.xml
settings.xml — , , XML, , «». «» .

: <?xml version="1.0" encoding="UTF-8"?> <settings> <category label="128"> <setting id="quality" type="labelenum" label="100500" values="HD|SD" default="HD" /> </category> </settings>

— (labelenum) (HD SD). label= , , . id="" (. 101 default.py).
settings.xml « XBMC Addon Developers Guide » ( ). , , .
XML \userdata\addon_data\ XBMC. Windows , , %AppData%\XBMC, Linux — $HOME/.xbmc.

\language
\language . , , , , strings.po. \English, \Russian \Ukrainian, . . , . , , \English . , , .

strings.po: # XBMC Media Center language file msgid "" msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: ru\n" msgctxt "#100500" msgid "Quality:" msgstr ":" msgctxt "#100501" msgid "Access error!" msgstr " !" msgctxt "#100502" msgid "Failed to retrieve the feed data." msgstr " ."

, .po GNU Gettext. XBMC Gettext: .mo, , msgctxt. : XBMC XML, www.transifex.net . -, .po , Gettext.
, getLocalizedString() msgctxt. : XBMC + . , . , , , , , . .
, . , .
, settings.xml .
, XBMC, . .

\lib
\lib , . \lib . , : , . . , . , , , , , , .

\thumbnails
\thumbnails cnet.com. \lib, , .


. XBMC. ( XBMC " "). print xbmc.log().
XBMC.
, XBMC Eclipse + PyDev. .


XBMC . , , . «» .
, , , .


, , Wiki XBMC: http://wiki.xbmc.org/index.php?title=Category:Addon_Development
«XBMC Addon Developers Guide»: yadi.sk/d/NvfFuXYw92paL
XBMC Python API: mirrors.xbmc.org/docs/python-docs

PS
Post factum . . XBMC , . .


XBMC : I — .
executable, . .:
<extension point="xbmc.python.script" library="default.py"> <provides>executable</provides> </extension>

«». - «», «» «» (video, music image ), - , runtime- ( ).

.

16—22 . XBMC. 23 , .

icon.png
icon.png — () , XBMC. — PNG 256x256 .

fanart.jpg
fanart.jpg — (), . — JPG 1280x720 . : (). , «» , / .

changelog.txt
changelog.txt .

License.txt
License.txt . , . , , XBMC.

default.py
default.py — . «default.py» , ( library= extension), default.py, — , .
- PEP 8, , ____, .

: # -*- coding: utf-8 -*- # Name: plugin.video.cnet # Licence: GPL v.3: http://www.gnu.org/copyleft/gpl.html # import sys, os, urllib2, socket, xml.dom.minidom # XBMC import xbmc, xbmcplugin, xbmcaddon, xbmcgui # _ADDON_NAME = 'plugin.video.cnet' _addon = xbmcaddon.Addon(id=_ADDON_NAME) _addon_id = int(sys.argv[1]) _addon_url = sys.argv[0] _addon_path = _addon.getAddonInfo('path').decode('utf-8') # sys.path.append(os.path.join(_addon_path, 'resources', 'lib')) import feeds # def _string(string_id): return _addon.getLocalizedString(string_id).encode('utf-8') # URL-encoded def get_feed_name(): paramstring = sys.argv[2] if paramstring: feed_name = feeds.NAMES[paramstring.replace('?feed=', '')] else: feed_name = '' return feed_name # RSS- def rss_parser(url): listing = [] try: HEADER = {'User-Agent' : 'Mozilla/5.0 (Windows NT 6.1; rv:18.0) Gecko/20100101 Firefox/20.0'} request = urllib2.Request(url, None, HEADER) rss = xml.dom.minidom.parse(urllib2.urlopen(request, None, 3)) except urllib2.URLError, socket.timeout: pass else: titles = rss.getElementsByTagName('title') links = rss.getElementsByTagName('link') for title, link in zip(titles[2:], links[2:]): title = title.toxml().replace('<title><![CDATA[', '').replace(']]></title>', '') link = link.toxml().replace('<link>', '').replace('</link>', '') listing.append([title, link]) return listing # ( ). def feed_list(addon_id, addon_url, fanart, thumbpath, feeds): names = dict.keys(feeds) for name in names: thumb = os.path.join(thumbpath, feeds[name]['thumb']) # . list_item = xbmcgui.ListItem(name, thumbnailImage=thumb) # . . list_item.setProperty('fanart_image', fanart) # URL, . url = addon_url + '?feed=' + name.replace(' ', '').replace('*', '') # . isFolder=True , (). xbmcplugin.addDirectoryItem(addon_id, url, list_item, isFolder=True) # -- , . xbmcplugin.addSortMethod(addon_id, xbmcplugin.SORT_METHOD_LABEL_IGNORE_THE) # xbmcplugin.endOfDirectory(addon_id) # . switch_view() # . def switch_view(): skin_used = xbmc.getSkinDir() if skin_used == 'skin.confluence': xbmc.executebuiltin('Container.SetViewMode(500)') # "". elif skin_used == 'skin.aeon.nox': xbmc.executebuiltin('Container.SetViewMode(512)') # "-" # . def podcast_list(addon_id, fanart, thumb, listing): for item in listing: # . list_item = xbmcgui.ListItem(item[0], thumbnailImage=thumb) # . . list_item.setProperty('fanart_image', fanart) # . isFolder=False , . xbmcplugin.addDirectoryItem(addon_id, item[1], list_item, isFolder=False) # . xbmcplugin.endOfDirectory(addon_id) def main(): # . thumbpath = os.path.join(_addon_path, 'resources', 'thumbnails') fanart = os.path.join(_addon_path, 'fanart.jpg') # . feed = get_feed_name() # XBMC ( ), . if feed: # . quality = _addon.getSetting('quality') # . listing = rss_parser(url=feeds.FEEDS[feed][quality]) if listing: # . xbmc.log('%s: Started - Opening podcasts List' % _ADDON_NAME, xbmc.LOGNOTICE) thumb = os.path.join(thumbpath, feeds.FEEDS[feed]['thumb']) # . podcast_list(addon_id=_addon_id, fanart=fanart, thumb=thumb, listing=listing) else: # ( ), xbmc.log('%s: Failed to retrieve %s feed data!' % (_ADDON_NAME, feed), xbmc.LOGERROR) # XBMC. xbmc.executebuiltin('Notification(%s,%s)' % (_string(100501), _string(100502))) else: # XBMC ( ), , xbmc.log('%s: Started - Opening feeds List' % _ADDON_NAME, xbmc.LOGNOTICE) # . feed_list(addon_id=_addon_id, addon_url=_addon_url, fanart=fanart, thumbpath=thumbpath, feeds=feeds.FEEDS) if __name__ == '__main__': main()


. .

11—15 — . , , , , PEP 8.

:

11: . , addon.xml, ( ).

12: Addon .

13: runtime- (handle). — , 2- XBMC. addDirectoryItem(). . , - - , - () , XBMC API. , . XBMC ( ) - 3 sys.argv: URL, runtime- (, ), URL-encoded . -, , , sys.argv . , runtime- , addDirectoryItem(), - . URL .

14: URL . URL ''plugin://'' + ( ) + ''/''. URL : "plugin://plugin.video.cnet/". URL ( ) , . , , , XBMC , ( — ) . , , .
(), , . , «», «» «» XBMC « », , , . - , — , — , . , (- ) — XBMC. «» , ( ) . . addDirectoryItem() , . .

15: getAddonInfo('path') , , , , . : Windows, ( , ASCII), , (exeption). , Windows . decode('utf-8'). , , Windows.
getAddonInfo('path') — ( XBMC) . os.path.dirname(__file__) , os.getcwd() . , , , XBMC.

18—19: feeds /resources/lib. cnet.com, URL (dictionary). , , — www.cnet.com/podcasts — , — , , .

22—23: . .getLocalizedString() , UTF-8. , , , , .

26—32: - URL-encoded . - , . , XBMC URL-encoded . URL (. ) URL-encoded .
. , plugin.video.acme, , , . «Action» «Movies» plugin://plugin.video.acme/?=Movies&=Action. addDirectoryItem ( ). , plugin.video.acme, sys.argv[2] «?=Movies&=Action». , , , : XBMC . XBMC (, ), , , (, «Action» «Movies»). .
: , . URL, plugin://.
: xbmcplugin.addDirectoryItem, - , isFolder=False. isFolder=True XBMC , .

35—49: RSS- . XML - . , - (, , ).

52—64: cnet.com — , . .

57: xbmcgui.ListItem. , . , , , () ().

59: . , , .

61: . . , .

63: . , sys.argv[1]. , - . , addDirectoryItem() . isFolder=True XBMC, — , . . , . url , , .

65: . , , .

67: XBMC, .

69: (). , , ( , ), , , , . Container.SetViewMode() , MyVideoNav.xml, MyMusicNav.xml MyPics.xml , .
, . — Youtube.

72—77: . . 500 «» Confluence, 512 — «-» «Aeon-Nox». .

80—89: , . , feed_list(), , URL isFolder False, . . ( ) , .
: isFolder=False , , xbmcplugin.addDirectoryItem. , ( ). xbmcplugin.addDirectoryItem .

92—119: . , . .

101: , . , , .

114: XBMC. _string(), XBMC. .

\resources
\resources — , .

settings.xml
settings.xml — , , XML, , «». «» .

: <?xml version="1.0" encoding="UTF-8"?> <settings> <category label="128"> <setting id="quality" type="labelenum" label="100500" values="HD|SD" default="HD" /> </category> </settings>

— (labelenum) (HD SD). label= , , . id="" (. 101 default.py).
settings.xml « XBMC Addon Developers Guide » ( ). , , .
XML \userdata\addon_data\ XBMC. Windows , , %AppData%\XBMC, Linux — $HOME/.xbmc.

\language
\language . , , , , strings.po. \English, \Russian \Ukrainian, . . , . , , \English . , , .

strings.po: # XBMC Media Center language file msgid "" msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: ru\n" msgctxt "#100500" msgid "Quality:" msgstr ":" msgctxt "#100501" msgid "Access error!" msgstr " !" msgctxt "#100502" msgid "Failed to retrieve the feed data." msgstr " ."

, .po GNU Gettext. XBMC Gettext: .mo, , msgctxt. : XBMC XML, www.transifex.net . -, .po , Gettext.
, getLocalizedString() msgctxt. : XBMC + . , . , , , , , . .
, . , .
, settings.xml .
, XBMC, . .

\lib
\lib , . \lib . , : , . . , . , , , , , , .

\thumbnails
\thumbnails cnet.com. \lib, , .


. XBMC. ( XBMC " "). print xbmc.log().
XBMC.
, XBMC Eclipse + PyDev. .


XBMC . , , . «» .
, , , .


, , Wiki XBMC: http://wiki.xbmc.org/index.php?title=Category:Addon_Development
«XBMC Addon Developers Guide»: yadi.sk/d/NvfFuXYw92paL
XBMC Python API: mirrors.xbmc.org/docs/python-docs

PS
Post factum . . XBMC , . .


XBMC : I — .
    executable, . .: 
<extension point="xbmc.python.script" library="default.py"> <provides>executable</provides> </extension>

«». - «», «» «» (video, music image ), - , runtime- ( ).

.

16—22 . XBMC. 23 , .

icon.png
icon.png — () , XBMC. — PNG 256x256 .

fanart.jpg
fanart.jpg — (), . — JPG 1280x720 . : (). , «» , / .

changelog.txt
changelog.txt .

License.txt
License.txt . , . , , XBMC.

default.py
default.py — . «default.py» , ( library= extension), default.py, — , .
- PEP 8, , ____, .

: # -*- coding: utf-8 -*- # Name: plugin.video.cnet # Licence: GPL v.3: http://www.gnu.org/copyleft/gpl.html # import sys, os, urllib2, socket, xml.dom.minidom # XBMC import xbmc, xbmcplugin, xbmcaddon, xbmcgui # _ADDON_NAME = 'plugin.video.cnet' _addon = xbmcaddon.Addon(id=_ADDON_NAME) _addon_id = int(sys.argv[1]) _addon_url = sys.argv[0] _addon_path = _addon.getAddonInfo('path').decode('utf-8') # sys.path.append(os.path.join(_addon_path, 'resources', 'lib')) import feeds # def _string(string_id): return _addon.getLocalizedString(string_id).encode('utf-8') # URL-encoded def get_feed_name(): paramstring = sys.argv[2] if paramstring: feed_name = feeds.NAMES[paramstring.replace('?feed=', '')] else: feed_name = '' return feed_name # RSS- def rss_parser(url): listing = [] try: HEADER = {'User-Agent' : 'Mozilla/5.0 (Windows NT 6.1; rv:18.0) Gecko/20100101 Firefox/20.0'} request = urllib2.Request(url, None, HEADER) rss = xml.dom.minidom.parse(urllib2.urlopen(request, None, 3)) except urllib2.URLError, socket.timeout: pass else: titles = rss.getElementsByTagName('title') links = rss.getElementsByTagName('link') for title, link in zip(titles[2:], links[2:]): title = title.toxml().replace('<title><![CDATA[', '').replace(']]></title>', '') link = link.toxml().replace('<link>', '').replace('</link>', '') listing.append([title, link]) return listing # ( ). def feed_list(addon_id, addon_url, fanart, thumbpath, feeds): names = dict.keys(feeds) for name in names: thumb = os.path.join(thumbpath, feeds[name]['thumb']) # . list_item = xbmcgui.ListItem(name, thumbnailImage=thumb) # . . list_item.setProperty('fanart_image', fanart) # URL, . url = addon_url + '?feed=' + name.replace(' ', '').replace('*', '') # . isFolder=True , (). xbmcplugin.addDirectoryItem(addon_id, url, list_item, isFolder=True) # -- , . xbmcplugin.addSortMethod(addon_id, xbmcplugin.SORT_METHOD_LABEL_IGNORE_THE) # xbmcplugin.endOfDirectory(addon_id) # . switch_view() # . def switch_view(): skin_used = xbmc.getSkinDir() if skin_used == 'skin.confluence': xbmc.executebuiltin('Container.SetViewMode(500)') # "". elif skin_used == 'skin.aeon.nox': xbmc.executebuiltin('Container.SetViewMode(512)') # "-" # . def podcast_list(addon_id, fanart, thumb, listing): for item in listing: # . list_item = xbmcgui.ListItem(item[0], thumbnailImage=thumb) # . . list_item.setProperty('fanart_image', fanart) # . isFolder=False , . xbmcplugin.addDirectoryItem(addon_id, item[1], list_item, isFolder=False) # . xbmcplugin.endOfDirectory(addon_id) def main(): # . thumbpath = os.path.join(_addon_path, 'resources', 'thumbnails') fanart = os.path.join(_addon_path, 'fanart.jpg') # . feed = get_feed_name() # XBMC ( ), . if feed: # . quality = _addon.getSetting('quality') # . listing = rss_parser(url=feeds.FEEDS[feed][quality]) if listing: # . xbmc.log('%s: Started - Opening podcasts List' % _ADDON_NAME, xbmc.LOGNOTICE) thumb = os.path.join(thumbpath, feeds.FEEDS[feed]['thumb']) # . podcast_list(addon_id=_addon_id, fanart=fanart, thumb=thumb, listing=listing) else: # ( ), xbmc.log('%s: Failed to retrieve %s feed data!' % (_ADDON_NAME, feed), xbmc.LOGERROR) # XBMC. xbmc.executebuiltin('Notification(%s,%s)' % (_string(100501), _string(100502))) else: # XBMC ( ), , xbmc.log('%s: Started - Opening feeds List' % _ADDON_NAME, xbmc.LOGNOTICE) # . feed_list(addon_id=_addon_id, addon_url=_addon_url, fanart=fanart, thumbpath=thumbpath, feeds=feeds.FEEDS) if __name__ == '__main__': main()

. .

11—15 — . , , , , PEP 8.

:

11: . , addon.xml, ( ).

12: Addon .

13: runtime- (handle). — , 2- XBMC. addDirectoryItem(). . , - - , - () , XBMC API. , . XBMC ( ) - 3 sys.argv: URL, runtime- (, ), URL-encoded . -, , , sys.argv . , runtime- , addDirectoryItem(), - . URL .

14: URL . URL ''plugin://'' + ( ) + ''/''. URL : "plugin://plugin.video.cnet/". URL ( ) , . , , , XBMC , ( — ) . , , .
(), , . , «», «» «» XBMC « », , , . - , — , — , . , (- ) — XBMC. «» , ( ) . . addDirectoryItem() , . .

15: getAddonInfo('path') , , , , . : Windows, ( , ASCII), , (exeption). , Windows . decode('utf-8'). , , Windows.
getAddonInfo('path') — ( XBMC) . os.path.dirname(__file__) , os.getcwd() . , , , XBMC.

18—19: feeds /resources/lib. cnet.com, URL (dictionary). , , — www.cnet.com/podcasts — , — , , .

22—23: . .getLocalizedString() , UTF-8. , , , , .

26—32: - URL-encoded . - , . , XBMC URL-encoded . URL (. ) URL-encoded .
. , plugin.video.acme, , , . «Action» «Movies» plugin://plugin.video.acme/?=Movies&=Action. addDirectoryItem ( ). , plugin.video.acme, sys.argv[2] «?=Movies&=Action». , , , : XBMC . XBMC (, ), , , (, «Action» «Movies»). .
: , . URL, plugin://.
: xbmcplugin.addDirectoryItem, - , isFolder=False. isFolder=True XBMC , .

35—49: RSS- . XML - . , - (, , ).

52—64: cnet.com — , . .

57: xbmcgui.ListItem. , . , , , () ().

59: . , , .

61: . . , .

63: . , sys.argv[1]. , - . , addDirectoryItem() . isFolder=True XBMC, — , . . , . url , , .

65: . , , .

67: XBMC, .

69: (). , , ( , ), , , , . Container.SetViewMode() , MyVideoNav.xml, MyMusicNav.xml MyPics.xml , .
, . — Youtube.

72—77: . . 500 «» Confluence, 512 — «-» «Aeon-Nox». .

80—89: , . , feed_list(), , URL isFolder False, . . ( ) , .
: isFolder=False , , xbmcplugin.addDirectoryItem. , ( ). xbmcplugin.addDirectoryItem .

92—119: . , . .

101: , . , , .

114: XBMC. _string(), XBMC. .

\resources
\resources — , .

settings.xml
settings.xml — , , XML, , «». «» .

: <?xml version="1.0" encoding="UTF-8"?> <settings> <category label="128"> <setting id="quality" type="labelenum" label="100500" values="HD|SD" default="HD" /> </category> </settings>

— (labelenum) (HD SD). label= , , . id="" (. 101 default.py).
settings.xml « XBMC Addon Developers Guide » ( ). , , .
XML \userdata\addon_data\ XBMC. Windows , , %AppData%\XBMC, Linux — $HOME/.xbmc.

\language
\language . , , , , strings.po. \English, \Russian \Ukrainian, . . , . , , \English . , , .

strings.po: # XBMC Media Center language file msgid "" msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: ru\n" msgctxt "#100500" msgid "Quality:" msgstr ":" msgctxt "#100501" msgid "Access error!" msgstr " !" msgctxt "#100502" msgid "Failed to retrieve the feed data." msgstr " ."

, .po GNU Gettext. XBMC Gettext: .mo, , msgctxt. : XBMC XML, www.transifex.net . -, .po , Gettext.
, getLocalizedString() msgctxt. : XBMC + . , . , , , , , . .
, . , .
, settings.xml .
, XBMC, . .

\lib
\lib , . \lib . , : , . . , . , , , , , , .

\thumbnails
\thumbnails cnet.com. \lib, , .


. XBMC. ( XBMC " "). print xbmc.log().
XBMC.
, XBMC Eclipse + PyDev. .


XBMC . , , . «» .
, , , .


, , Wiki XBMC: http://wiki.xbmc.org/index.php?title=Category:Addon_Development
«XBMC Addon Developers Guide»: yadi.sk/d/NvfFuXYw92paL
XBMC Python API: mirrors.xbmc.org/docs/python-docs

PS
Post factum . . XBMC , . .


XBMC : I — .

executable, . .:
<extension point="xbmc.python.script" library="default.py"> <provides>executable</provides> </extension>

«». - «», «» «» (video, music image ), - , runtime- ( ).

.

16—22 . XBMC. 23 , .

icon.png
icon.png — () , XBMC. — PNG 256x256 .

fanart.jpg
fanart.jpg — (), . — JPG 1280x720 . : (). , «» , / .

changelog.txt
changelog.txt .

License.txt
License.txt . , . , , XBMC.

default.py
default.py — . «default.py» , ( library= extension), default.py, — , .
- PEP 8, , ____, .

: # -*- coding: utf-8 -*- # Name: plugin.video.cnet # Licence: GPL v.3: http://www.gnu.org/copyleft/gpl.html # import sys, os, urllib2, socket, xml.dom.minidom # XBMC import xbmc, xbmcplugin, xbmcaddon, xbmcgui # _ADDON_NAME = 'plugin.video.cnet' _addon = xbmcaddon.Addon(id=_ADDON_NAME) _addon_id = int(sys.argv[1]) _addon_url = sys.argv[0] _addon_path = _addon.getAddonInfo('path').decode('utf-8') # sys.path.append(os.path.join(_addon_path, 'resources', 'lib')) import feeds # def _string(string_id): return _addon.getLocalizedString(string_id).encode('utf-8') # URL-encoded def get_feed_name(): paramstring = sys.argv[2] if paramstring: feed_name = feeds.NAMES[paramstring.replace('?feed=', '')] else: feed_name = '' return feed_name # RSS- def rss_parser(url): listing = [] try: HEADER = {'User-Agent' : 'Mozilla/5.0 (Windows NT 6.1; rv:18.0) Gecko/20100101 Firefox/20.0'} request = urllib2.Request(url, None, HEADER) rss = xml.dom.minidom.parse(urllib2.urlopen(request, None, 3)) except urllib2.URLError, socket.timeout: pass else: titles = rss.getElementsByTagName('title') links = rss.getElementsByTagName('link') for title, link in zip(titles[2:], links[2:]): title = title.toxml().replace('<title><![CDATA[', '').replace(']]></title>', '') link = link.toxml().replace('<link>', '').replace('</link>', '') listing.append([title, link]) return listing # ( ). def feed_list(addon_id, addon_url, fanart, thumbpath, feeds): names = dict.keys(feeds) for name in names: thumb = os.path.join(thumbpath, feeds[name]['thumb']) # . list_item = xbmcgui.ListItem(name, thumbnailImage=thumb) # . . list_item.setProperty('fanart_image', fanart) # URL, . url = addon_url + '?feed=' + name.replace(' ', '').replace('*', '') # . isFolder=True , (). xbmcplugin.addDirectoryItem(addon_id, url, list_item, isFolder=True) # -- , . xbmcplugin.addSortMethod(addon_id, xbmcplugin.SORT_METHOD_LABEL_IGNORE_THE) # xbmcplugin.endOfDirectory(addon_id) # . switch_view() # . def switch_view(): skin_used = xbmc.getSkinDir() if skin_used == 'skin.confluence': xbmc.executebuiltin('Container.SetViewMode(500)') # "". elif skin_used == 'skin.aeon.nox': xbmc.executebuiltin('Container.SetViewMode(512)') # "-" # . def podcast_list(addon_id, fanart, thumb, listing): for item in listing: # . list_item = xbmcgui.ListItem(item[0], thumbnailImage=thumb) # . . list_item.setProperty('fanart_image', fanart) # . isFolder=False , . xbmcplugin.addDirectoryItem(addon_id, item[1], list_item, isFolder=False) # . xbmcplugin.endOfDirectory(addon_id) def main(): # . thumbpath = os.path.join(_addon_path, 'resources', 'thumbnails') fanart = os.path.join(_addon_path, 'fanart.jpg') # . feed = get_feed_name() # XBMC ( ), . if feed: # . quality = _addon.getSetting('quality') # . listing = rss_parser(url=feeds.FEEDS[feed][quality]) if listing: # . xbmc.log('%s: Started - Opening podcasts List' % _ADDON_NAME, xbmc.LOGNOTICE) thumb = os.path.join(thumbpath, feeds.FEEDS[feed]['thumb']) # . podcast_list(addon_id=_addon_id, fanart=fanart, thumb=thumb, listing=listing) else: # ( ), xbmc.log('%s: Failed to retrieve %s feed data!' % (_ADDON_NAME, feed), xbmc.LOGERROR) # XBMC. xbmc.executebuiltin('Notification(%s,%s)' % (_string(100501), _string(100502))) else: # XBMC ( ), , xbmc.log('%s: Started - Opening feeds List' % _ADDON_NAME, xbmc.LOGNOTICE) # . feed_list(addon_id=_addon_id, addon_url=_addon_url, fanart=fanart, thumbpath=thumbpath, feeds=feeds.FEEDS) if __name__ == '__main__': main()

. .

11—15 — . , , , , PEP 8.

:

11: . , addon.xml, ( ).

12: Addon .

13: runtime- (handle). — , 2- XBMC. addDirectoryItem(). . , - - , - () , XBMC API. , . XBMC ( ) - 3 sys.argv: URL, runtime- (, ), URL-encoded . -, , , sys.argv . , runtime- , addDirectoryItem(), - . URL .

14: URL . URL ''plugin://'' + ( ) + ''/''. URL : "plugin://plugin.video.cnet/". URL ( ) , . , , , XBMC , ( — ) . , , .
(), , . , «», «» «» XBMC « », , , . - , — , — , . , (- ) — XBMC. «» , ( ) . . addDirectoryItem() , . .

15: getAddonInfo('path') , , , , . : Windows, ( , ASCII), , (exeption). , Windows . decode('utf-8'). , , Windows.
getAddonInfo('path') — ( XBMC) . os.path.dirname(__file__) , os.getcwd() . , , , XBMC.

18—19: feeds /resources/lib. cnet.com, URL (dictionary). , , — www.cnet.com/podcasts — , — , , .

22—23: . .getLocalizedString() , UTF-8. , , , , .

26—32: - URL-encoded . - , . , XBMC URL-encoded . URL (. ) URL-encoded .
. , plugin.video.acme, , , . «Action» «Movies» plugin://plugin.video.acme/?=Movies&=Action. addDirectoryItem ( ). , plugin.video.acme, sys.argv[2] «?=Movies&=Action». , , , : XBMC . XBMC (, ), , , (, «Action» «Movies»). .
: , . URL, plugin://.
: xbmcplugin.addDirectoryItem, - , isFolder=False. isFolder=True XBMC , .

35—49: RSS- . XML - . , - (, , ).

52—64: cnet.com — , . .

57: xbmcgui.ListItem. , . , , , () ().

59: . , , .

61: . . , .

63: . , sys.argv[1]. , - . , addDirectoryItem() . isFolder=True XBMC, — , . . , . url , , .

65: . , , .

67: XBMC, .

69: (). , , ( , ), , , , . Container.SetViewMode() , MyVideoNav.xml, MyMusicNav.xml MyPics.xml , .
, . — Youtube.

72—77: . . 500 «» Confluence, 512 — «-» «Aeon-Nox». .

80—89: , . , feed_list(), , URL isFolder False, . . ( ) , .
: isFolder=False , , xbmcplugin.addDirectoryItem. , ( ). xbmcplugin.addDirectoryItem .

92—119: . , . .

101: , . , , .

114: XBMC. _string(), XBMC. .

\resources
\resources — , .

settings.xml
settings.xml — , , XML, , «». «» .

: <?xml version="1.0" encoding="UTF-8"?> <settings> <category label="128"> <setting id="quality" type="labelenum" label="100500" values="HD|SD" default="HD" /> </category> </settings>

— (labelenum) (HD SD). label= , , . id="" (. 101 default.py).
settings.xml « XBMC Addon Developers Guide » ( ). , , .
XML \userdata\addon_data\ XBMC. Windows , , %AppData%\XBMC, Linux — $HOME/.xbmc.

\language
\language . , , , , strings.po. \English, \Russian \Ukrainian, . . , . , , \English . , , .

strings.po: # XBMC Media Center language file msgid "" msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: ru\n" msgctxt "#100500" msgid "Quality:" msgstr ":" msgctxt "#100501" msgid "Access error!" msgstr " !" msgctxt "#100502" msgid "Failed to retrieve the feed data." msgstr " ."

, .po GNU Gettext. XBMC Gettext: .mo, , msgctxt. : XBMC XML, www.transifex.net . -, .po , Gettext.
, getLocalizedString() msgctxt. : XBMC + . , . , , , , , . .
, . , .
, settings.xml .
, XBMC, . .

\lib
\lib , . \lib . , : , . . , . , , , , , , .

\thumbnails
\thumbnails cnet.com. \lib, , .


. XBMC. ( XBMC " "). print xbmc.log().
XBMC.
, XBMC Eclipse + PyDev. .


XBMC . , , . «» .
, , , .


, , Wiki XBMC: http://wiki.xbmc.org/index.php?title=Category:Addon_Development
«XBMC Addon Developers Guide»: yadi.sk/d/NvfFuXYw92paL
XBMC Python API: mirrors.xbmc.org/docs/python-docs

PS
Post factum . . XBMC , . .


XBMC : I — .

executable, . .:
<extension point="xbmc.python.script" library="default.py"> <provides>executable</provides> </extension>

«». - «», «» «» (video, music image ), - , runtime- ( ).

.

16—22 . XBMC. 23 , .

icon.png
icon.png — () , XBMC. — PNG 256x256 .

fanart.jpg
fanart.jpg — (), . — JPG 1280x720 . : (). , «» , / .

changelog.txt
changelog.txt .

License.txt
License.txt . , . , , XBMC.

default.py
default.py — . «default.py» , ( library= extension), default.py, — , .
- PEP 8, , ____, .

: # -*- coding: utf-8 -*- # Name: plugin.video.cnet # Licence: GPL v.3: http://www.gnu.org/copyleft/gpl.html # import sys, os, urllib2, socket, xml.dom.minidom # XBMC import xbmc, xbmcplugin, xbmcaddon, xbmcgui # _ADDON_NAME = 'plugin.video.cnet' _addon = xbmcaddon.Addon(id=_ADDON_NAME) _addon_id = int(sys.argv[1]) _addon_url = sys.argv[0] _addon_path = _addon.getAddonInfo('path').decode('utf-8') # sys.path.append(os.path.join(_addon_path, 'resources', 'lib')) import feeds # def _string(string_id): return _addon.getLocalizedString(string_id).encode('utf-8') # URL-encoded def get_feed_name(): paramstring = sys.argv[2] if paramstring: feed_name = feeds.NAMES[paramstring.replace('?feed=', '')] else: feed_name = '' return feed_name # RSS- def rss_parser(url): listing = [] try: HEADER = {'User-Agent' : 'Mozilla/5.0 (Windows NT 6.1; rv:18.0) Gecko/20100101 Firefox/20.0'} request = urllib2.Request(url, None, HEADER) rss = xml.dom.minidom.parse(urllib2.urlopen(request, None, 3)) except urllib2.URLError, socket.timeout: pass else: titles = rss.getElementsByTagName('title') links = rss.getElementsByTagName('link') for title, link in zip(titles[2:], links[2:]): title = title.toxml().replace('<title><![CDATA[', '').replace(']]></title>', '') link = link.toxml().replace('<link>', '').replace('</link>', '') listing.append([title, link]) return listing # ( ). def feed_list(addon_id, addon_url, fanart, thumbpath, feeds): names = dict.keys(feeds) for name in names: thumb = os.path.join(thumbpath, feeds[name]['thumb']) # . list_item = xbmcgui.ListItem(name, thumbnailImage=thumb) # . . list_item.setProperty('fanart_image', fanart) # URL, . url = addon_url + '?feed=' + name.replace(' ', '').replace('*', '') # . isFolder=True , (). xbmcplugin.addDirectoryItem(addon_id, url, list_item, isFolder=True) # -- , . xbmcplugin.addSortMethod(addon_id, xbmcplugin.SORT_METHOD_LABEL_IGNORE_THE) # xbmcplugin.endOfDirectory(addon_id) # . switch_view() # . def switch_view(): skin_used = xbmc.getSkinDir() if skin_used == 'skin.confluence': xbmc.executebuiltin('Container.SetViewMode(500)') # "". elif skin_used == 'skin.aeon.nox': xbmc.executebuiltin('Container.SetViewMode(512)') # "-" # . def podcast_list(addon_id, fanart, thumb, listing): for item in listing: # . list_item = xbmcgui.ListItem(item[0], thumbnailImage=thumb) # . . list_item.setProperty('fanart_image', fanart) # . isFolder=False , . xbmcplugin.addDirectoryItem(addon_id, item[1], list_item, isFolder=False) # . xbmcplugin.endOfDirectory(addon_id) def main(): # . thumbpath = os.path.join(_addon_path, 'resources', 'thumbnails') fanart = os.path.join(_addon_path, 'fanart.jpg') # . feed = get_feed_name() # XBMC ( ), . if feed: # . quality = _addon.getSetting('quality') # . listing = rss_parser(url=feeds.FEEDS[feed][quality]) if listing: # . xbmc.log('%s: Started - Opening podcasts List' % _ADDON_NAME, xbmc.LOGNOTICE) thumb = os.path.join(thumbpath, feeds.FEEDS[feed]['thumb']) # . podcast_list(addon_id=_addon_id, fanart=fanart, thumb=thumb, listing=listing) else: # ( ), xbmc.log('%s: Failed to retrieve %s feed data!' % (_ADDON_NAME, feed), xbmc.LOGERROR) # XBMC. xbmc.executebuiltin('Notification(%s,%s)' % (_string(100501), _string(100502))) else: # XBMC ( ), , xbmc.log('%s: Started - Opening feeds List' % _ADDON_NAME, xbmc.LOGNOTICE) # . feed_list(addon_id=_addon_id, addon_url=_addon_url, fanart=fanart, thumbpath=thumbpath, feeds=feeds.FEEDS) if __name__ == '__main__': main()

. .

11—15 — . , , , , PEP 8.

:

11: . , addon.xml, ( ).

12: Addon .

13: runtime- (handle). — , 2- XBMC. addDirectoryItem(). . , - - , - () , XBMC API. , . XBMC ( ) - 3 sys.argv: URL, runtime- (, ), URL-encoded . -, , , sys.argv . , runtime- , addDirectoryItem(), - . URL .

14: URL . URL ''plugin://'' + ( ) + ''/''. URL : "plugin://plugin.video.cnet/". URL ( ) , . , , , XBMC , ( — ) . , , .
(), , . , «», «» «» XBMC « », , , . - , — , — , . , (- ) — XBMC. «» , ( ) . . addDirectoryItem() , . .

15: getAddonInfo('path') , , , , . : Windows, ( , ASCII), , (exeption). , Windows . decode('utf-8'). , , Windows.
getAddonInfo('path') — ( XBMC) . os.path.dirname(__file__) , os.getcwd() . , , , XBMC.

18—19: feeds /resources/lib. cnet.com, URL (dictionary). , , — www.cnet.com/podcasts — , — , , .

22—23: . .getLocalizedString() , UTF-8. , , , , .

26—32: - URL-encoded . - , . , XBMC URL-encoded . URL (. ) URL-encoded .
. , plugin.video.acme, , , . «Action» «Movies» plugin://plugin.video.acme/?=Movies&=Action. addDirectoryItem ( ). , plugin.video.acme, sys.argv[2] «?=Movies&=Action». , , , : XBMC . XBMC (, ), , , (, «Action» «Movies»). .
: , . URL, plugin://.
: xbmcplugin.addDirectoryItem, - , isFolder=False. isFolder=True XBMC , .

35—49: RSS- . XML - . , - (, , ).

52—64: cnet.com — , . .

57: xbmcgui.ListItem. , . , , , () ().

59: . , , .

61: . . , .

63: . , sys.argv[1]. , - . , addDirectoryItem() . isFolder=True XBMC, — , . . , . url , , .

65: . , , .

67: XBMC, .

69: (). , , ( , ), , , , . Container.SetViewMode() , MyVideoNav.xml, MyMusicNav.xml MyPics.xml , .
, . — Youtube.

72—77: . . 500 «» Confluence, 512 — «-» «Aeon-Nox». .

80—89: , . , feed_list(), , URL isFolder False, . . ( ) , .
: isFolder=False , , xbmcplugin.addDirectoryItem. , ( ). xbmcplugin.addDirectoryItem .

92—119: . , . .

101: , . , , .

114: XBMC. _string(), XBMC. .

\resources
\resources — , .

settings.xml
settings.xml — , , XML, , «». «» .

: <?xml version="1.0" encoding="UTF-8"?> <settings> <category label="128"> <setting id="quality" type="labelenum" label="100500" values="HD|SD" default="HD" /> </category> </settings>

— (labelenum) (HD SD). label= , , . id="" (. 101 default.py).
settings.xml « XBMC Addon Developers Guide » ( ). , , .
XML \userdata\addon_data\ XBMC. Windows , , %AppData%\XBMC, Linux — $HOME/.xbmc.

\language
\language . , , , , strings.po. \English, \Russian \Ukrainian, . . , . , , \English . , , .

strings.po: # XBMC Media Center language file msgid "" msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: ru\n" msgctxt "#100500" msgid "Quality:" msgstr ":" msgctxt "#100501" msgid "Access error!" msgstr " !" msgctxt "#100502" msgid "Failed to retrieve the feed data." msgstr " ."

, .po GNU Gettext. XBMC Gettext: .mo, , msgctxt. : XBMC XML, www.transifex.net . -, .po , Gettext.
, getLocalizedString() msgctxt. : XBMC + . , . , , , , , . .
, . , .
, settings.xml .
, XBMC, . .

\lib
\lib , . \lib . , : , . . , . , , , , , , .

\thumbnails
\thumbnails cnet.com. \lib, , .


. XBMC. ( XBMC " "). print xbmc.log().
XBMC.
, XBMC Eclipse + PyDev. .


XBMC . , , . «» .
, , , .


, , Wiki XBMC: http://wiki.xbmc.org/index.php?title=Category:Addon_Development
«XBMC Addon Developers Guide»: yadi.sk/d/NvfFuXYw92paL
XBMC Python API: mirrors.xbmc.org/docs/python-docs

PS
Post factum . . XBMC , . .


XBMC : I — .

executable, . .:
<extension point="xbmc.python.script" library="default.py"> <provides>executable</provides> </extension>

«». - «», «» «» (video, music image ), - , runtime- ( ).

.

16—22 . XBMC. 23 , .

icon.png
icon.png — () , XBMC. — PNG 256x256 .

fanart.jpg
fanart.jpg — (), . — JPG 1280x720 . : (). , «» , / .

changelog.txt
changelog.txt .

License.txt
License.txt . , . , , XBMC.

default.py
default.py — . «default.py» , ( library= extension), default.py, — , .
- PEP 8, , ____, .

: # -*- coding: utf-8 -*- # Name: plugin.video.cnet # Licence: GPL v.3: http://www.gnu.org/copyleft/gpl.html # import sys, os, urllib2, socket, xml.dom.minidom # XBMC import xbmc, xbmcplugin, xbmcaddon, xbmcgui # _ADDON_NAME = 'plugin.video.cnet' _addon = xbmcaddon.Addon(id=_ADDON_NAME) _addon_id = int(sys.argv[1]) _addon_url = sys.argv[0] _addon_path = _addon.getAddonInfo('path').decode('utf-8') # sys.path.append(os.path.join(_addon_path, 'resources', 'lib')) import feeds # def _string(string_id): return _addon.getLocalizedString(string_id).encode('utf-8') # URL-encoded def get_feed_name(): paramstring = sys.argv[2] if paramstring: feed_name = feeds.NAMES[paramstring.replace('?feed=', '')] else: feed_name = '' return feed_name # RSS- def rss_parser(url): listing = [] try: HEADER = {'User-Agent' : 'Mozilla/5.0 (Windows NT 6.1; rv:18.0) Gecko/20100101 Firefox/20.0'} request = urllib2.Request(url, None, HEADER) rss = xml.dom.minidom.parse(urllib2.urlopen(request, None, 3)) except urllib2.URLError, socket.timeout: pass else: titles = rss.getElementsByTagName('title') links = rss.getElementsByTagName('link') for title, link in zip(titles[2:], links[2:]): title = title.toxml().replace('<title><![CDATA[', '').replace(']]></title>', '') link = link.toxml().replace('<link>', '').replace('</link>', '') listing.append([title, link]) return listing # ( ). def feed_list(addon_id, addon_url, fanart, thumbpath, feeds): names = dict.keys(feeds) for name in names: thumb = os.path.join(thumbpath, feeds[name]['thumb']) # . list_item = xbmcgui.ListItem(name, thumbnailImage=thumb) # . . list_item.setProperty('fanart_image', fanart) # URL, . url = addon_url + '?feed=' + name.replace(' ', '').replace('*', '') # . isFolder=True , (). xbmcplugin.addDirectoryItem(addon_id, url, list_item, isFolder=True) # -- , . xbmcplugin.addSortMethod(addon_id, xbmcplugin.SORT_METHOD_LABEL_IGNORE_THE) # xbmcplugin.endOfDirectory(addon_id) # . switch_view() # . def switch_view(): skin_used = xbmc.getSkinDir() if skin_used == 'skin.confluence': xbmc.executebuiltin('Container.SetViewMode(500)') # "". elif skin_used == 'skin.aeon.nox': xbmc.executebuiltin('Container.SetViewMode(512)') # "-" # . def podcast_list(addon_id, fanart, thumb, listing): for item in listing: # . list_item = xbmcgui.ListItem(item[0], thumbnailImage=thumb) # . . list_item.setProperty('fanart_image', fanart) # . isFolder=False , . xbmcplugin.addDirectoryItem(addon_id, item[1], list_item, isFolder=False) # . xbmcplugin.endOfDirectory(addon_id) def main(): # . thumbpath = os.path.join(_addon_path, 'resources', 'thumbnails') fanart = os.path.join(_addon_path, 'fanart.jpg') # . feed = get_feed_name() # XBMC ( ), . if feed: # . quality = _addon.getSetting('quality') # . listing = rss_parser(url=feeds.FEEDS[feed][quality]) if listing: # . xbmc.log('%s: Started - Opening podcasts List' % _ADDON_NAME, xbmc.LOGNOTICE) thumb = os.path.join(thumbpath, feeds.FEEDS[feed]['thumb']) # . podcast_list(addon_id=_addon_id, fanart=fanart, thumb=thumb, listing=listing) else: # ( ), xbmc.log('%s: Failed to retrieve %s feed data!' % (_ADDON_NAME, feed), xbmc.LOGERROR) # XBMC. xbmc.executebuiltin('Notification(%s,%s)' % (_string(100501), _string(100502))) else: # XBMC ( ), , xbmc.log('%s: Started - Opening feeds List' % _ADDON_NAME, xbmc.LOGNOTICE) # . feed_list(addon_id=_addon_id, addon_url=_addon_url, fanart=fanart, thumbpath=thumbpath, feeds=feeds.FEEDS) if __name__ == '__main__': main()

. .

11—15 — . , , , , PEP 8.

:

11: . , addon.xml, ( ).

12: Addon .

13: runtime- (handle). — , 2- XBMC. addDirectoryItem(). . , - - , - () , XBMC API. , . XBMC ( ) - 3 sys.argv: URL, runtime- (, ), URL-encoded . -, , , sys.argv . , runtime- , addDirectoryItem(), - . URL .

14: URL . URL ''plugin://'' + ( ) + ''/''. URL : "plugin://plugin.video.cnet/". URL ( ) , . , , , XBMC , ( — ) . , , .
(), , . , «», «» «» XBMC « », , , . - , — , — , . , (- ) — XBMC. «» , ( ) . . addDirectoryItem() , . .

15: getAddonInfo('path') , , , , . : Windows, ( , ASCII), , (exeption). , Windows . decode('utf-8'). , , Windows.
getAddonInfo('path') — ( XBMC) . os.path.dirname(__file__) , os.getcwd() . , , , XBMC.

18—19: feeds /resources/lib. cnet.com, URL (dictionary). , , — www.cnet.com/podcasts — , — , , .

22—23: . .getLocalizedString() , UTF-8. , , , , .

26—32: - URL-encoded . - , . , XBMC URL-encoded . URL (. ) URL-encoded .
. , plugin.video.acme, , , . «Action» «Movies» plugin://plugin.video.acme/?=Movies&=Action. addDirectoryItem ( ). , plugin.video.acme, sys.argv[2] «?=Movies&=Action». , , , : XBMC . XBMC (, ), , , (, «Action» «Movies»). .
: , . URL, plugin://.
: xbmcplugin.addDirectoryItem, - , isFolder=False. isFolder=True XBMC , .

35—49: RSS- . XML - . , - (, , ).

52—64: cnet.com — , . .

57: xbmcgui.ListItem. , . , , , () ().

59: . , , .

61: . . , .

63: . , sys.argv[1]. , - . , addDirectoryItem() . isFolder=True XBMC, — , . . , . url , , .

65: . , , .

67: XBMC, .

69: (). , , ( , ), , , , . Container.SetViewMode() , MyVideoNav.xml, MyMusicNav.xml MyPics.xml , .
, . — Youtube.

72—77: . . 500 «» Confluence, 512 — «-» «Aeon-Nox». .

80—89: , . , feed_list(), , URL isFolder False, . . ( ) , .
: isFolder=False , , xbmcplugin.addDirectoryItem. , ( ). xbmcplugin.addDirectoryItem .

92—119: . , . .

101: , . , , .

114: XBMC. _string(), XBMC. .

\resources
\resources — , .

settings.xml
settings.xml — , , XML, , «». «» .

: <?xml version="1.0" encoding="UTF-8"?> <settings> <category label="128"> <setting id="quality" type="labelenum" label="100500" values="HD|SD" default="HD" /> </category> </settings>

— (labelenum) (HD SD). label= , , . id="" (. 101 default.py).
settings.xml « XBMC Addon Developers Guide » ( ). , , .
XML \userdata\addon_data\ XBMC. Windows , , %AppData%\XBMC, Linux — $HOME/.xbmc.

\language
\language . , , , , strings.po. \English, \Russian \Ukrainian, . . , . , , \English . , , .

strings.po: # XBMC Media Center language file msgid "" msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: ru\n" msgctxt "#100500" msgid "Quality:" msgstr ":" msgctxt "#100501" msgid "Access error!" msgstr " !" msgctxt "#100502" msgid "Failed to retrieve the feed data." msgstr " ."

, .po GNU Gettext. XBMC Gettext: .mo, , msgctxt. : XBMC XML, www.transifex.net . -, .po , Gettext.
, getLocalizedString() msgctxt. : XBMC + . , . , , , , , . .
, . , .
, settings.xml .
, XBMC, . .

\lib
\lib , . \lib . , : , . . , . , , , , , , .

\thumbnails
\thumbnails cnet.com. \lib, , .


. XBMC. ( XBMC " "). print xbmc.log().
XBMC.
, XBMC Eclipse + PyDev. .


XBMC . , , . «» .
, , , .


, , Wiki XBMC: http://wiki.xbmc.org/index.php?title=Category:Addon_Development
«XBMC Addon Developers Guide»: yadi.sk/d/NvfFuXYw92paL
XBMC Python API: mirrors.xbmc.org/docs/python-docs

PS
Post factum . . XBMC , . .


XBMC : I — .

executable, . .:
<extension point="xbmc.python.script" library="default.py"> <provides>executable</provides> </extension>

«». - «», «» «» (video, music image ), - , runtime- ( ).

.

16—22 . XBMC. 23 , .

icon.png
icon.png — () , XBMC. — PNG 256x256 .

fanart.jpg
fanart.jpg — (), . — JPG 1280x720 . : (). , «» , / .

changelog.txt
changelog.txt .

License.txt
License.txt . , . , , XBMC.

default.py
default.py — . «default.py» , ( library= extension), default.py, — , .
- PEP 8, , ____, .

: # -*- coding: utf-8 -*- # Name: plugin.video.cnet # Licence: GPL v.3: http://www.gnu.org/copyleft/gpl.html # import sys, os, urllib2, socket, xml.dom.minidom # XBMC import xbmc, xbmcplugin, xbmcaddon, xbmcgui # _ADDON_NAME = 'plugin.video.cnet' _addon = xbmcaddon.Addon(id=_ADDON_NAME) _addon_id = int(sys.argv[1]) _addon_url = sys.argv[0] _addon_path = _addon.getAddonInfo('path').decode('utf-8') # sys.path.append(os.path.join(_addon_path, 'resources', 'lib')) import feeds # def _string(string_id): return _addon.getLocalizedString(string_id).encode('utf-8') # URL-encoded def get_feed_name(): paramstring = sys.argv[2] if paramstring: feed_name = feeds.NAMES[paramstring.replace('?feed=', '')] else: feed_name = '' return feed_name # RSS- def rss_parser(url): listing = [] try: HEADER = {'User-Agent' : 'Mozilla/5.0 (Windows NT 6.1; rv:18.0) Gecko/20100101 Firefox/20.0'} request = urllib2.Request(url, None, HEADER) rss = xml.dom.minidom.parse(urllib2.urlopen(request, None, 3)) except urllib2.URLError, socket.timeout: pass else: titles = rss.getElementsByTagName('title') links = rss.getElementsByTagName('link') for title, link in zip(titles[2:], links[2:]): title = title.toxml().replace('<title><![CDATA[', '').replace(']]></title>', '') link = link.toxml().replace('<link>', '').replace('</link>', '') listing.append([title, link]) return listing # ( ). def feed_list(addon_id, addon_url, fanart, thumbpath, feeds): names = dict.keys(feeds) for name in names: thumb = os.path.join(thumbpath, feeds[name]['thumb']) # . list_item = xbmcgui.ListItem(name, thumbnailImage=thumb) # . . list_item.setProperty('fanart_image', fanart) # URL, . url = addon_url + '?feed=' + name.replace(' ', '').replace('*', '') # . isFolder=True , (). xbmcplugin.addDirectoryItem(addon_id, url, list_item, isFolder=True) # -- , . xbmcplugin.addSortMethod(addon_id, xbmcplugin.SORT_METHOD_LABEL_IGNORE_THE) # xbmcplugin.endOfDirectory(addon_id) # . switch_view() # . def switch_view(): skin_used = xbmc.getSkinDir() if skin_used == 'skin.confluence': xbmc.executebuiltin('Container.SetViewMode(500)') # "". elif skin_used == 'skin.aeon.nox': xbmc.executebuiltin('Container.SetViewMode(512)') # "-" # . def podcast_list(addon_id, fanart, thumb, listing): for item in listing: # . list_item = xbmcgui.ListItem(item[0], thumbnailImage=thumb) # . . list_item.setProperty('fanart_image', fanart) # . isFolder=False , . xbmcplugin.addDirectoryItem(addon_id, item[1], list_item, isFolder=False) # . xbmcplugin.endOfDirectory(addon_id) def main(): # . thumbpath = os.path.join(_addon_path, 'resources', 'thumbnails') fanart = os.path.join(_addon_path, 'fanart.jpg') # . feed = get_feed_name() # XBMC ( ), . if feed: # . quality = _addon.getSetting('quality') # . listing = rss_parser(url=feeds.FEEDS[feed][quality]) if listing: # . xbmc.log('%s: Started - Opening podcasts List' % _ADDON_NAME, xbmc.LOGNOTICE) thumb = os.path.join(thumbpath, feeds.FEEDS[feed]['thumb']) # . podcast_list(addon_id=_addon_id, fanart=fanart, thumb=thumb, listing=listing) else: # ( ), xbmc.log('%s: Failed to retrieve %s feed data!' % (_ADDON_NAME, feed), xbmc.LOGERROR) # XBMC. xbmc.executebuiltin('Notification(%s,%s)' % (_string(100501), _string(100502))) else: # XBMC ( ), , xbmc.log('%s: Started - Opening feeds List' % _ADDON_NAME, xbmc.LOGNOTICE) # . feed_list(addon_id=_addon_id, addon_url=_addon_url, fanart=fanart, thumbpath=thumbpath, feeds=feeds.FEEDS) if __name__ == '__main__': main()

. .

11—15 — . , , , , PEP 8.

:

11: . , addon.xml, ( ).

12: Addon .

13: runtime- (handle). — , 2- XBMC. addDirectoryItem(). . , - - , - () , XBMC API. , . XBMC ( ) - 3 sys.argv: URL, runtime- (, ), URL-encoded . -, , , sys.argv . , runtime- , addDirectoryItem(), - . URL .

14: URL . URL ''plugin://'' + ( ) + ''/''. URL : "plugin://plugin.video.cnet/". URL ( ) , . , , , XBMC , ( — ) . , , .
(), , . , «», «» «» XBMC « », , , . - , — , — , . , (- ) — XBMC. «» , ( ) . . addDirectoryItem() , . .

15: getAddonInfo('path') , , , , . : Windows, ( , ASCII), , (exeption). , Windows . decode('utf-8'). , , Windows.
getAddonInfo('path') — ( XBMC) . os.path.dirname(__file__) , os.getcwd() . , , , XBMC.

18—19: feeds /resources/lib. cnet.com, URL (dictionary). , , — www.cnet.com/podcasts — , — , , .

22—23: . .getLocalizedString() , UTF-8. , , , , .

26—32: - URL-encoded . - , . , XBMC URL-encoded . URL (. ) URL-encoded .
. , plugin.video.acme, , , . «Action» «Movies» plugin://plugin.video.acme/?=Movies&=Action. addDirectoryItem ( ). , plugin.video.acme, sys.argv[2] «?=Movies&=Action». , , , : XBMC . XBMC (, ), , , (, «Action» «Movies»). .
: , . URL, plugin://.
: xbmcplugin.addDirectoryItem, - , isFolder=False. isFolder=True XBMC , .

35—49: RSS- . XML - . , - (, , ).

52—64: cnet.com — , . .

57: xbmcgui.ListItem. , . , , , () ().

59: . , , .

61: . . , .

63: . , sys.argv[1]. , - . , addDirectoryItem() . isFolder=True XBMC, — , . . , . url , , .

65: . , , .

67: XBMC, .

69: (). , , ( , ), , , , . Container.SetViewMode() , MyVideoNav.xml, MyMusicNav.xml MyPics.xml , .
, . — Youtube.

72—77: . . 500 «» Confluence, 512 — «-» «Aeon-Nox». .

80—89: , . , feed_list(), , URL isFolder False, . . ( ) , .
: isFolder=False , , xbmcplugin.addDirectoryItem. , ( ). xbmcplugin.addDirectoryItem .

92—119: . , . .

101: , . , , .

114: XBMC. _string(), XBMC. .

\resources
\resources — , .

settings.xml
settings.xml — , , XML, , «». «» .

: <?xml version="1.0" encoding="UTF-8"?> <settings> <category label="128"> <setting id="quality" type="labelenum" label="100500" values="HD|SD" default="HD" /> </category> </settings>

— (labelenum) (HD SD). label= , , . id="" (. 101 default.py).
settings.xml « XBMC Addon Developers Guide » ( ). , , .
XML \userdata\addon_data\ XBMC. Windows , , %AppData%\XBMC, Linux — $HOME/.xbmc.

\language
\language . , , , , strings.po. \English, \Russian \Ukrainian, . . , . , , \English . , , .

strings.po: # XBMC Media Center language file msgid "" msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: ru\n" msgctxt "#100500" msgid "Quality:" msgstr ":" msgctxt "#100501" msgid "Access error!" msgstr " !" msgctxt "#100502" msgid "Failed to retrieve the feed data." msgstr " ."

, .po GNU Gettext. XBMC Gettext: .mo, , msgctxt. : XBMC XML, www.transifex.net . -, .po , Gettext.
, getLocalizedString() msgctxt. : XBMC + . , . , , , , , . .
, . , .
, settings.xml .
, XBMC, . .

\lib
\lib , . \lib . , : , . . , . , , , , , , .

\thumbnails
\thumbnails cnet.com. \lib, , .


. XBMC. ( XBMC " "). print xbmc.log().
XBMC.
, XBMC Eclipse + PyDev. .


XBMC . , , . «» .
, , , .


, , Wiki XBMC: http://wiki.xbmc.org/index.php?title=Category:Addon_Development
«XBMC Addon Developers Guide»: yadi.sk/d/NvfFuXYw92paL
XBMC Python API: mirrors.xbmc.org/docs/python-docs

PS
Post factum . . XBMC , . .


XBMC : I — .

executable, . .:
<extension point="xbmc.python.script" library="default.py"> <provides>executable</provides> </extension>

«». - «», «» «» (video, music image ), - , runtime- ( ).

.

16—22 . XBMC. 23 , .

icon.png
icon.png — () , XBMC. — PNG 256x256 .

fanart.jpg
fanart.jpg — (), . — JPG 1280x720 . : (). , «» , / .

changelog.txt
changelog.txt .

License.txt
License.txt . , . , , XBMC.

default.py
default.py — . «default.py» , ( library= extension), default.py, — , .
- PEP 8, , ____, .

: # -*- coding: utf-8 -*- # Name: plugin.video.cnet # Licence: GPL v.3: http://www.gnu.org/copyleft/gpl.html # import sys, os, urllib2, socket, xml.dom.minidom # XBMC import xbmc, xbmcplugin, xbmcaddon, xbmcgui # _ADDON_NAME = 'plugin.video.cnet' _addon = xbmcaddon.Addon(id=_ADDON_NAME) _addon_id = int(sys.argv[1]) _addon_url = sys.argv[0] _addon_path = _addon.getAddonInfo('path').decode('utf-8') # sys.path.append(os.path.join(_addon_path, 'resources', 'lib')) import feeds # def _string(string_id): return _addon.getLocalizedString(string_id).encode('utf-8') # URL-encoded def get_feed_name(): paramstring = sys.argv[2] if paramstring: feed_name = feeds.NAMES[paramstring.replace('?feed=', '')] else: feed_name = '' return feed_name # RSS- def rss_parser(url): listing = [] try: HEADER = {'User-Agent' : 'Mozilla/5.0 (Windows NT 6.1; rv:18.0) Gecko/20100101 Firefox/20.0'} request = urllib2.Request(url, None, HEADER) rss = xml.dom.minidom.parse(urllib2.urlopen(request, None, 3)) except urllib2.URLError, socket.timeout: pass else: titles = rss.getElementsByTagName('title') links = rss.getElementsByTagName('link') for title, link in zip(titles[2:], links[2:]): title = title.toxml().replace('<title><![CDATA[', '').replace(']]></title>', '') link = link.toxml().replace('<link>', '').replace('</link>', '') listing.append([title, link]) return listing # ( ). def feed_list(addon_id, addon_url, fanart, thumbpath, feeds): names = dict.keys(feeds) for name in names: thumb = os.path.join(thumbpath, feeds[name]['thumb']) # . list_item = xbmcgui.ListItem(name, thumbnailImage=thumb) # . . list_item.setProperty('fanart_image', fanart) # URL, . url = addon_url + '?feed=' + name.replace(' ', '').replace('*', '') # . isFolder=True , (). xbmcplugin.addDirectoryItem(addon_id, url, list_item, isFolder=True) # -- , . xbmcplugin.addSortMethod(addon_id, xbmcplugin.SORT_METHOD_LABEL_IGNORE_THE) # xbmcplugin.endOfDirectory(addon_id) # . switch_view() # . def switch_view(): skin_used = xbmc.getSkinDir() if skin_used == 'skin.confluence': xbmc.executebuiltin('Container.SetViewMode(500)') # "". elif skin_used == 'skin.aeon.nox': xbmc.executebuiltin('Container.SetViewMode(512)') # "-" # . def podcast_list(addon_id, fanart, thumb, listing): for item in listing: # . list_item = xbmcgui.ListItem(item[0], thumbnailImage=thumb) # . . list_item.setProperty('fanart_image', fanart) # . isFolder=False , . xbmcplugin.addDirectoryItem(addon_id, item[1], list_item, isFolder=False) # . xbmcplugin.endOfDirectory(addon_id) def main(): # . thumbpath = os.path.join(_addon_path, 'resources', 'thumbnails') fanart = os.path.join(_addon_path, 'fanart.jpg') # . feed = get_feed_name() # XBMC ( ), . if feed: # . quality = _addon.getSetting('quality') # . listing = rss_parser(url=feeds.FEEDS[feed][quality]) if listing: # . xbmc.log('%s: Started - Opening podcasts List' % _ADDON_NAME, xbmc.LOGNOTICE) thumb = os.path.join(thumbpath, feeds.FEEDS[feed]['thumb']) # . podcast_list(addon_id=_addon_id, fanart=fanart, thumb=thumb, listing=listing) else: # ( ), xbmc.log('%s: Failed to retrieve %s feed data!' % (_ADDON_NAME, feed), xbmc.LOGERROR) # XBMC. xbmc.executebuiltin('Notification(%s,%s)' % (_string(100501), _string(100502))) else: # XBMC ( ), , xbmc.log('%s: Started - Opening feeds List' % _ADDON_NAME, xbmc.LOGNOTICE) # . feed_list(addon_id=_addon_id, addon_url=_addon_url, fanart=fanart, thumbpath=thumbpath, feeds=feeds.FEEDS) if __name__ == '__main__': main()

. .

11—15 — . , , , , PEP 8.

:

11: . , addon.xml, ( ).

12: Addon .

13: runtime- (handle). — , 2- XBMC. addDirectoryItem(). . , - - , - () , XBMC API. , . XBMC ( ) - 3 sys.argv: URL, runtime- (, ), URL-encoded . -, , , sys.argv . , runtime- , addDirectoryItem(), - . URL .

14: URL . URL ''plugin://'' + ( ) + ''/''. URL : "plugin://plugin.video.cnet/". URL ( ) , . , , , XBMC , ( — ) . , , .
(), , . , «», «» «» XBMC « », , , . - , — , — , . , (- ) — XBMC. «» , ( ) . . addDirectoryItem() , . .

15: getAddonInfo('path') , , , , . : Windows, ( , ASCII), , (exeption). , Windows . decode('utf-8'). , , Windows.
getAddonInfo('path') — ( XBMC) . os.path.dirname(__file__) , os.getcwd() . , , , XBMC.

18—19: feeds /resources/lib. cnet.com, URL (dictionary). , , — www.cnet.com/podcasts — , — , , .

22—23: . .getLocalizedString() , UTF-8. , , , , .

26—32: - URL-encoded . - , . , XBMC URL-encoded . URL (. ) URL-encoded .
. , plugin.video.acme, , , . «Action» «Movies» plugin://plugin.video.acme/?=Movies&=Action. addDirectoryItem ( ). , plugin.video.acme, sys.argv[2] «?=Movies&=Action». , , , : XBMC . XBMC (, ), , , (, «Action» «Movies»). .
: , . URL, plugin://.
: xbmcplugin.addDirectoryItem, - , isFolder=False. isFolder=True XBMC , .

35—49: RSS- . XML - . , - (, , ).

52—64: cnet.com — , . .

57: xbmcgui.ListItem. , . , , , () ().

59: . , , .

61: . . , .

63: . , sys.argv[1]. , - . , addDirectoryItem() . isFolder=True XBMC, — , . . , . url , , .

65: . , , .

67: XBMC, .

69: (). , , ( , ), , , , . Container.SetViewMode() , MyVideoNav.xml, MyMusicNav.xml MyPics.xml , .
, . — Youtube.

72—77: . . 500 «» Confluence, 512 — «-» «Aeon-Nox». .

80—89: , . , feed_list(), , URL isFolder False, . . ( ) , .
: isFolder=False , , xbmcplugin.addDirectoryItem. , ( ). xbmcplugin.addDirectoryItem .

92—119: . , . .

101: , . , , .

114: XBMC. _string(), XBMC. .

\resources
\resources — , .

settings.xml
settings.xml — , , XML, , «». «» .

: <?xml version="1.0" encoding="UTF-8"?> <settings> <category label="128"> <setting id="quality" type="labelenum" label="100500" values="HD|SD" default="HD" /> </category> </settings>

— (labelenum) (HD SD). label= , , . id="" (. 101 default.py).
settings.xml « XBMC Addon Developers Guide » ( ). , , .
XML \userdata\addon_data\ XBMC. Windows , , %AppData%\XBMC, Linux — $HOME/.xbmc.

\language
\language . , , , , strings.po. \English, \Russian \Ukrainian, . . , . , , \English . , , .

strings.po: # XBMC Media Center language file msgid "" msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: ru\n" msgctxt "#100500" msgid "Quality:" msgstr ":" msgctxt "#100501" msgid "Access error!" msgstr " !" msgctxt "#100502" msgid "Failed to retrieve the feed data." msgstr " ."

, .po GNU Gettext. XBMC Gettext: .mo, , msgctxt. : XBMC XML, www.transifex.net . -, .po , Gettext.
, getLocalizedString() msgctxt. : XBMC + . , . , , , , , . .
, . , .
, settings.xml .
, XBMC, . .

\lib
\lib , . \lib . , : , . . , . , , , , , , .

\thumbnails
\thumbnails cnet.com. \lib, , .


. XBMC. ( XBMC " "). print xbmc.log().
XBMC.
, XBMC Eclipse + PyDev. .


XBMC . , , . «» .
, , , .


, , Wiki XBMC: http://wiki.xbmc.org/index.php?title=Category:Addon_Development
«XBMC Addon Developers Guide»: yadi.sk/d/NvfFuXYw92paL
XBMC Python API: mirrors.xbmc.org/docs/python-docs

PS
Post factum . . XBMC , . .


XBMC : I — .

executable, . .:
<extension point="xbmc.python.script" library="default.py"> <provides>executable</provides> </extension>

«». - «», «» «» (video, music image ), - , runtime- ( ).

.

16—22 . XBMC. 23 , .

icon.png
icon.png — () , XBMC. — PNG 256x256 .

fanart.jpg
fanart.jpg — (), . — JPG 1280x720 . : (). , «» , / .

changelog.txt
changelog.txt .

License.txt
License.txt . , . , , XBMC.

default.py
default.py — . «default.py» , ( library= extension), default.py, — , .
- PEP 8, , ____, .

: # -*- coding: utf-8 -*- # Name: plugin.video.cnet # Licence: GPL v.3: http://www.gnu.org/copyleft/gpl.html # import sys, os, urllib2, socket, xml.dom.minidom # XBMC import xbmc, xbmcplugin, xbmcaddon, xbmcgui # _ADDON_NAME = 'plugin.video.cnet' _addon = xbmcaddon.Addon(id=_ADDON_NAME) _addon_id = int(sys.argv[1]) _addon_url = sys.argv[0] _addon_path = _addon.getAddonInfo('path').decode('utf-8') # sys.path.append(os.path.join(_addon_path, 'resources', 'lib')) import feeds # def _string(string_id): return _addon.getLocalizedString(string_id).encode('utf-8') # URL-encoded def get_feed_name(): paramstring = sys.argv[2] if paramstring: feed_name = feeds.NAMES[paramstring.replace('?feed=', '')] else: feed_name = '' return feed_name # RSS- def rss_parser(url): listing = [] try: HEADER = {'User-Agent' : 'Mozilla/5.0 (Windows NT 6.1; rv:18.0) Gecko/20100101 Firefox/20.0'} request = urllib2.Request(url, None, HEADER) rss = xml.dom.minidom.parse(urllib2.urlopen(request, None, 3)) except urllib2.URLError, socket.timeout: pass else: titles = rss.getElementsByTagName('title') links = rss.getElementsByTagName('link') for title, link in zip(titles[2:], links[2:]): title = title.toxml().replace('<title><![CDATA[', '').replace(']]></title>', '') link = link.toxml().replace('<link>', '').replace('</link>', '') listing.append([title, link]) return listing # ( ). def feed_list(addon_id, addon_url, fanart, thumbpath, feeds): names = dict.keys(feeds) for name in names: thumb = os.path.join(thumbpath, feeds[name]['thumb']) # . list_item = xbmcgui.ListItem(name, thumbnailImage=thumb) # . . list_item.setProperty('fanart_image', fanart) # URL, . url = addon_url + '?feed=' + name.replace(' ', '').replace('*', '') # . isFolder=True , (). xbmcplugin.addDirectoryItem(addon_id, url, list_item, isFolder=True) # -- , . xbmcplugin.addSortMethod(addon_id, xbmcplugin.SORT_METHOD_LABEL_IGNORE_THE) # xbmcplugin.endOfDirectory(addon_id) # . switch_view() # . def switch_view(): skin_used = xbmc.getSkinDir() if skin_used == 'skin.confluence': xbmc.executebuiltin('Container.SetViewMode(500)') # "". elif skin_used == 'skin.aeon.nox': xbmc.executebuiltin('Container.SetViewMode(512)') # "-" # . def podcast_list(addon_id, fanart, thumb, listing): for item in listing: # . list_item = xbmcgui.ListItem(item[0], thumbnailImage=thumb) # . . list_item.setProperty('fanart_image', fanart) # . isFolder=False , . xbmcplugin.addDirectoryItem(addon_id, item[1], list_item, isFolder=False) # . xbmcplugin.endOfDirectory(addon_id) def main(): # . thumbpath = os.path.join(_addon_path, 'resources', 'thumbnails') fanart = os.path.join(_addon_path, 'fanart.jpg') # . feed = get_feed_name() # XBMC ( ), . if feed: # . quality = _addon.getSetting('quality') # . listing = rss_parser(url=feeds.FEEDS[feed][quality]) if listing: # . xbmc.log('%s: Started - Opening podcasts List' % _ADDON_NAME, xbmc.LOGNOTICE) thumb = os.path.join(thumbpath, feeds.FEEDS[feed]['thumb']) # . podcast_list(addon_id=_addon_id, fanart=fanart, thumb=thumb, listing=listing) else: # ( ), xbmc.log('%s: Failed to retrieve %s feed data!' % (_ADDON_NAME, feed), xbmc.LOGERROR) # XBMC. xbmc.executebuiltin('Notification(%s,%s)' % (_string(100501), _string(100502))) else: # XBMC ( ), , xbmc.log('%s: Started - Opening feeds List' % _ADDON_NAME, xbmc.LOGNOTICE) # . feed_list(addon_id=_addon_id, addon_url=_addon_url, fanart=fanart, thumbpath=thumbpath, feeds=feeds.FEEDS) if __name__ == '__main__': main()

. .

11—15 — . , , , , PEP 8.

:

11: . , addon.xml, ( ).

12: Addon .

13: runtime- (handle). — , 2- XBMC. addDirectoryItem(). . , - - , - () , XBMC API. , . XBMC ( ) - 3 sys.argv: URL, runtime- (, ), URL-encoded . -, , , sys.argv . , runtime- , addDirectoryItem(), - . URL .

14: URL . URL ''plugin://'' + ( ) + ''/''. URL : "plugin://plugin.video.cnet/". URL ( ) , . , , , XBMC , ( — ) . , , .
(), , . , «», «» «» XBMC « », , , . - , — , — , . , (- ) — XBMC. «» , ( ) . . addDirectoryItem() , . .

15: getAddonInfo('path') , , , , . : Windows, ( , ASCII), , (exeption). , Windows . decode('utf-8'). , , Windows.
getAddonInfo('path') — ( XBMC) . os.path.dirname(__file__) , os.getcwd() . , , , XBMC.

18—19: feeds /resources/lib. cnet.com, URL (dictionary). , , — www.cnet.com/podcasts — , — , , .

22—23: . .getLocalizedString() , UTF-8. , , , , .

26—32: - URL-encoded . - , . , XBMC URL-encoded . URL (. ) URL-encoded .
. , plugin.video.acme, , , . «Action» «Movies» plugin://plugin.video.acme/?=Movies&=Action. addDirectoryItem ( ). , plugin.video.acme, sys.argv[2] «?=Movies&=Action». , , , : XBMC . XBMC (, ), , , (, «Action» «Movies»). .
: , . URL, plugin://.
: xbmcplugin.addDirectoryItem, - , isFolder=False. isFolder=True XBMC , .

35—49: RSS- . XML - . , - (, , ).

52—64: cnet.com — , . .

57: xbmcgui.ListItem. , . , , , () ().

59: . , , .

61: . . , .

63: . , sys.argv[1]. , - . , addDirectoryItem() . isFolder=True XBMC, — , . . , . url , , .

65: . , , .

67: XBMC, .

69: (). , , ( , ), , , , . Container.SetViewMode() , MyVideoNav.xml, MyMusicNav.xml MyPics.xml , .
, . — Youtube.

72—77: . . 500 «» Confluence, 512 — «-» «Aeon-Nox». .

80—89: , . , feed_list(), , URL isFolder False, . . ( ) , .
: isFolder=False , , xbmcplugin.addDirectoryItem. , ( ). xbmcplugin.addDirectoryItem .

92—119: . , . .

101: , . , , .

114: XBMC. _string(), XBMC. .

\resources
\resources — , .

settings.xml
settings.xml — , , XML, , «». «» .

: <?xml version="1.0" encoding="UTF-8"?> <settings> <category label="128"> <setting id="quality" type="labelenum" label="100500" values="HD|SD" default="HD" /> </category> </settings>

— (labelenum) (HD SD). label= , , . id="" (. 101 default.py).
settings.xml « XBMC Addon Developers Guide » ( ). , , .
XML \userdata\addon_data\ XBMC. Windows , , %AppData%\XBMC, Linux — $HOME/.xbmc.

\language
\language . , , , , strings.po. \English, \Russian \Ukrainian, . . , . , , \English . , , .

strings.po: # XBMC Media Center language file msgid "" msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: ru\n" msgctxt "#100500" msgid "Quality:" msgstr ":" msgctxt "#100501" msgid "Access error!" msgstr " !" msgctxt "#100502" msgid "Failed to retrieve the feed data." msgstr " ."

, .po GNU Gettext. XBMC Gettext: .mo, , msgctxt. : XBMC XML, www.transifex.net . -, .po , Gettext.
, getLocalizedString() msgctxt. : XBMC + . , . , , , , , . .
, . , .
, settings.xml .
, XBMC, . .

\lib
\lib , . \lib . , : , . . , . , , , , , , .

\thumbnails
\thumbnails cnet.com. \lib, , .


. XBMC. ( XBMC " "). print xbmc.log().
XBMC.
, XBMC Eclipse + PyDev. .


XBMC . , , . «» .
, , , .


, , Wiki XBMC: http://wiki.xbmc.org/index.php?title=Category:Addon_Development
«XBMC Addon Developers Guide»: yadi.sk/d/NvfFuXYw92paL
XBMC Python API: mirrors.xbmc.org/docs/python-docs

PS
Post factum . . XBMC , . .


XBMC : I — .

executable, . .:
<extension point="xbmc.python.script" library="default.py"> <provides>executable</provides> </extension>

«». - «», «» «» (video, music image ), - , runtime- ( ).

.

16—22 . XBMC. 23 , .

icon.png
icon.png — () , XBMC. — PNG 256x256 .

fanart.jpg
fanart.jpg — (), . — JPG 1280x720 . : (). , «» , / .

changelog.txt
changelog.txt .

License.txt
License.txt . , . , , XBMC.

default.py
default.py — . «default.py» , ( library= extension), default.py, — , .
- PEP 8, , ____, .

: # -*- coding: utf-8 -*- # Name: plugin.video.cnet # Licence: GPL v.3: http://www.gnu.org/copyleft/gpl.html # import sys, os, urllib2, socket, xml.dom.minidom # XBMC import xbmc, xbmcplugin, xbmcaddon, xbmcgui # _ADDON_NAME = 'plugin.video.cnet' _addon = xbmcaddon.Addon(id=_ADDON_NAME) _addon_id = int(sys.argv[1]) _addon_url = sys.argv[0] _addon_path = _addon.getAddonInfo('path').decode('utf-8') # sys.path.append(os.path.join(_addon_path, 'resources', 'lib')) import feeds # def _string(string_id): return _addon.getLocalizedString(string_id).encode('utf-8') # URL-encoded def get_feed_name(): paramstring = sys.argv[2] if paramstring: feed_name = feeds.NAMES[paramstring.replace('?feed=', '')] else: feed_name = '' return feed_name # RSS- def rss_parser(url): listing = [] try: HEADER = {'User-Agent' : 'Mozilla/5.0 (Windows NT 6.1; rv:18.0) Gecko/20100101 Firefox/20.0'} request = urllib2.Request(url, None, HEADER) rss = xml.dom.minidom.parse(urllib2.urlopen(request, None, 3)) except urllib2.URLError, socket.timeout: pass else: titles = rss.getElementsByTagName('title') links = rss.getElementsByTagName('link') for title, link in zip(titles[2:], links[2:]): title = title.toxml().replace('<title><![CDATA[', '').replace(']]></title>', '') link = link.toxml().replace('<link>', '').replace('</link>', '') listing.append([title, link]) return listing # ( ). def feed_list(addon_id, addon_url, fanart, thumbpath, feeds): names = dict.keys(feeds) for name in names: thumb = os.path.join(thumbpath, feeds[name]['thumb']) # . list_item = xbmcgui.ListItem(name, thumbnailImage=thumb) # . . list_item.setProperty('fanart_image', fanart) # URL, . url = addon_url + '?feed=' + name.replace(' ', '').replace('*', '') # . isFolder=True , (). xbmcplugin.addDirectoryItem(addon_id, url, list_item, isFolder=True) # -- , . xbmcplugin.addSortMethod(addon_id, xbmcplugin.SORT_METHOD_LABEL_IGNORE_THE) # xbmcplugin.endOfDirectory(addon_id) # . switch_view() # . def switch_view(): skin_used = xbmc.getSkinDir() if skin_used == 'skin.confluence': xbmc.executebuiltin('Container.SetViewMode(500)') # "". elif skin_used == 'skin.aeon.nox': xbmc.executebuiltin('Container.SetViewMode(512)') # "-" # . def podcast_list(addon_id, fanart, thumb, listing): for item in listing: # . list_item = xbmcgui.ListItem(item[0], thumbnailImage=thumb) # . . list_item.setProperty('fanart_image', fanart) # . isFolder=False , . xbmcplugin.addDirectoryItem(addon_id, item[1], list_item, isFolder=False) # . xbmcplugin.endOfDirectory(addon_id) def main(): # . thumbpath = os.path.join(_addon_path, 'resources', 'thumbnails') fanart = os.path.join(_addon_path, 'fanart.jpg') # . feed = get_feed_name() # XBMC ( ), . if feed: # . quality = _addon.getSetting('quality') # . listing = rss_parser(url=feeds.FEEDS[feed][quality]) if listing: # . xbmc.log('%s: Started - Opening podcasts List' % _ADDON_NAME, xbmc.LOGNOTICE) thumb = os.path.join(thumbpath, feeds.FEEDS[feed]['thumb']) # . podcast_list(addon_id=_addon_id, fanart=fanart, thumb=thumb, listing=listing) else: # ( ), xbmc.log('%s: Failed to retrieve %s feed data!' % (_ADDON_NAME, feed), xbmc.LOGERROR) # XBMC. xbmc.executebuiltin('Notification(%s,%s)' % (_string(100501), _string(100502))) else: # XBMC ( ), , xbmc.log('%s: Started - Opening feeds List' % _ADDON_NAME, xbmc.LOGNOTICE) # . feed_list(addon_id=_addon_id, addon_url=_addon_url, fanart=fanart, thumbpath=thumbpath, feeds=feeds.FEEDS) if __name__ == '__main__': main()

. .

11—15 — . , , , , PEP 8.

:

11: . , addon.xml, ( ).

12: Addon .

13: runtime- (handle). — , 2- XBMC. addDirectoryItem(). . , - - , - () , XBMC API. , . XBMC ( ) - 3 sys.argv: URL, runtime- (, ), URL-encoded . -, , , sys.argv . , runtime- , addDirectoryItem(), - . URL .

14: URL . URL ''plugin://'' + ( ) + ''/''. URL : "plugin://plugin.video.cnet/". URL ( ) , . , , , XBMC , ( — ) . , , .
(), , . , «», «» «» XBMC « », , , . - , — , — , . , (- ) — XBMC. «» , ( ) . . addDirectoryItem() , . .

15: getAddonInfo('path') , , , , . : Windows, ( , ASCII), , (exeption). , Windows . decode('utf-8'). , , Windows.
getAddonInfo('path') — ( XBMC) . os.path.dirname(__file__) , os.getcwd() . , , , XBMC.

18—19: feeds /resources/lib. cnet.com, URL (dictionary). , , — www.cnet.com/podcasts — , — , , .

22—23: . .getLocalizedString() , UTF-8. , , , , .

26—32: - URL-encoded . - , . , XBMC URL-encoded . URL (. ) URL-encoded .
. , plugin.video.acme, , , . «Action» «Movies» plugin://plugin.video.acme/?=Movies&=Action. addDirectoryItem ( ). , plugin.video.acme, sys.argv[2] «?=Movies&=Action». , , , : XBMC . XBMC (, ), , , (, «Action» «Movies»). .
: , . URL, plugin://.
: xbmcplugin.addDirectoryItem, - , isFolder=False. isFolder=True XBMC , .

35—49: RSS- . XML - . , - (, , ).

52—64: cnet.com — , . .

57: xbmcgui.ListItem. , . , , , () ().

59: . , , .

61: . . , .

63: . , sys.argv[1]. , - . , addDirectoryItem() . isFolder=True XBMC, — , . . , . url , , .

65: . , , .

67: XBMC, .

69: (). , , ( , ), , , , . Container.SetViewMode() , MyVideoNav.xml, MyMusicNav.xml MyPics.xml , .
, . — Youtube.

72—77: . . 500 «» Confluence, 512 — «-» «Aeon-Nox». .

80—89: , . , feed_list(), , URL isFolder False, . . ( ) , .
: isFolder=False , , xbmcplugin.addDirectoryItem. , ( ). xbmcplugin.addDirectoryItem .

92—119: . , . .

101: , . , , .

114: XBMC. _string(), XBMC. .

\resources
\resources — , .

settings.xml
settings.xml — , , XML, , «». «» .

: <?xml version="1.0" encoding="UTF-8"?> <settings> <category label="128"> <setting id="quality" type="labelenum" label="100500" values="HD|SD" default="HD" /> </category> </settings>

— (labelenum) (HD SD). label= , , . id="" (. 101 default.py).
settings.xml « XBMC Addon Developers Guide » ( ). , , .
XML \userdata\addon_data\ XBMC. Windows , , %AppData%\XBMC, Linux — $HOME/.xbmc.

\language
\language . , , , , strings.po. \English, \Russian \Ukrainian, . . , . , , \English . , , .

strings.po: # XBMC Media Center language file msgid "" msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: ru\n" msgctxt "#100500" msgid "Quality:" msgstr ":" msgctxt "#100501" msgid "Access error!" msgstr " !" msgctxt "#100502" msgid "Failed to retrieve the feed data." msgstr " ."

, .po GNU Gettext. XBMC Gettext: .mo, , msgctxt. : XBMC XML, www.transifex.net . -, .po , Gettext.
, getLocalizedString() msgctxt. : XBMC + . , . , , , , , . .
, . , .
, settings.xml .
, XBMC, . .

\lib
\lib , . \lib . , : , . . , . , , , , , , .

\thumbnails
\thumbnails cnet.com. \lib, , .


. XBMC. ( XBMC " "). print xbmc.log().
XBMC.
, XBMC Eclipse + PyDev. .


XBMC . , , . «» .
, , , .


, , Wiki XBMC: http://wiki.xbmc.org/index.php?title=Category:Addon_Development
«XBMC Addon Developers Guide»: yadi.sk/d/NvfFuXYw92paL
XBMC Python API: mirrors.xbmc.org/docs/python-docs

PS
Post factum . . XBMC , . .


XBMC : I — .

executable, . .:
<extension point="xbmc.python.script" library="default.py"> <provides>executable</provides> </extension>

«». - «», «» «» (video, music image ), - , runtime- ( ).

.

16—22 . XBMC. 23 , .

icon.png
icon.png — () , XBMC. — PNG 256x256 .

fanart.jpg
fanart.jpg — (), . — JPG 1280x720 . : (). , «» , / .

changelog.txt
changelog.txt .

License.txt
License.txt . , . , , XBMC.

default.py
default.py — . «default.py» , ( library= extension), default.py, — , .
- PEP 8, , ____, .

: # -*- coding: utf-8 -*- # Name: plugin.video.cnet # Licence: GPL v.3: http://www.gnu.org/copyleft/gpl.html # import sys, os, urllib2, socket, xml.dom.minidom # XBMC import xbmc, xbmcplugin, xbmcaddon, xbmcgui # _ADDON_NAME = 'plugin.video.cnet' _addon = xbmcaddon.Addon(id=_ADDON_NAME) _addon_id = int(sys.argv[1]) _addon_url = sys.argv[0] _addon_path = _addon.getAddonInfo('path').decode('utf-8') # sys.path.append(os.path.join(_addon_path, 'resources', 'lib')) import feeds # def _string(string_id): return _addon.getLocalizedString(string_id).encode('utf-8') # URL-encoded def get_feed_name(): paramstring = sys.argv[2] if paramstring: feed_name = feeds.NAMES[paramstring.replace('?feed=', '')] else: feed_name = '' return feed_name # RSS- def rss_parser(url): listing = [] try: HEADER = {'User-Agent' : 'Mozilla/5.0 (Windows NT 6.1; rv:18.0) Gecko/20100101 Firefox/20.0'} request = urllib2.Request(url, None, HEADER) rss = xml.dom.minidom.parse(urllib2.urlopen(request, None, 3)) except urllib2.URLError, socket.timeout: pass else: titles = rss.getElementsByTagName('title') links = rss.getElementsByTagName('link') for title, link in zip(titles[2:], links[2:]): title = title.toxml().replace('<title><![CDATA[', '').replace(']]></title>', '') link = link.toxml().replace('<link>', '').replace('</link>', '') listing.append([title, link]) return listing # ( ). def feed_list(addon_id, addon_url, fanart, thumbpath, feeds): names = dict.keys(feeds) for name in names: thumb = os.path.join(thumbpath, feeds[name]['thumb']) # . list_item = xbmcgui.ListItem(name, thumbnailImage=thumb) # . . list_item.setProperty('fanart_image', fanart) # URL, . url = addon_url + '?feed=' + name.replace(' ', '').replace('*', '') # . isFolder=True , (). xbmcplugin.addDirectoryItem(addon_id, url, list_item, isFolder=True) # -- , . xbmcplugin.addSortMethod(addon_id, xbmcplugin.SORT_METHOD_LABEL_IGNORE_THE) # xbmcplugin.endOfDirectory(addon_id) # . switch_view() # . def switch_view(): skin_used = xbmc.getSkinDir() if skin_used == 'skin.confluence': xbmc.executebuiltin('Container.SetViewMode(500)') # "". elif skin_used == 'skin.aeon.nox': xbmc.executebuiltin('Container.SetViewMode(512)') # "-" # . def podcast_list(addon_id, fanart, thumb, listing): for item in listing: # . list_item = xbmcgui.ListItem(item[0], thumbnailImage=thumb) # . . list_item.setProperty('fanart_image', fanart) # . isFolder=False , . xbmcplugin.addDirectoryItem(addon_id, item[1], list_item, isFolder=False) # . xbmcplugin.endOfDirectory(addon_id) def main(): # . thumbpath = os.path.join(_addon_path, 'resources', 'thumbnails') fanart = os.path.join(_addon_path, 'fanart.jpg') # . feed = get_feed_name() # XBMC ( ), . if feed: # . quality = _addon.getSetting('quality') # . listing = rss_parser(url=feeds.FEEDS[feed][quality]) if listing: # . xbmc.log('%s: Started - Opening podcasts List' % _ADDON_NAME, xbmc.LOGNOTICE) thumb = os.path.join(thumbpath, feeds.FEEDS[feed]['thumb']) # . podcast_list(addon_id=_addon_id, fanart=fanart, thumb=thumb, listing=listing) else: # ( ), xbmc.log('%s: Failed to retrieve %s feed data!' % (_ADDON_NAME, feed), xbmc.LOGERROR) # XBMC. xbmc.executebuiltin('Notification(%s,%s)' % (_string(100501), _string(100502))) else: # XBMC ( ), , xbmc.log('%s: Started - Opening feeds List' % _ADDON_NAME, xbmc.LOGNOTICE) # . feed_list(addon_id=_addon_id, addon_url=_addon_url, fanart=fanart, thumbpath=thumbpath, feeds=feeds.FEEDS) if __name__ == '__main__': main()

. .

11—15 — . , , , , PEP 8.

:

11: . , addon.xml, ( ).

12: Addon .

13: runtime- (handle). — , 2- XBMC. addDirectoryItem(). . , - - , - () , XBMC API. , . XBMC ( ) - 3 sys.argv: URL, runtime- (, ), URL-encoded . -, , , sys.argv . , runtime- , addDirectoryItem(), - . URL .

14: URL . URL ''plugin://'' + ( ) + ''/''. URL : "plugin://plugin.video.cnet/". URL ( ) , . , , , XBMC , ( — ) . , , .
(), , . , «», «» «» XBMC « », , , . - , — , — , . , (- ) — XBMC. «» , ( ) . . addDirectoryItem() , . .

15: getAddonInfo('path') , , , , . : Windows, ( , ASCII), , (exeption). , Windows . decode('utf-8'). , , Windows.
getAddonInfo('path') — ( XBMC) . os.path.dirname(__file__) , os.getcwd() . , , , XBMC.

18—19: feeds /resources/lib. cnet.com, URL (dictionary). , , — www.cnet.com/podcasts — , — , , .

22—23: . .getLocalizedString() , UTF-8. , , , , .

26—32: - URL-encoded . - , . , XBMC URL-encoded . URL (. ) URL-encoded .
. , plugin.video.acme, , , . «Action» «Movies» plugin://plugin.video.acme/?=Movies&=Action. addDirectoryItem ( ). , plugin.video.acme, sys.argv[2] «?=Movies&=Action». , , , : XBMC . XBMC (, ), , , (, «Action» «Movies»). .
: , . URL, plugin://.
: xbmcplugin.addDirectoryItem, - , isFolder=False. isFolder=True XBMC , .

35—49: RSS- . XML - . , - (, , ).

52—64: cnet.com — , . .

57: xbmcgui.ListItem. , . , , , () ().

59: . , , .

61: . . , .

63: . , sys.argv[1]. , - . , addDirectoryItem() . isFolder=True XBMC, — , . . , . url , , .

65: . , , .

67: XBMC, .

69: (). , , ( , ), , , , . Container.SetViewMode() , MyVideoNav.xml, MyMusicNav.xml MyPics.xml , .
, . — Youtube.

72—77: . . 500 «» Confluence, 512 — «-» «Aeon-Nox». .

80—89: , . , feed_list(), , URL isFolder False, . . ( ) , .
: isFolder=False , , xbmcplugin.addDirectoryItem. , ( ). xbmcplugin.addDirectoryItem .

92—119: . , . .

101: , . , , .

114: XBMC. _string(), XBMC. .

\resources
\resources — , .

settings.xml
settings.xml — , , XML, , «». «» .

: <?xml version="1.0" encoding="UTF-8"?> <settings> <category label="128"> <setting id="quality" type="labelenum" label="100500" values="HD|SD" default="HD" /> </category> </settings>

— (labelenum) (HD SD). label= , , . id="" (. 101 default.py).
settings.xml « XBMC Addon Developers Guide » ( ). , , .
XML \userdata\addon_data\ XBMC. Windows , , %AppData%\XBMC, Linux — $HOME/.xbmc.

\language
\language . , , , , strings.po. \English, \Russian \Ukrainian, . . , . , , \English . , , .

strings.po: # XBMC Media Center language file msgid "" msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: ru\n" msgctxt "#100500" msgid "Quality:" msgstr ":" msgctxt "#100501" msgid "Access error!" msgstr " !" msgctxt "#100502" msgid "Failed to retrieve the feed data." msgstr " ."

, .po GNU Gettext. XBMC Gettext: .mo, , msgctxt. : XBMC XML, www.transifex.net . -, .po , Gettext.
, getLocalizedString() msgctxt. : XBMC + . , . , , , , , . .
, . , .
, settings.xml .
, XBMC, . .

\lib
\lib , . \lib . , : , . . , . , , , , , , .

\thumbnails
\thumbnails cnet.com. \lib, , .


. XBMC. ( XBMC " "). print xbmc.log().
XBMC.
, XBMC Eclipse + PyDev. .


XBMC . , , . «» .
, , , .


, , Wiki XBMC: http://wiki.xbmc.org/index.php?title=Category:Addon_Development
«XBMC Addon Developers Guide»: yadi.sk/d/NvfFuXYw92paL
XBMC Python API: mirrors.xbmc.org/docs/python-docs

PS
Post factum . . XBMC , . .


XBMC : I — .

executable, . .:
<extension point="xbmc.python.script" library="default.py"> <provides>executable</provides> </extension>

«». - «», «» «» (video, music image ), - , runtime- ( ).

.

16—22 . XBMC. 23 , .

icon.png
icon.png — () , XBMC. — PNG 256x256 .

fanart.jpg
fanart.jpg — (), . — JPG 1280x720 . : (). , «» , / .

changelog.txt
changelog.txt .

License.txt
License.txt . , . , , XBMC.

default.py
default.py — . «default.py» , ( library= extension), default.py, — , .
- PEP 8, , ____, .

: # -*- coding: utf-8 -*- # Name: plugin.video.cnet # Licence: GPL v.3: http://www.gnu.org/copyleft/gpl.html # import sys, os, urllib2, socket, xml.dom.minidom # XBMC import xbmc, xbmcplugin, xbmcaddon, xbmcgui # _ADDON_NAME = 'plugin.video.cnet' _addon = xbmcaddon.Addon(id=_ADDON_NAME) _addon_id = int(sys.argv[1]) _addon_url = sys.argv[0] _addon_path = _addon.getAddonInfo('path').decode('utf-8') # sys.path.append(os.path.join(_addon_path, 'resources', 'lib')) import feeds # def _string(string_id): return _addon.getLocalizedString(string_id).encode('utf-8') # URL-encoded def get_feed_name(): paramstring = sys.argv[2] if paramstring: feed_name = feeds.NAMES[paramstring.replace('?feed=', '')] else: feed_name = '' return feed_name # RSS- def rss_parser(url): listing = [] try: HEADER = {'User-Agent' : 'Mozilla/5.0 (Windows NT 6.1; rv:18.0) Gecko/20100101 Firefox/20.0'} request = urllib2.Request(url, None, HEADER) rss = xml.dom.minidom.parse(urllib2.urlopen(request, None, 3)) except urllib2.URLError, socket.timeout: pass else: titles = rss.getElementsByTagName('title') links = rss.getElementsByTagName('link') for title, link in zip(titles[2:], links[2:]): title = title.toxml().replace('<title><![CDATA[', '').replace(']]></title>', '') link = link.toxml().replace('<link>', '').replace('</link>', '') listing.append([title, link]) return listing # ( ). def feed_list(addon_id, addon_url, fanart, thumbpath, feeds): names = dict.keys(feeds) for name in names: thumb = os.path.join(thumbpath, feeds[name]['thumb']) # . list_item = xbmcgui.ListItem(name, thumbnailImage=thumb) # . . list_item.setProperty('fanart_image', fanart) # URL, . url = addon_url + '?feed=' + name.replace(' ', '').replace('*', '') # . isFolder=True , (). xbmcplugin.addDirectoryItem(addon_id, url, list_item, isFolder=True) # -- , . xbmcplugin.addSortMethod(addon_id, xbmcplugin.SORT_METHOD_LABEL_IGNORE_THE) # xbmcplugin.endOfDirectory(addon_id) # . switch_view() # . def switch_view(): skin_used = xbmc.getSkinDir() if skin_used == 'skin.confluence': xbmc.executebuiltin('Container.SetViewMode(500)') # "". elif skin_used == 'skin.aeon.nox': xbmc.executebuiltin('Container.SetViewMode(512)') # "-" # . def podcast_list(addon_id, fanart, thumb, listing): for item in listing: # . list_item = xbmcgui.ListItem(item[0], thumbnailImage=thumb) # . . list_item.setProperty('fanart_image', fanart) # . isFolder=False , . xbmcplugin.addDirectoryItem(addon_id, item[1], list_item, isFolder=False) # . xbmcplugin.endOfDirectory(addon_id) def main(): # . thumbpath = os.path.join(_addon_path, 'resources', 'thumbnails') fanart = os.path.join(_addon_path, 'fanart.jpg') # . feed = get_feed_name() # XBMC ( ), . if feed: # . quality = _addon.getSetting('quality') # . listing = rss_parser(url=feeds.FEEDS[feed][quality]) if listing: # . xbmc.log('%s: Started - Opening podcasts List' % _ADDON_NAME, xbmc.LOGNOTICE) thumb = os.path.join(thumbpath, feeds.FEEDS[feed]['thumb']) # . podcast_list(addon_id=_addon_id, fanart=fanart, thumb=thumb, listing=listing) else: # ( ), xbmc.log('%s: Failed to retrieve %s feed data!' % (_ADDON_NAME, feed), xbmc.LOGERROR) # XBMC. xbmc.executebuiltin('Notification(%s,%s)' % (_string(100501), _string(100502))) else: # XBMC ( ), , xbmc.log('%s: Started - Opening feeds List' % _ADDON_NAME, xbmc.LOGNOTICE) # . feed_list(addon_id=_addon_id, addon_url=_addon_url, fanart=fanart, thumbpath=thumbpath, feeds=feeds.FEEDS) if __name__ == '__main__': main()

. .

11—15 — . , , , , PEP 8.

:

11: . , addon.xml, ( ).

12: Addon .

13: runtime- (handle). — , 2- XBMC. addDirectoryItem(). . , - - , - () , XBMC API. , . XBMC ( ) - 3 sys.argv: URL, runtime- (, ), URL-encoded . -, , , sys.argv . , runtime- , addDirectoryItem(), - . URL .

14: URL . URL ''plugin://'' + ( ) + ''/''. URL : "plugin://plugin.video.cnet/". URL ( ) , . , , , XBMC , ( — ) . , , .
(), , . , «», «» «» XBMC « », , , . - , — , — , . , (- ) — XBMC. «» , ( ) . . addDirectoryItem() , . .

15: getAddonInfo('path') , , , , . : Windows, ( , ASCII), , (exeption). , Windows . decode('utf-8'). , , Windows.
getAddonInfo('path') — ( XBMC) . os.path.dirname(__file__) , os.getcwd() . , , , XBMC.

18—19: feeds /resources/lib. cnet.com, URL (dictionary). , , — www.cnet.com/podcasts — , — , , .

22—23: . .getLocalizedString() , UTF-8. , , , , .

26—32: - URL-encoded . - , . , XBMC URL-encoded . URL (. ) URL-encoded .
. , plugin.video.acme, , , . «Action» «Movies» plugin://plugin.video.acme/?=Movies&=Action. addDirectoryItem ( ). , plugin.video.acme, sys.argv[2] «?=Movies&=Action». , , , : XBMC . XBMC (, ), , , (, «Action» «Movies»). .
: , . URL, plugin://.
: xbmcplugin.addDirectoryItem, - , isFolder=False. isFolder=True XBMC , .

35—49: RSS- . XML - . , - (, , ).

52—64: cnet.com — , . .

57: xbmcgui.ListItem. , . , , , () ().

59: . , , .

61: . . , .

63: . , sys.argv[1]. , - . , addDirectoryItem() . isFolder=True XBMC, — , . . , . url , , .

65: . , , .

67: XBMC, .

69: (). , , ( , ), , , , . Container.SetViewMode() , MyVideoNav.xml, MyMusicNav.xml MyPics.xml , .
, . — Youtube.

72—77: . . 500 «» Confluence, 512 — «-» «Aeon-Nox». .

80—89: , . , feed_list(), , URL isFolder False, . . ( ) , .
: isFolder=False , , xbmcplugin.addDirectoryItem. , ( ). xbmcplugin.addDirectoryItem .

92—119: . , . .

101: , . , , .

114: XBMC. _string(), XBMC. .

\resources
\resources — , .

settings.xml
settings.xml — , , XML, , «». «» .

: <?xml version="1.0" encoding="UTF-8"?> <settings> <category label="128"> <setting id="quality" type="labelenum" label="100500" values="HD|SD" default="HD" /> </category> </settings>

— (labelenum) (HD SD). label= , , . id="" (. 101 default.py).
settings.xml « XBMC Addon Developers Guide » ( ). , , .
XML \userdata\addon_data\ XBMC. Windows , , %AppData%\XBMC, Linux — $HOME/.xbmc.

\language
\language . , , , , strings.po. \English, \Russian \Ukrainian, . . , . , , \English . , , .

strings.po: # XBMC Media Center language file msgid "" msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: ru\n" msgctxt "#100500" msgid "Quality:" msgstr ":" msgctxt "#100501" msgid "Access error!" msgstr " !" msgctxt "#100502" msgid "Failed to retrieve the feed data." msgstr " ."

, .po GNU Gettext. XBMC Gettext: .mo, , msgctxt. : XBMC XML, www.transifex.net . -, .po , Gettext.
, getLocalizedString() msgctxt. : XBMC + . , . , , , , , . .
, . , .
, settings.xml .
, XBMC, . .

\lib
\lib , . \lib . , : , . . , . , , , , , , .

\thumbnails
\thumbnails cnet.com. \lib, , .


. XBMC. ( XBMC " "). print xbmc.log().
XBMC.
, XBMC Eclipse + PyDev. .


XBMC . , , . «» .
, , , .


, , Wiki XBMC: http://wiki.xbmc.org/index.php?title=Category:Addon_Development
«XBMC Addon Developers Guide»: yadi.sk/d/NvfFuXYw92paL
XBMC Python API: mirrors.xbmc.org/docs/python-docs

PS
Post factum . . XBMC , . .


XBMC : I — .

executable, . .:
<extension point="xbmc.python.script" library="default.py"> <provides>executable</provides> </extension>

«». - «», «» «» (video, music image ), - , runtime- ( ).

.

16—22 . XBMC. 23 , .

icon.png
icon.png — () , XBMC. — PNG 256x256 .

fanart.jpg
fanart.jpg — (), . — JPG 1280x720 . : (). , «» , / .

changelog.txt
changelog.txt .

License.txt
License.txt . , . , , XBMC.

default.py
default.py — . «default.py» , ( library= extension), default.py, — , .
- PEP 8, , ____, .

: # -*- coding: utf-8 -*- # Name: plugin.video.cnet # Licence: GPL v.3: http://www.gnu.org/copyleft/gpl.html # import sys, os, urllib2, socket, xml.dom.minidom # XBMC import xbmc, xbmcplugin, xbmcaddon, xbmcgui # _ADDON_NAME = 'plugin.video.cnet' _addon = xbmcaddon.Addon(id=_ADDON_NAME) _addon_id = int(sys.argv[1]) _addon_url = sys.argv[0] _addon_path = _addon.getAddonInfo('path').decode('utf-8') # sys.path.append(os.path.join(_addon_path, 'resources', 'lib')) import feeds # def _string(string_id): return _addon.getLocalizedString(string_id).encode('utf-8') # URL-encoded def get_feed_name(): paramstring = sys.argv[2] if paramstring: feed_name = feeds.NAMES[paramstring.replace('?feed=', '')] else: feed_name = '' return feed_name # RSS- def rss_parser(url): listing = [] try: HEADER = {'User-Agent' : 'Mozilla/5.0 (Windows NT 6.1; rv:18.0) Gecko/20100101 Firefox/20.0'} request = urllib2.Request(url, None, HEADER) rss = xml.dom.minidom.parse(urllib2.urlopen(request, None, 3)) except urllib2.URLError, socket.timeout: pass else: titles = rss.getElementsByTagName('title') links = rss.getElementsByTagName('link') for title, link in zip(titles[2:], links[2:]): title = title.toxml().replace('<title><![CDATA[', '').replace(']]></title>', '') link = link.toxml().replace('<link>', '').replace('</link>', '') listing.append([title, link]) return listing # ( ). def feed_list(addon_id, addon_url, fanart, thumbpath, feeds): names = dict.keys(feeds) for name in names: thumb = os.path.join(thumbpath, feeds[name]['thumb']) # . list_item = xbmcgui.ListItem(name, thumbnailImage=thumb) # . . list_item.setProperty('fanart_image', fanart) # URL, . url = addon_url + '?feed=' + name.replace(' ', '').replace('*', '') # . isFolder=True , (). xbmcplugin.addDirectoryItem(addon_id, url, list_item, isFolder=True) # -- , . xbmcplugin.addSortMethod(addon_id, xbmcplugin.SORT_METHOD_LABEL_IGNORE_THE) # xbmcplugin.endOfDirectory(addon_id) # . switch_view() # . def switch_view(): skin_used = xbmc.getSkinDir() if skin_used == 'skin.confluence': xbmc.executebuiltin('Container.SetViewMode(500)') # "". elif skin_used == 'skin.aeon.nox': xbmc.executebuiltin('Container.SetViewMode(512)') # "-" # . def podcast_list(addon_id, fanart, thumb, listing): for item in listing: # . list_item = xbmcgui.ListItem(item[0], thumbnailImage=thumb) # . . list_item.setProperty('fanart_image', fanart) # . isFolder=False , . xbmcplugin.addDirectoryItem(addon_id, item[1], list_item, isFolder=False) # . xbmcplugin.endOfDirectory(addon_id) def main(): # . thumbpath = os.path.join(_addon_path, 'resources', 'thumbnails') fanart = os.path.join(_addon_path, 'fanart.jpg') # . feed = get_feed_name() # XBMC ( ), . if feed: # . quality = _addon.getSetting('quality') # . listing = rss_parser(url=feeds.FEEDS[feed][quality]) if listing: # . xbmc.log('%s: Started - Opening podcasts List' % _ADDON_NAME, xbmc.LOGNOTICE) thumb = os.path.join(thumbpath, feeds.FEEDS[feed]['thumb']) # . podcast_list(addon_id=_addon_id, fanart=fanart, thumb=thumb, listing=listing) else: # ( ), xbmc.log('%s: Failed to retrieve %s feed data!' % (_ADDON_NAME, feed), xbmc.LOGERROR) # XBMC. xbmc.executebuiltin('Notification(%s,%s)' % (_string(100501), _string(100502))) else: # XBMC ( ), , xbmc.log('%s: Started - Opening feeds List' % _ADDON_NAME, xbmc.LOGNOTICE) # . feed_list(addon_id=_addon_id, addon_url=_addon_url, fanart=fanart, thumbpath=thumbpath, feeds=feeds.FEEDS) if __name__ == '__main__': main()

. .

11—15 — . , , , , PEP 8.

:

11: . , addon.xml, ( ).

12: Addon .

13: runtime- (handle). — , 2- XBMC. addDirectoryItem(). . , - - , - () , XBMC API. , . XBMC ( ) - 3 sys.argv: URL, runtime- (, ), URL-encoded . -, , , sys.argv . , runtime- , addDirectoryItem(), - . URL .

14: URL . URL ''plugin://'' + ( ) + ''/''. URL : "plugin://plugin.video.cnet/". URL ( ) , . , , , XBMC , ( — ) . , , .
(), , . , «», «» «» XBMC « », , , . - , — , — , . , (- ) — XBMC. «» , ( ) . . addDirectoryItem() , . .

15: getAddonInfo('path') , , , , . : Windows, ( , ASCII), , (exeption). , Windows . decode('utf-8'). , , Windows.
getAddonInfo('path') — ( XBMC) . os.path.dirname(__file__) , os.getcwd() . , , , XBMC.

18—19: feeds /resources/lib. cnet.com, URL (dictionary). , , — www.cnet.com/podcasts — , — , , .

22—23: . .getLocalizedString() , UTF-8. , , , , .

26—32: - URL-encoded . - , . , XBMC URL-encoded . URL (. ) URL-encoded .
. , plugin.video.acme, , , . «Action» «Movies» plugin://plugin.video.acme/?=Movies&=Action. addDirectoryItem ( ). , plugin.video.acme, sys.argv[2] «?=Movies&=Action». , , , : XBMC . XBMC (, ), , , (, «Action» «Movies»). .
: , . URL, plugin://.
: xbmcplugin.addDirectoryItem, - , isFolder=False. isFolder=True XBMC , .

35—49: RSS- . XML - . , - (, , ).

52—64: cnet.com — , . .

57: xbmcgui.ListItem. , . , , , () ().

59: . , , .

61: . . , .

63: . , sys.argv[1]. , - . , addDirectoryItem() . isFolder=True XBMC, — , . . , . url , , .

65: . , , .

67: XBMC, .

69: (). , , ( , ), , , , . Container.SetViewMode() , MyVideoNav.xml, MyMusicNav.xml MyPics.xml , .
, . — Youtube.

72—77: . . 500 «» Confluence, 512 — «-» «Aeon-Nox». .

80—89: , . , feed_list(), , URL isFolder False, . . ( ) , .
: isFolder=False , , xbmcplugin.addDirectoryItem. , ( ). xbmcplugin.addDirectoryItem .

92—119: . , . .

101: , . , , .

114: XBMC. _string(), XBMC. .

\resources
\resources — , .

settings.xml
settings.xml — , , XML, , «». «» .

: <?xml version="1.0" encoding="UTF-8"?> <settings> <category label="128"> <setting id="quality" type="labelenum" label="100500" values="HD|SD" default="HD" /> </category> </settings>

— (labelenum) (HD SD). label= , , . id="" (. 101 default.py).
settings.xml « XBMC Addon Developers Guide » ( ). , , .
XML \userdata\addon_data\ XBMC. Windows , , %AppData%\XBMC, Linux — $HOME/.xbmc.

\language
\language . , , , , strings.po. \English, \Russian \Ukrainian, . . , . , , \English . , , .

strings.po: # XBMC Media Center language file msgid "" msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: ru\n" msgctxt "#100500" msgid "Quality:" msgstr ":" msgctxt "#100501" msgid "Access error!" msgstr " !" msgctxt "#100502" msgid "Failed to retrieve the feed data." msgstr " ."

, .po GNU Gettext. XBMC Gettext: .mo, , msgctxt. : XBMC XML, www.transifex.net . -, .po , Gettext.
, getLocalizedString() msgctxt. : XBMC + . , . , , , , , . .
, . , .
, settings.xml .
, XBMC, . .

\lib
\lib , . \lib . , : , . . , . , , , , , , .

\thumbnails
\thumbnails cnet.com. \lib, , .


. XBMC. ( XBMC " "). print xbmc.log().
XBMC.
, XBMC Eclipse + PyDev. .


XBMC . , , . «» .
, , , .


, , Wiki XBMC: http://wiki.xbmc.org/index.php?title=Category:Addon_Development
«XBMC Addon Developers Guide»: yadi.sk/d/NvfFuXYw92paL
XBMC Python API: mirrors.xbmc.org/docs/python-docs

PS
Post factum . . XBMC , . .


XBMC : I — .
executable, . .:
<extension point="xbmc.python.script" library="default.py"> <provides>executable</provides> </extension>

«». - «», «» «» (video, music image ), - , runtime- ( ).

.

16—22 . XBMC. 23 , .

icon.png
icon.png — () , XBMC. — PNG 256x256 .

fanart.jpg
fanart.jpg — (), . — JPG 1280x720 . : (). , «» , / .

changelog.txt
changelog.txt .

License.txt
License.txt . , . , , XBMC.

default.py
default.py — . «default.py» , ( library= extension), default.py, — , .
- PEP 8, , ____, .

: # -*- coding: utf-8 -*- # Name: plugin.video.cnet # Licence: GPL v.3: http://www.gnu.org/copyleft/gpl.html # import sys, os, urllib2, socket, xml.dom.minidom # XBMC import xbmc, xbmcplugin, xbmcaddon, xbmcgui # _ADDON_NAME = 'plugin.video.cnet' _addon = xbmcaddon.Addon(id=_ADDON_NAME) _addon_id = int(sys.argv[1]) _addon_url = sys.argv[0] _addon_path = _addon.getAddonInfo('path').decode('utf-8') # sys.path.append(os.path.join(_addon_path, 'resources', 'lib')) import feeds # def _string(string_id): return _addon.getLocalizedString(string_id).encode('utf-8') # URL-encoded def get_feed_name(): paramstring = sys.argv[2] if paramstring: feed_name = feeds.NAMES[paramstring.replace('?feed=', '')] else: feed_name = '' return feed_name # RSS- def rss_parser(url): listing = [] try: HEADER = {'User-Agent' : 'Mozilla/5.0 (Windows NT 6.1; rv:18.0) Gecko/20100101 Firefox/20.0'} request = urllib2.Request(url, None, HEADER) rss = xml.dom.minidom.parse(urllib2.urlopen(request, None, 3)) except urllib2.URLError, socket.timeout: pass else: titles = rss.getElementsByTagName('title') links = rss.getElementsByTagName('link') for title, link in zip(titles[2:], links[2:]): title = title.toxml().replace('<title><![CDATA[', '').replace(']]></title>', '') link = link.toxml().replace('<link>', '').replace('</link>', '') listing.append([title, link]) return listing # ( ). def feed_list(addon_id, addon_url, fanart, thumbpath, feeds): names = dict.keys(feeds) for name in names: thumb = os.path.join(thumbpath, feeds[name]['thumb']) # . list_item = xbmcgui.ListItem(name, thumbnailImage=thumb) # . . list_item.setProperty('fanart_image', fanart) # URL, . url = addon_url + '?feed=' + name.replace(' ', '').replace('*', '') # . isFolder=True , (). xbmcplugin.addDirectoryItem(addon_id, url, list_item, isFolder=True) # -- , . xbmcplugin.addSortMethod(addon_id, xbmcplugin.SORT_METHOD_LABEL_IGNORE_THE) # xbmcplugin.endOfDirectory(addon_id) # . switch_view() # . def switch_view(): skin_used = xbmc.getSkinDir() if skin_used == 'skin.confluence': xbmc.executebuiltin('Container.SetViewMode(500)') # "". elif skin_used == 'skin.aeon.nox': xbmc.executebuiltin('Container.SetViewMode(512)') # "-" # . def podcast_list(addon_id, fanart, thumb, listing): for item in listing: # . list_item = xbmcgui.ListItem(item[0], thumbnailImage=thumb) # . . list_item.setProperty('fanart_image', fanart) # . isFolder=False , . xbmcplugin.addDirectoryItem(addon_id, item[1], list_item, isFolder=False) # . xbmcplugin.endOfDirectory(addon_id) def main(): # . thumbpath = os.path.join(_addon_path, 'resources', 'thumbnails') fanart = os.path.join(_addon_path, 'fanart.jpg') # . feed = get_feed_name() # XBMC ( ), . if feed: # . quality = _addon.getSetting('quality') # . listing = rss_parser(url=feeds.FEEDS[feed][quality]) if listing: # . xbmc.log('%s: Started - Opening podcasts List' % _ADDON_NAME, xbmc.LOGNOTICE) thumb = os.path.join(thumbpath, feeds.FEEDS[feed]['thumb']) # . podcast_list(addon_id=_addon_id, fanart=fanart, thumb=thumb, listing=listing) else: # ( ), xbmc.log('%s: Failed to retrieve %s feed data!' % (_ADDON_NAME, feed), xbmc.LOGERROR) # XBMC. xbmc.executebuiltin('Notification(%s,%s)' % (_string(100501), _string(100502))) else: # XBMC ( ), , xbmc.log('%s: Started - Opening feeds List' % _ADDON_NAME, xbmc.LOGNOTICE) # . feed_list(addon_id=_addon_id, addon_url=_addon_url, fanart=fanart, thumbpath=thumbpath, feeds=feeds.FEEDS) if __name__ == '__main__': main()

. .

11—15 — . , , , , PEP 8.

:

11: . , addon.xml, ( ).

12: Addon .

13: runtime- (handle). — , 2- XBMC. addDirectoryItem(). . , - - , - () , XBMC API. , . XBMC ( ) - 3 sys.argv: URL, runtime- (, ), URL-encoded . -, , , sys.argv . , runtime- , addDirectoryItem(), - . URL .

14: URL . URL ''plugin://'' + ( ) + ''/''. URL : "plugin://plugin.video.cnet/". URL ( ) , . , , , XBMC , ( — ) . , , .
(), , . , «», «» «» XBMC « », , , . - , — , — , . , (- ) — XBMC. «» , ( ) . . addDirectoryItem() , . .

15: getAddonInfo('path') , , , , . : Windows, ( , ASCII), , (exeption). , Windows . decode('utf-8'). , , Windows.
getAddonInfo('path') — ( XBMC) . os.path.dirname(__file__) , os.getcwd() . , , , XBMC.

18—19: feeds /resources/lib. cnet.com, URL (dictionary). , , — www.cnet.com/podcasts — , — , , .

22—23: . .getLocalizedString() , UTF-8. , , , , .

26—32: - URL-encoded . - , . , XBMC URL-encoded . URL (. ) URL-encoded .
. , plugin.video.acme, , , . «Action» «Movies» plugin://plugin.video.acme/?=Movies&=Action. addDirectoryItem ( ). , plugin.video.acme, sys.argv[2] «?=Movies&=Action». , , , : XBMC . XBMC (, ), , , (, «Action» «Movies»). .
: , . URL, plugin://.
: xbmcplugin.addDirectoryItem, - , isFolder=False. isFolder=True XBMC , .

35—49: RSS- . XML - . , - (, , ).

52—64: cnet.com — , . .

57: xbmcgui.ListItem. , . , , , () ().

59: . , , .

61: . . , .

63: . , sys.argv[1]. , - . , addDirectoryItem() . isFolder=True XBMC, — , . . , . url , , .

65: . , , .

67: XBMC, .

69: (). , , ( , ), , , , . Container.SetViewMode() , MyVideoNav.xml, MyMusicNav.xml MyPics.xml , .
, . — Youtube.

72—77: . . 500 «» Confluence, 512 — «-» «Aeon-Nox». .

80—89: , . , feed_list(), , URL isFolder False, . . ( ) , .
: isFolder=False , , xbmcplugin.addDirectoryItem. , ( ). xbmcplugin.addDirectoryItem .

92—119: . , . .

101: , . , , .

114: XBMC. _string(), XBMC. .

\resources
\resources — , .

settings.xml
settings.xml — , , XML, , «». «» .

: <?xml version="1.0" encoding="UTF-8"?> <settings> <category label="128"> <setting id="quality" type="labelenum" label="100500" values="HD|SD" default="HD" /> </category> </settings>

— (labelenum) (HD SD). label= , , . id="" (. 101 default.py).
settings.xml « XBMC Addon Developers Guide » ( ). , , .
XML \userdata\addon_data\ XBMC. Windows , , %AppData%\XBMC, Linux — $HOME/.xbmc.

\language
\language . , , , , strings.po. \English, \Russian \Ukrainian, . . , . , , \English . , , .

strings.po: # XBMC Media Center language file msgid "" msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: ru\n" msgctxt "#100500" msgid "Quality:" msgstr ":" msgctxt "#100501" msgid "Access error!" msgstr " !" msgctxt "#100502" msgid "Failed to retrieve the feed data." msgstr " ."

, .po GNU Gettext. XBMC Gettext: .mo, , msgctxt. : XBMC XML, www.transifex.net . -, .po , Gettext.
, getLocalizedString() msgctxt. : XBMC + . , . , , , , , . .
, . , .
, settings.xml .
, XBMC, . .

\lib
\lib , . \lib . , : , . . , . , , , , , , .

\thumbnails
\thumbnails cnet.com. \lib, , .


. XBMC. ( XBMC " "). print xbmc.log().
XBMC.
, XBMC Eclipse + PyDev. .


XBMC . , , . «» .
, , , .


, , Wiki XBMC: http://wiki.xbmc.org/index.php?title=Category:Addon_Development
«XBMC Addon Developers Guide»: yadi.sk/d/NvfFuXYw92paL
XBMC Python API: mirrors.xbmc.org/docs/python-docs

PS
Post factum . . XBMC , . .


XBMC : I — .
executable, . .:
<extension point="xbmc.python.script" library="default.py"> <provides>executable</provides> </extension>

«». - «», «» «» (video, music image ), - , runtime- ( ).

.

16—22 . XBMC. 23 , .

icon.png
icon.png — () , XBMC. — PNG 256x256 .

fanart.jpg
fanart.jpg — (), . — JPG 1280x720 . : (). , «» , / .

changelog.txt
changelog.txt .

License.txt
License.txt . , . , , XBMC.

default.py
default.py — . «default.py» , ( library= extension), default.py, — , .
- PEP 8, , ____, .

: # -*- coding: utf-8 -*- # Name: plugin.video.cnet # Licence: GPL v.3: http://www.gnu.org/copyleft/gpl.html # import sys, os, urllib2, socket, xml.dom.minidom # XBMC import xbmc, xbmcplugin, xbmcaddon, xbmcgui # _ADDON_NAME = 'plugin.video.cnet' _addon = xbmcaddon.Addon(id=_ADDON_NAME) _addon_id = int(sys.argv[1]) _addon_url = sys.argv[0] _addon_path = _addon.getAddonInfo('path').decode('utf-8') # sys.path.append(os.path.join(_addon_path, 'resources', 'lib')) import feeds # def _string(string_id): return _addon.getLocalizedString(string_id).encode('utf-8') # URL-encoded def get_feed_name(): paramstring = sys.argv[2] if paramstring: feed_name = feeds.NAMES[paramstring.replace('?feed=', '')] else: feed_name = '' return feed_name # RSS- def rss_parser(url): listing = [] try: HEADER = {'User-Agent' : 'Mozilla/5.0 (Windows NT 6.1; rv:18.0) Gecko/20100101 Firefox/20.0'} request = urllib2.Request(url, None, HEADER) rss = xml.dom.minidom.parse(urllib2.urlopen(request, None, 3)) except urllib2.URLError, socket.timeout: pass else: titles = rss.getElementsByTagName('title') links = rss.getElementsByTagName('link') for title, link in zip(titles[2:], links[2:]): title = title.toxml().replace('<title><![CDATA[', '').replace(']]></title>', '') link = link.toxml().replace('<link>', '').replace('</link>', '') listing.append([title, link]) return listing # ( ). def feed_list(addon_id, addon_url, fanart, thumbpath, feeds): names = dict.keys(feeds) for name in names: thumb = os.path.join(thumbpath, feeds[name]['thumb']) # . list_item = xbmcgui.ListItem(name, thumbnailImage=thumb) # . . list_item.setProperty('fanart_image', fanart) # URL, . url = addon_url + '?feed=' + name.replace(' ', '').replace('*', '') # . isFolder=True , (). xbmcplugin.addDirectoryItem(addon_id, url, list_item, isFolder=True) # -- , . xbmcplugin.addSortMethod(addon_id, xbmcplugin.SORT_METHOD_LABEL_IGNORE_THE) # xbmcplugin.endOfDirectory(addon_id) # . switch_view() # . def switch_view(): skin_used = xbmc.getSkinDir() if skin_used == 'skin.confluence': xbmc.executebuiltin('Container.SetViewMode(500)') # "". elif skin_used == 'skin.aeon.nox': xbmc.executebuiltin('Container.SetViewMode(512)') # "-" # . def podcast_list(addon_id, fanart, thumb, listing): for item in listing: # . list_item = xbmcgui.ListItem(item[0], thumbnailImage=thumb) # . . list_item.setProperty('fanart_image', fanart) # . isFolder=False , . xbmcplugin.addDirectoryItem(addon_id, item[1], list_item, isFolder=False) # . xbmcplugin.endOfDirectory(addon_id) def main(): # . thumbpath = os.path.join(_addon_path, 'resources', 'thumbnails') fanart = os.path.join(_addon_path, 'fanart.jpg') # . feed = get_feed_name() # XBMC ( ), . if feed: # . quality = _addon.getSetting('quality') # . listing = rss_parser(url=feeds.FEEDS[feed][quality]) if listing: # . xbmc.log('%s: Started - Opening podcasts List' % _ADDON_NAME, xbmc.LOGNOTICE) thumb = os.path.join(thumbpath, feeds.FEEDS[feed]['thumb']) # . podcast_list(addon_id=_addon_id, fanart=fanart, thumb=thumb, listing=listing) else: # ( ), xbmc.log('%s: Failed to retrieve %s feed data!' % (_ADDON_NAME, feed), xbmc.LOGERROR) # XBMC. xbmc.executebuiltin('Notification(%s,%s)' % (_string(100501), _string(100502))) else: # XBMC ( ), , xbmc.log('%s: Started - Opening feeds List' % _ADDON_NAME, xbmc.LOGNOTICE) # . feed_list(addon_id=_addon_id, addon_url=_addon_url, fanart=fanart, thumbpath=thumbpath, feeds=feeds.FEEDS) if __name__ == '__main__': main()


. .

11—15 — . , , , , PEP 8.

:

11: . , addon.xml, ( ).

12: Addon .

13: runtime- (handle). — , 2- XBMC. addDirectoryItem(). . , - - , - () , XBMC API. , . XBMC ( ) - 3 sys.argv: URL, runtime- (, ), URL-encoded . -, , , sys.argv . , runtime- , addDirectoryItem(), - . URL .

14: URL . URL ''plugin://'' + ( ) + ''/''. URL : "plugin://plugin.video.cnet/". URL ( ) , . , , , XBMC , ( — ) . , , .
(), , . , «», «» «» XBMC « », , , . - , — , — , . , (- ) — XBMC. «» , ( ) . . addDirectoryItem() , . .

15: getAddonInfo('path') , , , , . : Windows, ( , ASCII), , (exeption). , Windows . decode('utf-8'). , , Windows.
getAddonInfo('path') — ( XBMC) . os.path.dirname(__file__) , os.getcwd() . , , , XBMC.

18—19: feeds /resources/lib. cnet.com, URL (dictionary). , , — www.cnet.com/podcasts — , — , , .

22—23: . .getLocalizedString() , UTF-8. , , , , .

26—32: - URL-encoded . - , . , XBMC URL-encoded . URL (. ) URL-encoded .
. , plugin.video.acme, , , . «Action» «Movies» plugin://plugin.video.acme/?=Movies&=Action. addDirectoryItem ( ). , plugin.video.acme, sys.argv[2] «?=Movies&=Action». , , , : XBMC . XBMC (, ), , , (, «Action» «Movies»). .
: , . URL, plugin://.
: xbmcplugin.addDirectoryItem, - , isFolder=False. isFolder=True XBMC , .

35—49: RSS- . XML - . , - (, , ).

52—64: cnet.com — , . .

57: xbmcgui.ListItem. , . , , , () ().

59: . , , .

61: . . , .

63: . , sys.argv[1]. , - . , addDirectoryItem() . isFolder=True XBMC, — , . . , . url , , .

65: . , , .

67: XBMC, .

69: (). , , ( , ), , , , . Container.SetViewMode() , MyVideoNav.xml, MyMusicNav.xml MyPics.xml , .
, . — Youtube.

72—77: . . 500 «» Confluence, 512 — «-» «Aeon-Nox». .

80—89: , . , feed_list(), , URL isFolder False, . . ( ) , .
: isFolder=False , , xbmcplugin.addDirectoryItem. , ( ). xbmcplugin.addDirectoryItem .

92—119: . , . .

101: , . , , .

114: XBMC. _string(), XBMC. .

\resources
\resources — , .

settings.xml
settings.xml — , , XML, , «». «» .

: <?xml version="1.0" encoding="UTF-8"?> <settings> <category label="128"> <setting id="quality" type="labelenum" label="100500" values="HD|SD" default="HD" /> </category> </settings>

— (labelenum) (HD SD). label= , , . id="" (. 101 default.py).
settings.xml « XBMC Addon Developers Guide » ( ). , , .
XML \userdata\addon_data\ XBMC. Windows , , %AppData%\XBMC, Linux — $HOME/.xbmc.

\language
\language . , , , , strings.po. \English, \Russian \Ukrainian, . . , . , , \English . , , .

strings.po: # XBMC Media Center language file msgid "" msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: ru\n" msgctxt "#100500" msgid "Quality:" msgstr ":" msgctxt "#100501" msgid "Access error!" msgstr " !" msgctxt "#100502" msgid "Failed to retrieve the feed data." msgstr " ."

, .po GNU Gettext. XBMC Gettext: .mo, , msgctxt. : XBMC XML, www.transifex.net . -, .po , Gettext.
, getLocalizedString() msgctxt. : XBMC + . , . , , , , , . .
, . , .
, settings.xml .
, XBMC, . .

\lib
\lib , . \lib . , : , . . , . , , , , , , .

\thumbnails
\thumbnails cnet.com. \lib, , .


. XBMC. ( XBMC " "). print xbmc.log().
XBMC.
, XBMC Eclipse + PyDev. .


XBMC . , , . «» .
, , , .


, , Wiki XBMC: http://wiki.xbmc.org/index.php?title=Category:Addon_Development
«XBMC Addon Developers Guide»: yadi.sk/d/NvfFuXYw92paL
XBMC Python API: mirrors.xbmc.org/docs/python-docs

PS
Post factum . . XBMC , . .


XBMC : I — .
executable, . .:
<extension point="xbmc.python.script" library="default.py"> <provides>executable</provides> </extension>

«». - «», «» «» (video, music image ), - , runtime- ( ).

.

16—22 . XBMC. 23 , .

icon.png
icon.png — () , XBMC. — PNG 256x256 .

fanart.jpg
fanart.jpg — (), . — JPG 1280x720 . : (). , «» , / .

changelog.txt
changelog.txt .

License.txt
License.txt . , . , , XBMC.

default.py
default.py — . «default.py» , ( library= extension), default.py, — , .
- PEP 8, , ____, .

: # -*- coding: utf-8 -*- # Name: plugin.video.cnet # Licence: GPL v.3: http://www.gnu.org/copyleft/gpl.html # import sys, os, urllib2, socket, xml.dom.minidom # XBMC import xbmc, xbmcplugin, xbmcaddon, xbmcgui # _ADDON_NAME = 'plugin.video.cnet' _addon = xbmcaddon.Addon(id=_ADDON_NAME) _addon_id = int(sys.argv[1]) _addon_url = sys.argv[0] _addon_path = _addon.getAddonInfo('path').decode('utf-8') # sys.path.append(os.path.join(_addon_path, 'resources', 'lib')) import feeds # def _string(string_id): return _addon.getLocalizedString(string_id).encode('utf-8') # URL-encoded def get_feed_name(): paramstring = sys.argv[2] if paramstring: feed_name = feeds.NAMES[paramstring.replace('?feed=', '')] else: feed_name = '' return feed_name # RSS- def rss_parser(url): listing = [] try: HEADER = {'User-Agent' : 'Mozilla/5.0 (Windows NT 6.1; rv:18.0) Gecko/20100101 Firefox/20.0'} request = urllib2.Request(url, None, HEADER) rss = xml.dom.minidom.parse(urllib2.urlopen(request, None, 3)) except urllib2.URLError, socket.timeout: pass else: titles = rss.getElementsByTagName('title') links = rss.getElementsByTagName('link') for title, link in zip(titles[2:], links[2:]): title = title.toxml().replace('<title><![CDATA[', '').replace(']]></title>', '') link = link.toxml().replace('<link>', '').replace('</link>', '') listing.append([title, link]) return listing # ( ). def feed_list(addon_id, addon_url, fanart, thumbpath, feeds): names = dict.keys(feeds) for name in names: thumb = os.path.join(thumbpath, feeds[name]['thumb']) # . list_item = xbmcgui.ListItem(name, thumbnailImage=thumb) # . . list_item.setProperty('fanart_image', fanart) # URL, . url = addon_url + '?feed=' + name.replace(' ', '').replace('*', '') # . isFolder=True , (). xbmcplugin.addDirectoryItem(addon_id, url, list_item, isFolder=True) # -- , . xbmcplugin.addSortMethod(addon_id, xbmcplugin.SORT_METHOD_LABEL_IGNORE_THE) # xbmcplugin.endOfDirectory(addon_id) # . switch_view() # . def switch_view(): skin_used = xbmc.getSkinDir() if skin_used == 'skin.confluence': xbmc.executebuiltin('Container.SetViewMode(500)') # "". elif skin_used == 'skin.aeon.nox': xbmc.executebuiltin('Container.SetViewMode(512)') # "-" # . def podcast_list(addon_id, fanart, thumb, listing): for item in listing: # . list_item = xbmcgui.ListItem(item[0], thumbnailImage=thumb) # . . list_item.setProperty('fanart_image', fanart) # . isFolder=False , . xbmcplugin.addDirectoryItem(addon_id, item[1], list_item, isFolder=False) # . xbmcplugin.endOfDirectory(addon_id) def main(): # . thumbpath = os.path.join(_addon_path, 'resources', 'thumbnails') fanart = os.path.join(_addon_path, 'fanart.jpg') # . feed = get_feed_name() # XBMC ( ), . if feed: # . quality = _addon.getSetting('quality') # . listing = rss_parser(url=feeds.FEEDS[feed][quality]) if listing: # . xbmc.log('%s: Started - Opening podcasts List' % _ADDON_NAME, xbmc.LOGNOTICE) thumb = os.path.join(thumbpath, feeds.FEEDS[feed]['thumb']) # . podcast_list(addon_id=_addon_id, fanart=fanart, thumb=thumb, listing=listing) else: # ( ), xbmc.log('%s: Failed to retrieve %s feed data!' % (_ADDON_NAME, feed), xbmc.LOGERROR) # XBMC. xbmc.executebuiltin('Notification(%s,%s)' % (_string(100501), _string(100502))) else: # XBMC ( ), , xbmc.log('%s: Started - Opening feeds List' % _ADDON_NAME, xbmc.LOGNOTICE) # . feed_list(addon_id=_addon_id, addon_url=_addon_url, fanart=fanart, thumbpath=thumbpath, feeds=feeds.FEEDS) if __name__ == '__main__': main()


. .

11—15 — . , , , , PEP 8.

:

11: . , addon.xml, ( ).

12: Addon .

13: runtime- (handle). — , 2- XBMC. addDirectoryItem(). . , - - , - () , XBMC API. , . XBMC ( ) - 3 sys.argv: URL, runtime- (, ), URL-encoded . -, , , sys.argv . , runtime- , addDirectoryItem(), - . URL .

14: URL . URL ''plugin://'' + ( ) + ''/''. URL : "plugin://plugin.video.cnet/". URL ( ) , . , , , XBMC , ( — ) . , , .
(), , . , «», «» «» XBMC « », , , . - , — , — , . , (- ) — XBMC. «» , ( ) . . addDirectoryItem() , . .

15: getAddonInfo('path') , , , , . : Windows, ( , ASCII), , (exeption). , Windows . decode('utf-8'). , , Windows.
getAddonInfo('path') — ( XBMC) . os.path.dirname(__file__) , os.getcwd() . , , , XBMC.

18—19: feeds /resources/lib. cnet.com, URL (dictionary). , , — www.cnet.com/podcasts — , — , , .

22—23: . .getLocalizedString() , UTF-8. , , , , .

26—32: - URL-encoded . - , . , XBMC URL-encoded . URL (. ) URL-encoded .
. , plugin.video.acme, , , . «Action» «Movies» plugin://plugin.video.acme/?=Movies&=Action. addDirectoryItem ( ). , plugin.video.acme, sys.argv[2] «?=Movies&=Action». , , , : XBMC . XBMC (, ), , , (, «Action» «Movies»). .
: , . URL, plugin://.
: xbmcplugin.addDirectoryItem, - , isFolder=False. isFolder=True XBMC , .

35—49: RSS- . XML - . , - (, , ).

52—64: cnet.com — , . .

57: xbmcgui.ListItem. , . , , , () ().

59: . , , .

61: . . , .

63: . , sys.argv[1]. , - . , addDirectoryItem() . isFolder=True XBMC, — , . . , . url , , .

65: . , , .

67: XBMC, .

69: (). , , ( , ), , , , . Container.SetViewMode() , MyVideoNav.xml, MyMusicNav.xml MyPics.xml , .
, . — Youtube.

72—77: . . 500 «» Confluence, 512 — «-» «Aeon-Nox». .

80—89: , . , feed_list(), , URL isFolder False, . . ( ) , .
: isFolder=False , , xbmcplugin.addDirectoryItem. , ( ). xbmcplugin.addDirectoryItem .

92—119: . , . .

101: , . , , .

114: XBMC. _string(), XBMC. .

\resources
\resources — , .

settings.xml
settings.xml — , , XML, , «». «» .

: <?xml version="1.0" encoding="UTF-8"?> <settings> <category label="128"> <setting id="quality" type="labelenum" label="100500" values="HD|SD" default="HD" /> </category> </settings>

— (labelenum) (HD SD). label= , , . id="" (. 101 default.py).
settings.xml « XBMC Addon Developers Guide » ( ). , , .
XML \userdata\addon_data\ XBMC. Windows , , %AppData%\XBMC, Linux — $HOME/.xbmc.

\language
\language . , , , , strings.po. \English, \Russian \Ukrainian, . . , . , , \English . , , .

strings.po: # XBMC Media Center language file msgid "" msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: ru\n" msgctxt "#100500" msgid "Quality:" msgstr ":" msgctxt "#100501" msgid "Access error!" msgstr " !" msgctxt "#100502" msgid "Failed to retrieve the feed data." msgstr " ."

, .po GNU Gettext. XBMC Gettext: .mo, , msgctxt. : XBMC XML, www.transifex.net . -, .po , Gettext.
, getLocalizedString() msgctxt. : XBMC + . , . , , , , , . .
, . , .
, settings.xml .
, XBMC, . .

\lib
\lib , . \lib . , : , . . , . , , , , , , .

\thumbnails
\thumbnails cnet.com. \lib, , .


. XBMC. ( XBMC " "). print xbmc.log().
XBMC.
, XBMC Eclipse + PyDev. .


XBMC . , , . «» .
, , , .


, , Wiki XBMC: http://wiki.xbmc.org/index.php?title=Category:Addon_Development
«XBMC Addon Developers Guide»: yadi.sk/d/NvfFuXYw92paL
XBMC Python API: mirrors.xbmc.org/docs/python-docs

PS
Post factum . . XBMC , . .


XBMC : I — .

Source: https://habr.com/ru/post/193374/


All Articles