Ayer vimos cómo añadir la sección de vídeos de la web oficial de Disney Junior, analizando paso a paso la extracción de los vídeos del listado, cómo añadirlos a la lista de items de XBMC, y cómo reproducir un vídeo seleccionado por el usuario.
Hoy terminamos el add-on añadiendo otra sección para ver los vídeos del canal de YouTube de Disney Junior, muy útil porque tiene una playlist para cada una de las series incluyendo algunos extras como el karaoke «Canta con Disney Junior».
También veremos cómo utilizar el teclado en pantalla de XBMC para incluir un buscador, que nos permita acceder fácilmente a una lista de resultados relevantes en YouTube, y cómo utilizar el sistema de configuración de un add-on en XBMC para dejar que el usuario personalice algunos aspectos.
¿Comenzamos?
Las series del canal de YouTube
Si recuerdas el add-on con el que empezó esta serie de tutoriales, uno sencillito que permitía acceder a los vídeos de mi propio canal de YouTube, verás que hacer lo mismo con cualquier otro canal de YouTube no es más complicado que cambiar el ID del canal en el código.
Sin embargo ese add-on utilizaba el API de YouTube para obtener la lista de vídeos subidos al canal
http://gdata.youtube.com/feeds/api/users/tvalacarta/uploads
pero para el de Disney Junior lo que nos interesa es tener la lista de las «playlists» que el propietario del canal ha definido.
http://gdata.youtube.com/feeds/api/users/DisneyJuniorES/playlists
De esta forma al seleccionar la opción «Canal de YouTube» obtendremos una lista de las series de Disney Junior, y al seleccionarlas podremos obtener una lista de vídeos de esa serie. Cada niño tiene sus preferencias.
Aunque podríamos parsear el XML que nos devuelve YouTube usando las librerías de XML de Python, probablemente de una forma más elegante, lo normal en un add-on va a ser encontrarse HTML así que vamos a usar expresiones regulares. Además quedará más compacto, créeme.
Como la estructura de un feed RSS es una sucesión de tags <entry> para cada vídeo, con la información del vídeo dentro de esos tags, vamos a utilizar una forma un poco distinta de extraer los datos.
En primer lugar vamos a utilizar una expresión regular sencilla que nos va a devolver un array con el texto que hay dentro de cada uno de los bloques <entry>.
119 # Show all YouTube playlists for the selected channel 120 def youtube_playlists(params): 121 plugintools.log("disneyjunior.youtube_playlists "+repr(params)) 122 123 # Fetch video list from YouTube feed 124 data = plugintools.read( params.get("url") ) 125 plugintools.log("data="+data) 126 127 # Extract items from feed 128 pattern = "" 129 matches = plugintools.find_multiple_matches(data,"<entry(.*?)</entry>")
Y ahora vamos a recorrer ese array extrayendo los datos de los vídeos para añadirlos a la lista de items de XBMC. Como dentro de un tag <entry> sólo hay un vídeo, usaremos la función find_single_match de Plugin Tools para sacar los valores.
131 for entry in matches: 132 plugintools.log("entry="+entry)
El título está entre los tags <title>, pero utilizaremos una curiosa expresión regular en este caso.
135 title = plugintools.find_single_match(entry,"<titl[^>]+>([^<]+)</title>")
Si utilizo como expresión regular
<title>([^<]+)</title>
El patrón fallará cuando se encuentre con cosas como esta:
"<title >Ejemplo</title> "<title lang="es">Ejemplo</title> ...
Así que sustituyo <title> por <titl[^>]+> de forma que cuando el patrón haya encontrado las primeras letras del nombre del tag «titl», deje de buscar para ir directamente al cierre de tag «>». Esto nos lo habríamos evitado usando un parser XML tradicional, pero el truco es útil también en HTML 🙂
El resto de elementos del feed se extraen de forma similar. El argumento del vídeo está en el tag <media:description>, la imagen en el tag <media:thumbnail> y la URL de la lista de vídeos en el tag <content> que tiene como atributo type «application/atom+xml;type=feed».
Observa que hay un caracter «\» antes de los símbolos «=», «+» y «;» debido a que tienen un significado especial dentro de una expresión regular, pero en este caso nos interesa su valor literal. Queremos la cadena «atom+xml», y no la interpretación de «uno o más» que tiene el símbolo «+».
136 plot = plugintools.find_single_match(entry,"<media\:descriptio[^>]+>([^<]+)</media\:description>") 137 thumbnail = plugintools.find_single_match(entry,"<media\:thumbnail url='([^']+)'") 138 url = plugintools.find_single_match(entry,"<content type\='application/atom\+xml\;type\=feed' src='([^']+)'/>") 139 140 # Appends a new item to the xbmc item list 141 plugintools.add_item( action="youtube_videos" , title=title , plot=plot , url=url , thumbnail=thumbnail , folder=True )
Al añadir cada elemento a la lista de items de XBMC indicamos la acción «youtube_videos», que mostrará la lista de vídeos incluidos en la playlist de la URL.
Con esto ya tenemos la lista de playlists del canal, no tenemos que preocuparnos de la paginación como en el plugin de «Mi media center» ya que la llamada de YouTube no permite paginación y siempre devuelve la lista completa.
Los vídeos de cada serie
Como la URL de la lista de vídeos está en el parámetro «url», y lo que estamos procesando es nuevamente un feed XML, la función «youtube_videos» acaba siendo muy similar a la anterior así que no vamos a verla en detalle.
Observa únicamente que en este caso la URL que añadimos tiene el esquema «plugin://plugin.video.youtube/», ya que estamos componiéndola de forma que al seleccionar el vídeo se llame al add-on de YouTube para su reproducción. Y como el elemento que añadimos en este paso es directamente reproducible, lo marcamos como «folder=False» para que XBMC sepa que no tiene que descender más.
144 # Show all YouTube videos for the selected playlist 145 def youtube_videos(params): 146 plugintools.log("disneyjunior.youtube_videos "+repr(params)) 147 148 # Fetch video list from YouTube feed 149 data = plugintools.read( params.get("url") ) 150 plugintools.log("data="+data) 151 152 # Extract items from feed 153 pattern = "" 154 matches = plugintools.find_multiple_matches(data,"<entry(.*?)</entry>") 155 156 for entry in matches: 157 plugintools.log("entry="+entry) 158 159 # Not the better way to parse XML, but clean and easy 160 title = plugintools.find_single_match(entry,"<titl[^>]+>([^<]+)</title>") 161 title = title.replace("Disney Junior España | ","") 162 plot = plugintools.find_single_match(entry,"<summa[^>]+>([^<]+)</summa") 163 thumbnail = plugintools.find_single_match(entry,"<media\:thumbnail url='([^']+)'") 164 video_id = plugintools.find_single_match(entry,"http\://www.youtube.com/watch\?v\=([0-9A-Za-z_-]{11})") 165 url = "plugin://plugin.video.youtube/?path=/root/video&action=play_video&videoid="+video_id 166 167 # Appends a new item to the xbmc item list 168 plugintools.add_item( action="play" , title=title , plot=plot , url=url , thumbnail=thumbnail , isPlayable=True, folder=False )
Cuando el usuario selecciona un vídeo se llama a la función «play».
Reproducción del vídeo
La función «play» es muy sencilla, a diferencia del caso de «disneyweb_play» que veíamos ayer, ya que la URL que viene como parámetro es directamente algo que XBMC sabe reproducir.
169 # Play selected vieo 170 def play(params): 171 plugintools.play_resolved_url( params.get("url") )
Una simple llamada a play_resolved_url de Plugin Tools le indica a XBMC que puede empezar la reproducción.
Configuración
Antes de ver el buscador es necesario conocer primero cómo funciona el mecanismo que XBMC pone a disposición de los add-ons para gestionar los parámetros de configuración. Esto es así porque el buscador hace uso de algunos de estos parámetros.
Para que tu add-on tenga configuración tienes que crear un fichero «settings.xml» dentro del directorio «resources», donde especificar uno por uno los parámetros que vas a emplear.
<?xml version="1.0" encoding="utf-8" standalone="yes"?> <settings> <setting id="youtube_channel_id" type="text" option="writeable" label="30006" default="DisneyJuniorES"/> <setting id="youtube_language" type="text" option="writeable" label="30007" default="es"/> <setting id="last_search" type="text" option="writeable" label="30001" default="la casa de mickey mouse"/> </settings>
En este caso he definido 3 parámetros
- youtube_channel_id: Indica el ID del canal de YouTube que se mostrará cuando el usuario seleccione esta opción, con un valor por defecto de «DisneyJuniorES» que es la versión española. Puedes poner «DisneyJuniorLA», «DisneyJuniorUK», … o cualquier otro 🙂
- youtube_language: Indica el idioma de los resultados de búsqueda, por defecto «es» para que salgan vídeos en español pero puedes poner cualquier código de idioma ISO.
- last_search: Es la última búsqueda que has introducido, para que la siguiente vez que uses el buscador la recuerde y te la proponga
Observa que los parámetros tienen un atributo «type» que te permite indicar «text», como en este caso, pero hay otros muchos tipos como «number», «folder», … La lista completa está en la Wiki de XBMC.
El texto que se muestra al usuario está en el atributo «label», donde puedes poner directamente una cadena de texto o como en mi caso el ID de la cadena en el fichero strings.xml. Así el diálogo de configuración estará también traducido al idioma del usuario.
Con este fichero presente tu add-on ya es configurable, y si en la lista de add-ons instalados seleccionas la opción «Configuración del Add-on» con el menú contextual podrás acceder al diálogo de configuración.
Como esto es algo complejo para el usuario yo prefiero poner una opción dentro del add-on para abrir el cuadro de diálogo, en este caso una que llama a la función «preferences» que a su vez encarga de abrir este cuadro de diálogo.
Y lo hace con la función open_settings_dialog de Plugin Tools.
173 # Open preferences dialog 174 def preferences(params): 175 plugintools.log("disneyjunior.preferences "+repr(params)) 176 177 plugintools.open_settings_dialog()
En el siguiente punto veremos cómo se implementa el buscador, y de paso cómo leer y escribir los valores de los parámetros de configuración desde el código de tu add-on.
Buscador
La función que XBMC ejecuta cuando seleccionas el buscador es muy sencilla, lo único que hace es leer el valor de «last_search» en la configuración para ver la última búsqueda que escribiste. Para ello utiliza la función get_setting.
Luego abre el teclado usando la función keyboard_input, almacena el resultado de nuevo en «last_search» usando la función set_setting.
179 # Open a popup dialog for input search terms, then call the result function 180 def search(params): 181 plugintools.log("disneyjunior.search "+repr(params)) 182 183 last_search = plugintools.get_setting("last_search") 184 texto = plugintools.keyboard_input(last_search) 185 plugintools.set_setting("last_search",texto) 186 187 params["texto"]=texto 188 189 youtube_search(params)
Una vez que tiene el texto tecleado lo almacena como un parámetro «texto» y llama a la función «youtube_search», que es similar a las anteriores ya que procesa un feed XML de YouTube con los resultados.
La única diferencia es que la llamada al API de YouTube es diferente. Usa el servicio «videos» de YouTube indicando en el parámetro «q» el texto que el usuario ha tecleado, y en el parámetro «lr» el idioma que ha puesto en la configuración.
191 # Show first 50 videos from YouTube that matches a search string 192 def youtube_search(params): 193 plugintools.log("disneyjunior.search "+repr(params)) 194 195 # Fetch video list from YouTube feed 196 data = plugintools.read( "https://gdata.youtube.com/feeds/api/videos?q="+params.get("texto").replace(" ","+")+"&orderby=published&start-index=1&max-results=50&v=2&lr="+plugintools.get_setting("youtube_language") ) 197 plugintools.log("data="+data) 198 199 # Extract items from feed 200 pattern = "" 201 matches = plugintools.find_multiple_matches(data,"<entry(.*?)</entry>") 202 203 for entry in matches: 204 plugintools.log("entry="+entry) 205 206 # Not the better way to parse XML, but clean and easy 207 title = plugintools.find_single_match(entry,"<titl[^>]+>([^<]+)</title>") 208 plot = plugintools.find_single_match(entry,"<summa[^>]+>([^<]+)</summa") 209 thumbnail = plugintools.find_single_match(entry,"<media\:thumbnail url='([^']+)'") 210 video_id = plugintools.find_single_match(entry,"http\://www.youtube.com/watch\?v\=([0-9A-Za-z_-]{11})") 211 if video_id=="": 212 video_id = plugintools.find_single_match(entry,"https\://www.youtube.com/watch\?v\=([0-9A-Za-z_-]{11})") 213 url = "plugin://plugin.video.youtube/?path=/root/video&action=play_video&videoid="+video_id 214 215 # Appends a new item to the xbmc item list 216 plugintools.add_item( action="play" , title=title , plot=plot , url=url , thumbnail=thumbnail , isPlayable=True, folder=False )
El resultado son los primeros 50 elementos que coinciden con el texto buscado, en el idioma elegido por el usuario. Y la acción de cada vídeo individual es la misma función «play» que en el caso anterior.
Mensajes al usuario
No he utilizado en este caso mensajes para el usuario, pero la librería Plugin Tools incluye una función message que te permite mostrar una alerta en caso necesario.
plugintools.message("Disney Junior","Ejemplo de mensaje")
Descarga el add-on de Disney Junior
Aunque estaba en la entrada de ayer, incluyo de nuevo el enlace al add-on completo para que puedas descargarlo y estudiarlo cómodamente. Es totalmente funcional, también puedes usarlo para ver algún vídeo si quieres 🙂
http://www.mimediacenter.info/descargas/plugin.video.disneyjunior-1.0.0.zip
Próxima entrega
Ya queda poco para llegar al final de esta serie de tutoriales sobre cómo programar add-ons para XBMC, espero que te estén gustando y que te animen a desarrollar tus propios add-ons 🙂
En la próxima entrega veremos algunas técnicas para leer datos de sitios web que no son tan sencillos, como sitios que requieran usuario y contraseña o que requieran cookies. Aprovecharé para ampliar la funcionalidad de Plugin Tools de nuevo para hacer que resulte sencillo.
Jesus
duda, con este ejemplo puedo comenzar a hacer add-ons para FRODO??? o solo funcionan en EDEN? cual es la diferencia??
Saludos
En realidad no hay ninguna diferencia desde el punto de vista de los add-ons, entre Frodo y Eden.
Bájate los add-ons de ejemplo y pruébalos en ambas versiones, verás como funcionan.
Excelente, ya lo probe y si funciona en ambos, solo para android no tengo que usar algunas librerías que no soporta y ya porte una version de tvolucion para probar…. Jesus, otra duda.. para que funcione en PLEX, ¿que cambios se tendrían que hacer??
muchas gracias por tu ayuda Jesus.
Plex es completamente distinto 🙁
Cuando acabe esta serie de tutoriales pensaba empezar otra para Plex.
Jesus, una duda.. estoy tratando de mejorar TVOLUCION, actualmente para ver un capitulo de una novela, seleccionas la NOVELA>CAPITULO>PLAY AL CAPITULO, esto porque asi esta en la pagina de tvolucion, pero queria hacer que desde que le picas al capitulo se diera el PLAY automatico, intente utilizar
plugintools.play_resolved_url( url)
Donde el URL es un string donde guardo la url del video (un mp4)..
Pero me regresa un error
ERROR: EndOfDirectory – called with an invalid handle.
¿Sabes a que se debe esto?
Saludos y muchas gracias por tu ayuda
Probablemente por cómo has indicado el atributo «Folder» del item.
Si pones un item con folder=True y luego al seleccionarlo no muestras más items, XBMC se queja. Y si pones un item con folder=False, pero la acción que ejecuta intenta añadir nuevos items, XBMC también se queja.
Asegúrate de que los items correspondientes a las series tienen «folder=True», son como carpetas que tienen dentro los vídeos. Y a su vez asegúrate de que los items de los episodios tienen «folder=False» y que lleven a una función «play» por ejemplo. Es importante también que marques el item del episodio como «isPlayable=True», de forma que XBMC se quede a la espera de que le des la URL buena.
Luego en la función «play» coges la URL de la página donde está episodio, la descargas, sacas la URL del video y se la das a play_resolved_url. Al ser un item que no es carpeta, y que ya le has dicho que es «playable», XBMC esperará que se pueda reproducir.
Tienes otra alternativa que es usar direct_play, que es el modo por defecto en pelisalacarta y tvalacarta. Te dará menos problemas, y no le afectan tanto los atributos «folder» e «isPlayable», aunque es más compatible play_resolved_url y por eso es la opción recomendada.
Buen dia Jesus
te nias razón, el problema estaba en que el Item lo tenia como FOLDER = true porque antes lo manejaba como folder y el playable lo hacia en la siguiente lista.. ya me funciona de maravilla…. muchas gracias.
Venga Jesús, no te rindas y saca un ratillo para terminar este maravillo manual sobre programación de addon para xbmc.
Muchas gracias por compartir tu conocimiento.
Gracias por los ánimos 🙂
Tengo las entradas en borrador, sólo necesito encontrar un rato para terminarlas.
Gracias y más gracias por los tutoriales. Perfectamente explicados y completos. ¡De 10!
Ahora como dice Alfonso, a ver si tienes un rato para terminar la serie. ¡Ánimo!
Me uno a las felicitaciones. Sencillamente genial este tutorial, lo que he aprendido en unas horas… uf! Necesito aprender más, esto es como una droga. Larga vida al XBMC y ánimo Jesús!!
Gracias 🙂
Hola amigo espero me ayudes,tengo un plugin editado x mi de livestream,y quisiera poner una eccion de nombre y pass,para que no me la roben,me podrias ayudar?
saludos maestro tengo una pregunta lo que necesito hacer es un addon que contenga una lista xml que ya tengo elaborada y bastante extensa la cual monte en la base de livestreampro y funciona perfecta pero mi idea es hacer un addon desde cero propio que contenga esta lista de la que le hable que debo hacer? me gustaría explicara esto saludos y gracias por los excelentes tutoriales¡¡¡
Buenas como puedo añadir last_search en mi addon como lo as explicado para you tube pero yo he creado mi addon y quisiera añadir last_search esto a mi addon