Пишем свой плагин для XBMC. Пока без блекджека и всех остальных

от автора


Всем привет. Речь в топике пойдёт о создании плагина (программного дополнения, аддона) к замечательной программе XBMC. Уровень сложности: для начинающих. Понадобятся знания HTML и общее представление о работе сайтов; не помешает знать как выглядит Python. Не ждите под катом уникальных алгоритмов и магического кода, это скорее отправная точка и общее объяснение механики работы плагинов. Код будет, надеюсь, наглядным.

Некоторые из вас могут спросить: «Ведь есть репозиторий seppius, который решает почти все проблемы с воспроизведением онлайн-контента в рунете (в контексте XBMC). Зачем велосипеды?». Я приведу свои доводы в небольшом предисловии.

Предисловие

Я занялся разработкой плагинов XBMC, когда у меня появился лишний ноутбук. Было принято решение, чтобы мёртвым грузом не валялся, сделать мультимедиа приставку для телевизора (TV был лишён связи с Всемирной паутиной). XBMC был шикарным решением проблемы, но вот незадача: живу-то я в Кыргызстане. Весь трафик с IP-адресов не относящихся к нашей стране очень дорог («внешка», не знаю есть ли у вас подобное? Лишь пару месяцев назад стали появляться доступные безлимитные тарифы). А вот внутренний трафик практически бесплатен (многие популярные сайты с медиа-контентом вообще не тарифицируются у многих провайдеров), а вот XBMC как-будто вообще никто до меня не пользовался. Придумано, сделано. К моменту написания статьи мной охвачены все нужные ресурсы. Теперь занимаюсь разработкой PVR плагина.

Я уже не пишу на Python код такой, какой будет приведён в статье. Но это было моей отправной точкой и пониманием механизмов работы дополнений.

Подготовка

Нам понадобятся:

  • Python 2.x не перепутайте с 3.x (здесь сказано что в XBMC используется версия 2.4, но мне кажется что информация устарела);
  • XBMC 11 Eden или XBMC 12 Frodo (сейчас для нас разницы нет, но лучше использовать последнюю версию, хоть она и RC)
  • Ваш любимый текстовый редактор

Устанавливаем вышеуказанные. Путь у Вас может отличаться – в скобках я указал свой: Python (C:\Python\, далее PathPython)

Не будет лишнем знать где лежит лог-файл XBMC:
Windows: %APPDATA%\XBMC\xbmc.log
Linux: $HOME/.xbmc/temp/xbmc.log
Mac OS X: /Users//Library/Logs/xbmc.log

Часть первая. Тренировка

Отмечу, что мои знания Python’а были практически нулевые. Поэтому, если и Вы с ним никогда не сталкивались, то пугаться не стóит. В изучении той области функционала, которая нам нужна, он очень прост. Ну, по крайней мере, мне так показалось.

В качестве подопытного кролика был выбран сайт LineCinema. Почему? Потому что гладиолус я зашёл в тему Запросы на создание плагинов (XBMC Russia) и на одной из последних страниц (если хотите точности, то на 87-ой) увидел запрос для этого сайта. Далее просто Сайт.

Итак, создаём в текстовом редакторе новый файл и пишем следующий код:

# -*- coding: utf-8 -*-  # Импортируем нужные нам библиотеки import urllib, urllib2, re, sys, os  # Функция для получения исходного кода web-страниц def GetHTML(url):     headers = {'User-Agent':'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-GB; rv:1.9.0.3) Gecko/2008092417 Firefox/3.0.3', 'Content-Type':'application/x-www-form-urlencoded'}     conn = urllib2.urlopen(urllib2.Request(url, urlencode({}), headers))     html = conn.read()     conn.close()          return html   # Тест на работоспособность html = GetHTML('http://www.linecinema.org/') print html 

Сохраняем PathPython/test.py

Открываем Командную строку и переходим в папку PathPython. Выполняем:

python test.py

Результатом её выполнения должен быть исходный код страницы Сайта.

Визуально

Теперь, в браузере открываем Сайт и лезем в исходный код (различные веб-инспекторы и firebug нам не помогут). Здесь надо обратить внимание на строчку

<meta http-equiv="Content-Type" content="text/html; charset=windows-1251" /> 

и не забыть, что кодировка страницы windows-1251.

Ищем ссылки раздела «Фильмы По Жанрам» и узнаём, что они имеют вид:

<a href="/newsz/drama-online/" title="" class="mainmenu">Драма</a><br /> 

Переключаемся (или открываем, если успели закрыть) на test.py. Удаляем строку

print html 

И на её место пишем

genre_links = re.compile('<a href="(.+?)" title="" class="mainmenu">(.+?)</a><br />').findall(html.decode('windows-1251'))  # Сопоставляем результаты первого и второго (.+?) с переменными url и title (будут присвоены в порядке указания) for url, title in genre_links:     print title + ' [' + url + ']' 

Где (.+?) регулярное выражение, пропускающее все символы (про регулярные выражения Python можно почитать на хабре).

Сохраняем файл и повторяем в Командной строке

python test.py

На этот раз результатом будут названия жанров и ссылка.

Визуально

Но есть лишние ссылки, которые в XBMC будут бесполезны: «Главная», «Заказать фильм», «Тех. Поддержка». Что с ними делать? Мы их исключим. Для начала грубо, хотя можно было бы использовать BeautifulSoup.

Напишем функцию isLinkUseful(), специально для Сайта, т. к. «лишние» ссылки будут объявлены внутри:

def isLinkUseful(needle):     haystack = ['/index.php', '/newsz/Televydenye/100432-2008-3-11-432.html', '/newsz/500183-tex-podderzhka.html']  # список “лишних” ссылок     for a in haystack:         if needle == a:             return False      return True 

и изменим последние строчки:

for url, title in genre_links:     if isLinkUseful(url):         print title + ' [' + url + ']' 

Проверяем. Всё как надо:

Визуально

Ещё немного тренировки. Теперь проверим один из разделов. Я взял «Документальный» (/newsz/dokumentalnyij-online/). Изменим код:

#url = 'http://www.linecinema.org/' #html = GetHTML(url) #genre_links = re.compile('<a href="(.+?)">(.+?)</a>').findall(html.decode('windows-1251')) #for url, title in genre_links:     #if isLinkUseful(url):         #print title + ' [' + url + ']'  url = 'http://www.linecinema.org/newsz/dokumentalnyij-online/' html = GetHTML(url) genre_links = re.compile('<h1> <a href="(.+?)">(.+?)</a>   </h1>').findall(html.decode('windows-1251'))  for url, title in genre_links:     print title 

Проверяем и видим, что здесь исключать ничего не надо:

Визуально

Тут мы использовали <h1> <a href="(.+?)">(.+?)</a> </h1> (да-да, с пробелами), т. к. в исходном коде тоже с пробелами и если бы мы использовали просто <a href="(.+?)">(.+?)</a>, то получили бы все ссылки со страницы. А оно нам надо?

Часть вторая. Адаптируемся к XBMC

Для начала было бы неплохо создать папку (где Вам больше всего нравится). И название можно было бы оставить «Новая папка (∞)», но не в этот раз. Прочитав Рекомендации к разработке дополнений можно узнать, что название папки составляется по следующему шаблону:

<addonType>[.<mediaType>].<yourPluginName>

Следуя этому шаблону (я надеюсь, Вы всё-таки прочтёте рекомендации) название будет следующим:

plugin.video.linecinema

Зайдем внутрь папки и заполним пустоту. Официальные требования к видео/аудио/некоторым другим add-on’ам (если Вы до сих пор не в курсе, мы делаем именно видео-плагин) гласят, что структура должна быть следующей:

addon.py
addon.xml
changelog.txt
fanart.jpg
icon.png
LICENSE.txt
/resources
    settings.xml
    /language/
    /lib/
    /media/

  • addon.py – здесь будет находится основной код плагина. В принципе, название не имеет значения (так как мы сами укажем его в файле addon.xml). В большинстве плагинов он назван default.py
  • addon.xml – сообщает XBMC: тип плагина (видео, аудио, изображения, скрипт и так далее); какой файл надо выполнить при обращении (тот самый *.py); платформу и зависимости; версию, автора и описание плагина. О форматировании можно почитать здесь.
  • changelog.txt, LICENSE.txt – надеюсь, понятно из названия.
  • fanart.jpg – фоновое изображение плагина. Ссылка на рекомендации.
  • icon.png – «лицо» нашего плагина. Рекомендации.
  • /resources/settings.xml – хранит в себе настройки плагина (ну, к примеру, логин/пароль для авторизации на сайтах), т.е. то, что мы хотим использовать в addon.py. Хранит не сами значения конечно же, а разметку формы, куда мы будем эти значения вписывать. Описание и формат.
  • /resources/language/ — языковые файлы. Если хотите, чтобы Ваш плагин попал в официальный репозиторий, то абсолютно всё надо переводить.
  • /resources/lib/ — лучшее место для хранения дополнительных библиотек Python.
  • /resources/media/ — сюда прятать изображения, звуки, видео и т. п.

Из всего вышеперечисленного мы задействуем: addon.py, addon.xml и… всё. Но в конце статьи можно найти архив, в котором сохранена вся структура.

Код файла addon.xml:

<?xml version="1.0" encoding="utf-8" standalone="yes"?> <addon id="plugin.video.linecinema"        name="LineCinema"        version="0.0.1"        provider-name="noname">   <requires>     <import addon="xbmc.python" version="1.0"/>   </requires>   <extension point="xbmc.python.pluginsource" provides="video" library="addon.py">     <provides>video</provides>   </extension>   <extension point="xbmc.addon.metadata">     <summary>LineCinema.org</summary>     <description> Привет, Хабрахабр.  Это тестовый плагин для сайта LineCinema.org     </description>     <platform>all</platform>   </extension> </addon> 

Остановимся подробнее. Из строки <addon id="plugin.video.linecinema" name="LineCinema" version="0.0.1" provider-name="noname"> XBMC узнает, что директория с плагином называется «plugin.video.linecinema», отображаемое название «LineCinema», версия 0.0.1, имя автора «noname».

Из строки <requires><import addon="xbmc.python" version="1.0"/></requires>, что для работы просто необходима библиотека xbmc.python версии 1.0.

Далее <extension point="xbmc.python.pluginsource" provides="video" library="addon.py"><provides>video</provides></extension> точка входа – файл «addon.py». А сам плагин надо разместить во вкладке «Видео дополнения».

Если остались непонятные места, то попробуйте перечитать формат файла addon.xml.

Перейдём к файлу addon.py:

# -*- coding: utf-8 -*-  import urllib, urllib2, re, sys import xbmcplugin, xbmcgui  def get_params():     param=[]     paramstring=sys.argv[2]     if len(paramstring)>=2:         params=sys.argv[2]         cleanedparams=params.replace('?','')         if (params[len(params)-1]=='/'):             params=params[0:len(params)-2]         pairsofparams=cleanedparams.split('&')         param={}         for i in range(len(pairsofparams)):             splitparams={}             splitparams=pairsofparams[i].split('=')             if (len(splitparams))==2:                 param[splitparams[0]]=splitparams[1]                                  return param 

xbmcplugin и xbmcgui – модули из библиотеки xbmc.python.

Функция get_params() – честно говоря, даже не пытался разобраться в том, что она делает. Шестым чувством догадываюсь, что разбивает строку обращения плагина на параметры, позволяющее сохранять переменные при переходе по директориям плагина. Если непонятно, то не кидайте тухлое яйцо в меня. Это функция, которая очень часто встречается в плагинах, взята из одного из них.

Пишем дальше:

def addLink(title, url):     item = xbmcgui.ListItem(title, iconImage='DefaultVideo.png', thumbnailImage='')     item.setInfo( type='Video', infoLabels={'Title': title} )      xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]), url=url, listitem=item)   def addDir(title, url, mode):     sys_url = sys.argv[0] + '?title=' + urllib.quote_plus(title) + '&url=' + urllib.quote_plus(url) + '&mode=' + urllib.quote_plus(str(mode))      item = xbmcgui.ListItem(title, iconImage='DefaultFolder.png', thumbnailImage='')     item.setInfo( type='Video', infoLabels={'Title': title} )      xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]), url=sys_url, listitem=item, isFolder=True) 

addDir(title, url, mode) – будет создавать пункт для перехода по ссылке и отображения новых пунктов. Mode – переменная, хранящая номер функции для обработки ссылки (ниже будет понятно что к чему).

addLink(title, url) – будет создавать пункт, при переходе на который начнётся воспроизведение url. В url должна храниться прямая ссылка на видео/аудио/изображение и т.п.

Добавляем ниже:

params = get_params() url    = None title  = None mode   = None  try:    title = urllib.unquote_plus(params['title']) except: pass  try:    url = urllib.unquote_plus(params['url']) except: pass  try:    mode = int(params['mode']) except: pass  xbmcplugin.endOfDirectory(int(sys.argv[1])) 

Это будут наши «глобальные переменные». А xbmcplugin.endOfDirectory(int(sys.argv[1])) сообщит XBMC, что это конец «директории» и больше пунктов не будет.

Осталось совсем немного. Добавляем наши функции getHTML(url) и isLinkUseful(needle) из первой части и теперь addon.py должен выглядеть следующим образом:

addon.py

# -*- coding: utf-8 -*-  import urllib, urllib2, re, sys import xbmcplugin, xbmcgui  def getHTML(url):     headers = {'User-Agent':'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-GB; rv:1.9.0.3) Gecko/2008092417 Firefox/3.0.3', 'Content-Type':'application/x-www-form-urlencoded'}     conn = urllib2.urlopen(urllib2.Request(url, urllib.urlencode({}), headers))          html = conn.read()     conn.close()          return html  def isLinkUseful(needle):     haystack = ['/index.php', '/newsz/Televydenye/100432-2008-3-11-432.html', '/newsz/500183-tex-podderzhka.html']     for a in haystack:         if needle == a:             return False      return True  def get_params():     param=[]     paramstring=sys.argv[2]     if len(paramstring)>=2:         params=sys.argv[2]         cleanedparams=params.replace('?','')         if (params[len(params)-1]=='/'):             params=params[0:len(params)-2]         pairsofparams=cleanedparams.split('&')         param={}         for i in range(len(pairsofparams)):             splitparams={}             splitparams=pairsofparams[i].split('=')             if (len(splitparams))==2:                 param[splitparams[0]]=splitparams[1]                                  return param   def addLink(title, url):     item = xbmcgui.ListItem(title, iconImage='DefaultVideo.png', thumbnailImage='')     item.setInfo( type='Video', infoLabels={'Title': title} )      xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]), url=url, listitem=item)   def addDir(title, url, mode):     sys_url = sys.argv[0] + '?title=' + urllib.quote_plus(title) + '&url=' + urllib.quote_plus(url) + '&mode=' + urllib.quote_plus(str(mode))      item = xbmcgui.ListItem(title, iconImage='DefaultFolder.png', thumbnailImage='')     item.setInfo( type='Video', infoLabels={'Title': title} )      xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]), url=sys_url, listitem=item, isFolder=True)   params = get_params() url    = None title  = None mode   = None  try:    title = urllib.unquote_plus(params['title']) except: pass  try:    url = urllib.unquote_plus(params['url']) except: pass  try:    mode = int(params['mode']) except: pass  xbmcplugin.endOfDirectory(int(sys.argv[1])) 

У Вас так же? Если да, то отлично.

Теперь опять вспомним первую часть и проанализируем Сайт: у нас будет список жанров; было бы неплохо найти и показать какие фильмы есть в каждой из категорий; отобразить ссылку на просмотр фильма. Получается нам нужны три функции. Назовём их так: Categories() – список жанров, Movies() – список доступных фильмов, Videos() – ссылки на видео. Приступим:

def Categories(): 	url = 'http://www.linecinema.org' 	html = getHTML(url) 	genre_links = re.compile('<a href="(.+?)" title="" class="mainmenu">(.+?)</a><br />').findall(html.decode('windows-1251').encode('utf-8'))  	for link, title in genre_links: 	    if isLinkUseful(link): 	        addDir(title, url + link, 20) 

addDir(title, url + link, 20) заменил нам print title + ' [' + url + ']'. Categories() не будет иметь входных параметров — url нам надо объявить самим. Обратите внимание, что последний слеш убран – он будет компенсирован переменной link.

Цифра 20 – сообщит, что следующая функция будет Movies(url). Но ведь магии не бывает и нам надо написать для этого в конец файла, перед xbmcplugin.endOfDirectory(int(sys.argv[1])), проверку:

if mode == None:     Categories()  elif mode == 20:     Movies(url)  elif mode == 30:     Videos(url, title) 

Чем это попахивает тут? Не говнокодом ли? Ну точно, им самым. Но такая модель помогла мне разобраться что к чему, сейчас я её, конечно же, не использую. А вот мой первый плагин был точно таким же.

Не отвлекаемся и парсим список фильмов:

def Movies(url): 	html = getHTML(url) 	movie_links = re.compile('<h1> <a href="(.+?)">(.+?)</a>   </h1>').findall(html.decode('windows-1251').encode('utf-8'))  	for link, title in movie_links: 	    addDir(title[:-12], link, 30) 

Опять код из первой части. title[:-12] что это за страшный смайлик? Если убрать [:-12], то вы увидите, что название будет иметь вид «Умопомрачительный фильмец / Magnificent movie (2013) HDRip онлайн». И зачем нам «онлайн»? Убрать его!

Теперь надо выдернуть прямую ссылку на видео. Открываем любую ссылку фильма с Сайта и смотрим его исходный код. Ого, да тут нас ждали! Видим вот такой блок:

<script language="javascript">  	flashvars = {  	uid: "player_uppod",  	bufferproc2 :1,  	bufferproc_reloadsec :10,  	st: "/templates/linecinema-dle90/swf/video30-365.txt",  	file: "http://st7.linecinema.org/s/820e31e7cbe3e8c362785b733db56c57/film10/Druzea.navek.DVDRip.flv"  	};  	params = {bgcolor:"#ffffff", allowFullScreen:"true", allowScriptAccess:"always",wmode:"opaque"};  	attributes = {  	id: "player_uppod",  	name: "player_uppod"  	};  	swfobject.embedSWF("/templates/linecinema-dle90/swf/uppod.swf", "player_uppod", "570", "440", "10.0.0",false,flashvars,params,attributes);  	</script> 

А вот и ссылка: http://st7.linecinema.org/s/820e31e7cbe3e8c362785b733db56c57/film10/Druzea.navek.DVDRip.flv (у Вас может отличаться). Исходя из этого делаем функцию:

def Videos(url, title): 	html = getHTML(url) 	link = re.compile('file:   "(.+?)"').findall(html.decode('windows-1251').encode('utf-8'))[0]  	addLink(title, link) 

С кодом закончили. Сравните свой результат с моим:

Файл addon.py

# -*- coding: utf-8 -*-  import urllib, urllib2, re, sys import xbmcplugin, xbmcgui  def getHTML(url):     headers = {'User-Agent':'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-GB; rv:1.9.0.3) Gecko/2008092417 Firefox/3.0.3', 'Content-Type':'application/x-www-form-urlencoded'}     conn = urllib2.urlopen(urllib2.Request(url, urllib.urlencode({}), headers))          html = conn.read()     conn.close()          return html  def isLinkUseful(needle):     haystack = ['/index.php', '/newsz/Televydenye/100432-2008-3-11-432.html', '/newsz/500183-tex-podderzhka.html']     for a in haystack:         if needle == a:             return False      return True  def Categories(): 	url = 'http://www.linecinema.org' 	html = getHTML(url) 	genre_links = re.compile('<a href="(.+?)" title="" class="mainmenu">(.+?)</a><br />').findall(html.decode('windows-1251').encode('utf-8'))  	for link, title in genre_links: 	    if isLinkUseful(link): 	        addDir(title, url + link, 20)  def Movies(url): 	html = getHTML(url) 	movie_links = re.compile('<h1> <a href="(.+?)">(.+?)</a>   </h1>').findall(html.decode('windows-1251').encode('utf-8'))  	for link, title in movie_links: 	    addDir(title[:-12], link, 30)  def Videos(url, title): 	html = getHTML(url) 	link = re.compile('file:   "(.+?)"').findall(html.decode('windows-1251').encode('utf-8'))[0]  	addLink(title, link)   def get_params():     param=[]     paramstring=sys.argv[2]     if len(paramstring)>=2:         params=sys.argv[2]         cleanedparams=params.replace('?','')         if (params[len(params)-1]=='/'):             params=params[0:len(params)-2]         pairsofparams=cleanedparams.split('&')         param={}         for i in range(len(pairsofparams)):             splitparams={}             splitparams=pairsofparams[i].split('=')             if (len(splitparams))==2:                 param[splitparams[0]]=splitparams[1]                                  return param   def addLink(title, url):     item = xbmcgui.ListItem(title, iconImage='DefaultVideo.png', thumbnailImage='')     item.setInfo( type='Video', infoLabels={'Title': title} )      xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]), url=url, listitem=item)   def addDir(title, url, mode):     sys_url = sys.argv[0] + '?title=' + urllib.quote_plus(title) + '&url=' + urllib.quote_plus(url) + '&mode=' + urllib.quote_plus(str(mode))      item = xbmcgui.ListItem(title, iconImage='DefaultFolder.png', thumbnailImage='')     item.setInfo( type='Video', infoLabels={'Title': title} )      xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]), url=sys_url, listitem=item, isFolder=True)   params = get_params() url    = None title  = None mode   = None  try:    title = urllib.unquote_plus(params['title']) except: pass  try:    url = urllib.unquote_plus(params['url']) except: pass  try:    mode = int(params['mode']) except: pass  if mode == None:     Categories()  elif mode == 20:     Movies(url)  elif mode == 30:     Videos(url, title)  xbmcplugin.endOfDirectory(int(sys.argv[1])) 

Наконец-то, перейдём к установке плагина. Существует два простых способа: установка из репозитория и установка из файла ZIP. Нам подходит второй. Запаковываем в ZIP нашу папку (папку!) любым архиватором. Затем запускаем XBMC, переходим Система (Настройки) > Дополнения > Установить из файла ZIP и указываем путь до нашего архива. После сообщения о том, что «Дополнение успешно включено», идём в главное меню Видео > Дополнения > LineCinema.

Скриншоты




Полезные ссылки

Спасибо за внимание. Надеюсь было интересно… или полезно. Не исключаю, что у меня ужасная манера изложения, но это первый опыт в написании статей.

P.S. Пожалуйста, не кидайте в меня тухлые помидоры.

ссылка на оригинал статьи http://habrahabr.ru/post/166097/