Este tutorial forma parte de la serie Cómo programar un add-on de XBMC
Estamos a mitad de camino de este tutorial, pero ya tienes el conocimiento necesario para hacer un add-on bastante completo. Espero que a estas alturas ya te hayas convencido de que es algo relativamente sencillo sin necesidad de saber demasiado de programación. Y ya sabes cómo obtener datos de una página HTML usando expresiones regulares, que es lo más difícil.
Así que antes de entrar en la recta final vamos construir un add-on paso a paso desde cero, con el objetivo de afianzar lo que has aprendido en las entregas anteriores y de paso divertirte un poco.
Te aconsejo que elijas un sitio web que te guste como fuente de vídeos, para mí he elegido hacer un add-on completo que permita ver los vídeos de Disney Junior tanto en la web oficial como en YouTube. No se trata de un add-on demasiado complejo, como podrás comprobar enseguida, pero no va a desmerecer en nada a cualquiera de los que puedes encontrar publicados en muchos repositorios
Puedes elegir tu propia fuente para hacer el add-on, siguiendo los mismos pasos que yo voy a dar, o construir conmigo este interesante proyecto.
Ficheros de identificación
Escribir el fichero addon.xml es el primer paso, así tendrás algo con lo que probar las primeras líneas de código que vayas escribiendo. Aquí va el mío donde verás que he cogido el del ejemplo de “Mi Mediacenter” cambiando el id del add-on (lo he llamado plugin.video.disneyjunior), el nombre (“Disney Junior”) y las descripciones:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<addon id="plugin.video.disneyjunior"
name="Disney Junior"
version="1.0.0"
provider-name="tvalacarta">
<requires>
<import addon="xbmc.python" version="2.0"/>
<import addon="plugin.video.youtube" version="3.0.0"/>
</requires>
<extension point="xbmc.python.pluginsource" library="default.py">
<provides>video</provides>
</extension>
<extension point="xbmc.addon.metadata">
<summary lang="en">Disney Junior</summary>
<description lang="en">Watch online Disney Junior videos (spanish)</description>
<summary lang="es">Disney Junior</summary>
<description lang="es">Todos los vídeos de Disney Junior online en Español</description>
<platform>all</platform>
</extension>
</addon>
Hacer el logo y el fanart requerirá un poco de práctica al principio, pero basta con que sepas manejar un programa de edición de imágenes donde puedas recortar y cambiar de tamaño. Un logo a 256×256 en PNG, y un fanart a 1280×720 en JPG. Ambos sacados de la web de Disney Junior y con algo de ayuda de Google.
Un menú de navegación
Un add-on que tenga algo más que unos pocos vídeos necesita un menú para que el usuario pueda elegir lo que quiere hacer. En mi caso tengo bastante claro el menú que tiene que tener un add-on de Disney Junior, porque conozco los gustos de mi hija pequeña.
Primero una opción “Web oficial” que muestre todos los vídeos de la web oficial de Disney Junior, seguido de otra opción “Canal de YouTube” para acceder a las playlists que la cadena tiene en su canal oficial de YouTube. Luego una entrada para recurrir al buscador, ya que solemos ponerle capítulos de Mickey Mouse buscándolos en YouTube, y por último una opción para configurar el add-on.
Hacer el menú es muy sencillo porque no hay que llamar a ningún sitio, simplemente añadir 4 elementos a la lista de items. En el fichero “default.py” de este add-on está hecho con estas líneas.
44 # Main menu
45 def main_list(params):
46 plugintools.log("disneyjunior.main_list "+repr(params))
47
48 plugintools.add_item(
49 action="disneyweb",
50 title=plugintools.get_localized_string(T_OFFICIAL_WEBSITE) ,
51 url="http://www.disney.es/disney-junior/contenido/video.jsp" ,
52 folder=True )
53
54 plugintools.add_item(
55 action="youtube_playlists",
56 title=plugintools.get_localized_string(T_YOUTUBE_CHANNEL),
57 url="http://gdata.youtube.com/feeds/api/users/"+YOUTUBE_CHANNEL_ID+"/playlists?v=2&start-index=1&max-results=30",
58 folder=True )
59
60 plugintools.add_item( action="search",
61 title=plugintools.get_localized_string(T_SEARCH) )
62
63 plugintools.add_item( action="preferences",
64 title=plugintools.get_localized_string(T_PREFERENCES),
65 folder = False )
Observarás que los títulos no están escritos directamente:
title = "Canal de YouTube"
sino que utilizan la función get_localized_string de Plugin Tools y un identificador numérico para la cadena de texto:
title = plugintools.get_localized_string(30003)
o mejor el identificador numérico de la cadena lo ponemos como una constante para mayor legibilidad:
T_YOUTUBE_CHANNEL=30003
title = plugintools.get_localized_string(T_YOUTUBE_CHANNEL)
Esto es así por el sistema que proporciona XBMC para tener un add-on donde los textos están adaptados al idioma de cada usuario. Es un poco lioso, pero simple y potente.
La idea es que a los textos que quieres traducir les asocias un identificador numérico (30003 en el ejemplo), y los escribes en un fichero “strings.xml” dentro del directorio “resources/language/Spanish”, “resources/language/English”, etc. Uno para cada idioma.
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<strings>
<string id="30001">Última búsqueda</string>
<string id="30002">Web oficial</string>
<string id="30003">Canal de YouTube</string>
<string id="30004">Buscador ...</string>
<string id="30005">Configuración ...</string>
<string id="30006">ID del canal de YouTube ...</string>
<string id="30007">Idioma para los resultados del buscador</string>
</strings>
Luego los pones en tu código usando la función get_localized_string a la que le pasas el identificador numérico.
Si el usuario tiene su XBMC en inglés, se leerá el fichero strings.xml del directorio “resources/language/English”. Si lo tiene en español, de “resources/language/Spanish”, etc. Si no pones literales en el idioma del usuario se cogerá por defecto los que estén en inglés, así que no olvides poner al menos ese idioma.
Mostrando los vídeos de la web oficial
Abriendo la zona de vídeos de la web de Disney Junior encontrarás el reproductor, y la lista de vídeos disponibles debajo. A mi hija le encanta porque pones un vídeo, y a partir de ahí los reproduce uno detrás de otro en carrusel.
Si le das en Firefox a “Herramientas / Desarrollador web / Código fuente de la página” accedes al HTML, y si buscas dentro del HTML la cadena del título de uno de los vídeos encontrarás enseguida el bloque de HTML que representa a cada vídeo.
Yo lo que suelo hacer para facilitar el trabajo es copiar el HTML que me interesa en cuanto lo localizo, lo pego en el módulo Python que estoy desarrollando dentro de un bloque de comentario, y lo utilizo como punto de partida para construir la expresión regular.
'''
<div class="promo" style="background-image: url(/cms_res/disney-junior/images/promo_support/promo_holders/video.png);">
<a href="/disney-junior/contenido/video/canta_con_dj_arcoiris.jsp " class="promoLinkTracking"><img src="/cms_res/disney-junior/images/video/canta_dj_arco_iris_164x104.jpg" class="promo_image" alt=""/></a>
<div class="promo_title_3row"><p>Canta con DJ: La canción del arco iris</p></div>
<a class="playlist_button_large" href="" ref="canta_con_dj_arcoiris"><img src="/cms_res/disney-junior/images/promo_support/playlist_add_icon.png" alt="" /></a>
</div>
'''
A continuación pego el html tal como está dentro de una variable, para ir sustituyendo paso a paso y cometer los menos errores posibles:
pattern ='<div class="promo" style="background-image: url(/cms_res/disney-junior/images/promo_support/promo_holders/video.png);">'
pattern+='<a href="/disney-junior/contenido/video/canta_con_dj_arcoiris.jsp " class="promoLinkTracking"><img src="/cms_res/disney-junior/images/video/canta_dj_arco_iris_164x104.jpg" class="promo_image" alt=""/></a>'
pattern+='<div class="promo_title_3row"><p>Canta con DJ: La canción del arco iris</p></div>'
pattern+='<a class="playlist_button_large" href="" ref="canta_con_dj_arcoiris"><img src="/cms_res/disney-junior/images/promo_support/playlist_add_icon.png" alt="" /></a>'
pattern+='</div>'
Y entonces empiezo a sustituir. Del primer tag sólo me interesa el principio ya que sirve para marcar el comienzo del patrón, así que dejo un trozo y luego utilizo el patrón [^<]+ para saltar al siguiente tag.
pattern = '<div class="promo"[^<]+'
El segundo tag del bloque tiene el enlace al vídeo en el atributo href y entre comillas, así que uso el patrón [^"]+ y lo marco entre paréntesis porque me interesa el contenido. Y a continuación salto al siguiente tag con [^<]+:
pattern += '<a href="([^"]+)"[^<]+'
Ahora viene un tag de imagen que necesito para tener la captura del vídeo, aunque esté en la misma línea del texto lo dejo separado para mayor legibilidad.
Cojo la URL de la imagen con el mismo patrón [^"]+ que antes, también entre paréntesis, y salto al siguiente tag con [^<]+:
pattern += '<img src="([^"]+)"[^<]+'
Ya tengo la URL del vídeo y su imagen, sólo necesito el título para poder mostrar la información completa en el add-on. Así que salto los tags que vienen a continuación hasta encontrar el tag <p> que contiene el título. Como es un valor que está entre tags y no en un atributo, utilizo el patrón ([^<]+).
pattern += '</a[^<]+'
pattern += '<div[^<]+'
pattern += '<p>([^<]+)</p>'
Podría haber usado (.*?) para saltar todos los elementos e ir directamente al título, pero intento guardar ese recurso cuando es realmente necesario. De esa forma consigo que la carga de los items sea lo más rápida posible.
Antes de poner el código de este bloque nos falta un detalle. Para evitar que haya algún elemento que coincida con el patrón que acabamos de escribir, pero que no corresponda con un vídeo, voy a limitar la zona de la página donde usaré la expresión regular al bloque donde están los vídeos contenidos.
Observa que todos los vídeos de la página están dentro de una zona que empieza con un <div> concreto, y acaban en una zona delimitada por la siguiente que también empieza con otro <div> concreto.
(aquí están todos los vídeos…)
Para delimitar el área de trabajo de la expresión regular a la zona entre estos dos tags, leo la página con read y la guardo en la variable “data”
data = plugintools.read( params.get("url") )
para inmediatamente después quedarme con la parte donde están los vídeos utilizando el patrón <div id=”video_main_promos_inner”>(.*?)<div id=”content_index_navigation”>. Que viene a ser “todo lo que está entre el primer div y el segundo”.
data = plugintools.find_single_match( data , '<div id="video_main_promos_inner">(.*?)<div id="content_index_navigation">')
Ya tengo todo lo necesario para escribir la función “disneyweb” que extrae los vídeos y los añade a la lista de items de XBMC:
67 # Show all videos from the official website
68 def disneyweb(params):
69 plugintools.log("disneyjunior.disneyweb "+repr(params))
70
71 # Fetch video list page
72 data = plugintools.read( params.get("url") )
73 data = plugintools.find_single_match( data , '<div id="video_main_promos_inner">(.*?)<div id="content_index_navigation">')
74
75 # Extract items
76 '''
77 <div class="promo" style="background-image: url(/cms_res/disney-junior/images/promo_support/promo_holders/video.png);">
78 <a href="/disney-junior/contenido/video/canta_con_dj_arcoiris.jsp " class="promoLinkTracking"><img src="/cms_res/disney-junior/images/video/canta_dj_arco_iris_164x104.jpg" class="promo_image" alt=""/></a>
79 <div class="promo_title_3row"><p>Canta con DJ: La canción del arco iris</p></div>
80 <a class="playlist_button_large" href="" ref="canta_con_dj_arcoiris"><img src="/cms_res/disney-junior/images/promo_support/playlist_add_icon.png" alt="" /></a>
81 </div>
82 '''
83 pattern = '<div class="promo"[^<]+'
84 pattern += '<a href="([^"]+)"[^<]+'
85 pattern += '<img src="([^"]+)"[^<]+'
86 pattern += '</a[^<]+'
87 pattern += '<div[^<]+'
88 pattern += '<p>([^<]+)</p>'
89 matches = plugintools.find_multiple_matches(data,pattern)
En este punto tengo en la variable “matches” la lista de videos, donde cada elemento de la lista es a su vez una lista con url, imagen y título, sólo falta añadirlos a la lista de items con la función add_item de Plugin Tools.
91 for scrapedurl, scrapedthumbnail, scrapedtitle in matches:
92 # Not the better way to parse XML, but clean and easy
93 title = scrapedtitle
94 thumbnail = urlparse.urljoin( params.get("url") , scrapedthumbnail )
95 url = urlparse.urljoin( params.get("url") , scrapedurl.strip() )
96 plot = ""
97
98 # Appends a new item to the xbmc item list
99 plugintools.add_item( action="disneyweb_play" , title=title , plot=plot , url=url ,thumbnail=thumbnail , isPlayable=True, folder=False )
Observa que la URL y la imagen (o thumbnail) se añaden usando primero la función estándar de Python “urljoin”, lo que viene bien cuando te encuentras con URL relativas en lugar de absolutas. Esta función crea una URL utilizando el primer parámetro como URL base, y el segundo como ruta relativa.
Y observa también que el elemento que añadimos a la lista de items no es una carpeta (folder=False), porque cuando el usuario seleccione uno de estos items simplemente se reproducirá el vídeo con la función “disneyweb_play”. No va a ser necesario que el usuario descienda más niveles, y por eso lo marcamos como “no carpeta”.
Reproducción del vídeo
Cuando el usuario elija un vídeo de la lista anterior, XBMC llamará a la acción “disneyweb_play” y le pasará como parámetro la URL del vídeo elegido entre otras cosas.
Lo primero que hay que hacer es leer esa URL para tener la información del vídeo, usando la función read nuevamente:
101 # Play one video from the official website
102 def disneyweb_play(params):
103 plugintools.log("disneyjunior.disneyweb_play "+repr(params))
104
105 # Fetch page
106 data = plugintools.read( params.get("url") )
Y ahora utilizar esa información para deducir la URL que hay que darle al reproductor de XBMC. En este caso es fácil porque la URL utiliza el protocolo “RTMPE”, y está en el código HTML separada en dos líneas diferentes de forma bastante clara:
config.firstVideoSource = 'winnie_heffa_woozles.mp4';
config.htmlContainerName = 'video_player';
config.loop = false;
config.autoplay = true;
config.rtmpeServer = 'rtmpe://cp121902.edgefcs.net/ondemand/';
Así que sólo tenemos que extraer los dos fragmentos y componerlos para tener la URL completa.
108 url_start = plugintools.find_single_match( data , "config.rtmpeServer \= '([^']+)'")
109 plugintools.log("disneyjunior.disneyweb_play url_start="+url_start)
110
111 url_end = plugintools.find_single_match( data , "config.firstVideoSource \= '([^']+)'")
112 plugintools.log("disneyjunior.disneyweb_play url_end="+url_end)
113
114 url = url_start + url_end
115 plugintools.log("disneyjunior.disneyweb_play url="+url)
116
117 plugintools.play_resolved_url( url )
Con play_resolved_url le decimos a XBMC que está listo para la reproducción, así que directamente empezará a verse el vídeo.
Próxima entrega
La extensión de esta entrada me obliga a dividirla en dos, todavía queda ver cómo montar la sección del canal de YouTube aunque resulta similar a la del ejemplo que vimos al principio de esta serie de tutoriales.
También veremos cómo funciona el buscador y la configuración, pero si has llegado hasta aquí y quieres echarle un vistazo al add-on terminado descargándotelo desde este enlace.
http://www.mimediacenter.info/descargas/plugin.video.disneyjunior-1.0.0.zip
Si no tienes hijos en edad de La Casa de Mickey Mouse, pónselo a tus sobrinos. Seguro que sus padres te preguntan cómo pueden instalárselo ellos también
via Mi media center http://www.mimediacenter.info/2013/01/12/como-programar-add-ons-en-xbmc-un-add-on-mas-completo/
Comentaris
Publica un comentari a l'entrada