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\
<?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>
<import addon="plugin.video.youtube" version="4.0.0"/>
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. 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