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.

15 comentarios

  1. Jesus
    duda, con este ejemplo puedo comenzar a hacer add-ons para FRODO??? o solo funcionan en EDEN? cual es la diferencia??

    Saludos

  2. 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.

  3. 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.

  4. Plex es completamente distinto 🙁

    Cuando acabe esta serie de tutoriales pensaba empezar otra para Plex.

  5. 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

  6. 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.

  7. 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.

  8. 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.

  9. Gracias por los ánimos 🙂

    Tengo las entradas en borrador, sólo necesito encontrar un rato para terminarlas.

  10. 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!

  11. 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!!

  12. Gracias 🙂

  13. 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?

  14. 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¡¡¡

  15. 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

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *