13 feb. 2012

Python + GObject + Gstreamer + GTK3 (Parte 3)

Ahora si estamos por llegar a la parte que estaban esperando y es el trabajo con GTK3 y Gstreamer.

Estos dos a veces parecen buenos amigos pero no siempre se comportan como queremos. Sin más rodeos vamos a trabajar.

Lo primero es que para esta parte bajen un programa en python que mostré hace algunas entradas en este blog, a partir de ese programa es que vamos a construir los elementos necesarios para reproducir nuestros archivos desde GTK3.


El programa que descargaron fue uno que estoy usando para mejorar el codigo de Aleatorio. Se ve como ven en la imagen. En ese punto el programa solo es capaz de correr el botón aleatorio.




Cuando abren el programa en algún editor encontraran unos espacios donde hay unos comentarios que dicen, código de gstreamer y cosas de ese estilo.

Como ya explique en la entrada anterior el código de Gstreamer simplemente lo voy a dejar aquí y explicare lo nuevo.

 #Codigo de Gstreamer
 self.playbin = gst.element_factory_make("playbin2", "player")
         bus = self.playbin.get_bus()
         bus.add_signal_watch()
         bus.connect("message", self.on_message)


 #Codigo de mensaje de Gstreamer
     def on_message(self, bus, message):
         t = message.type
         if t == gst.MESSAGE_EOS:
             self.playbin.set_state(gst.STATE_NULL)
         elif t == gst.MESSAGE_ERROR:
             self.playbin.set_state(gst.STATE_NULL)
             err, debug = message.parse_error()
             print "Error: %s" % err, debug

Listo eso es todo lo que ya vimos en la entrada anterior y no hay necesidad de explicar. Si tienen dudas comentan en la entrada anterior y puedo explicar en detalle.

Ahora vamos a ver algo nuevo y es recuperar la lista, hasta el momento solo sabemos que podemos meter listas dentro de el GtkTreeview para que se vea como en la imagen mas arriba, pero que pasa si necesitamos esa lista para reproducirla.

Gstreamer, Python y GTK3 se llevan bien pero no tan bien para que el proceso sea automatico, es por ello que es necesario recuperar el estado de la lista para poder pasarla a Gstreamer.

Para ello vamos a crear una nueva definición que la podemos crear debajo de la funcion Aleatoria en el código.
     def lista(self):
         listaa = []
         for allfiles in self.liststore2:
             for media_files in allfiles:
                 listaa.append(media_files)
         return listaa 
  
Como ven lista es una función propia (self) cuyo único objetivo es buscar que hay en el TreeView y meterlo dentro de una variable que se llama listaa, al final esta funcion se retorna y ya cualquier función externa puede usarla.

Como ya se imaginan la siguiente función que usara esta información sera Play.

      #Reproducir
      def play(self, wid):
          print "Reproducir"
          le = self.lista()
          for a in le:
              self.playbin.set_property("uri", a)
              self.playbin.set_state(gst.STATE_PLAYING)
      
      #Detener
      def detener(self, widget):
          print "Detener"
          self.playbin.set_state(gst.STATE_NULL)

Como ven la información que retorno self.lista la guardamos dentro de la variable "le" y ahí quedo toda la musica que estaba dentro del TreeView de GTK3, lo siguiente es simplemente decirle a Gstreamer que reproduzca todo el contenido de "le".

Detener es parecido pero simplemente lo único que hace es detener cualquier acción del contenedor de Gstreamer.

Si le dan reproducir notaran que el programa comienza a reproducir cualquier canción que le pongan a reproducir. Pero ojo que no hemos terminado, hasta este punto del tutorial el programa solo es capaz de reproducir el primer elemento de la lista y el ultimo elemento de la misma, esto se debe a que no hemos hecho los ciclos de GObject ni de Python que les conté en la primera entrada.

Espero disfruten sus primeros pasos con Python + GObject + Gstreamer + GTK3 pueden seguir revisando cosas y seguramente van a descubrir como se hace para reproducir todos los archivos.

Pronto la ultima parte de estas entradas cortas con el código final de GObject y Python necesario para reproducir toda la lista musical.


11 feb. 2012

Python + GObject + Gstreamer + GTK3 (Parte 2)

Para poder trabajar con 4 lenguajes distintos es fundamental comenzar a entender el funcionamiento del que se encargara del trabajo pesado que en nuestro caso es Gstreamer, como ya les comente este depende de un ligero balance con GObject que se encarga de realizar el bucle sobre el que vive y corre Gstreamer.

Algunos podrán pensar en usar Pygst pero como no ha sido 100% portado a la nueva tecnología es mejor usar elementos conocidos que no cambian tanto en el tiempo al evolucionar las versiones del código.

Lo primero es entender el funcionamiento de Gstreamer. Para no complicarnos los módulos para reproducir contenido de Gstreamer son bastante amplios pero como esto es una primer aproximación realmente solo nos vamos a enfocar en Playbin que se encarga de reproducir cualquier cosa que le demos de comer.

Para quien desea leer un poco mas de la arquitectura y ejemplos pensados para pygtk y gtk2 es bueno ver esta pagina.

Volviendo al tema de playbin.
El código funciona de la siguiente forma.

Primero se crea un un elemento de Gstreamer que para efectos mas prácticos le podemos decir contenedor.

self.playbin = gst.element_factory_make("playbin2","player")

Después es necesario crear un elemento llamado bus que se encarga de escuchar cambios en el interior del contenedor.

bus = self.playbin.get_bus()

Como pueden ver usa get que es obtener en español. Lo que obtiene es información de lo que pasa dentro del contenedor que se llama playbin y creamos con las instrucción anterior.

Al bus es necesario agregarle un observador de señales ya que como sabemos al contenedor le llegaran y le saldrá información.

bus.add_signal_watch()


Este sistema entonces emite señales de lo que entra y sale del contenedor en todo momento pero tiene que almacenar esa información en alguna parte por lo que la siguiente linea se encarga de hacer el proceso de almacenamiento mas simple, al crear una función llamada self.mensajes donde guardaremos esa información importante.

bus.connect("messages", self.mensajes)


Vamos entonces a crear una función que es obligatoria como las anteriores para el funcionamiento de gstreamer. Y es el almacenador de los mensajes.

def self.mensajes(self, bus, messages):
     t = message.type
     if t == gst.MESSAGE_EOS:
           self.playbin.set_state(gst.STATE_NULL)

Esta primera parte de la definición en la función self.mensajes lo que hace es guardar en una variable t todos los mensajes que vengan del bus. Específicamente todos los mensajes que se producen cuando termina de reproducir algún contenido, el bus lee esos mensajes con una forma MESSAGE_EOS. Y lo siguiente que el sistema hace es decirle a Gstreamer que si no encuentra contenido pause (detenga) la reproducción, esto se hace con el comando set_state(gst.STATE_NULL).

     elif t == gst.MESSAGE_ERROR:
           self.playbin.set_state(gst.STATE_NULL)
           err, debug = message.parse_error()
           print "Error: %s" % err, debug

La segunda parte de la definición self.mensajes se encarga de decirle que hacer a gstreamer si lo que se intenta meter dentro del contener tiene errores o no se puede reproducir, en este caso primero pausa la reproducción y después crea un texto en consola donde se muestra la razón por lo que no se puede reproducir el contenido.

Con esto hemos terminado la segunda parte y ya sabemos como crear un playbin y que debe tener como obligatorio en Gstreamer para su correcto funcionamiento, en la próxima entrada comenzare a mostrar con ejemplos en GTK3 como trabajar con Gstreamer.

8 feb. 2012

Python + GObject + Gstreamer + GTK3 (Parte 1)

Esta entrada esta pensada para poder dar una primera respuesta a las personas que han preguntado acerca de la parte final del tutorial de Python y GTK3.

Como algunos quizás saben Gstreamer es muy amigo de GObject porque están pensados para funcionar como hermanos.
GObject es entonces una parte fundamental para Gstreamer ya que se encarga de crear un ciclo o bucle en el que gst habita y hace su trabajo.

Todo esto suena muy bonito pero tiene su grado alto de complejidad y es que como sabemos GTK3 también es un ciclo, entonces hay que saber quien vive dentro de quien y porque razón.

De primer mano podríamos pensar que el ciclo mas grande seria GObject y que GTK3 y Gstreamer deberían habitar dentro de este. Esta seria una solución ideal de no ser por el hecho de que estamos trabajando en Python que crea Threads que son para no confundirlos unos mini ciclos.

Al tener tantos ciclos puede pasar que no se sabe quien vive dentro de quien para que funcionen de forma adecuada todo.

Es por eso que hay que pensar muy bien incluso como invocar los modulos.

La respuesta la verdad no es tan compleja, el ciclo mayor debe ser GTK3 y dentro debe estar lo demás, se deben abrir y cerrar esos ciclos de la siguiente forma:

Abrir
GTK3
GObject
Gstreamer

Cerrar
Gstreamer
GObject
GTK3

Para verlo con un poco más de codigo seria algo asi:

from gi.repository import Gtk, GObject
import gst

loop = GObject.MainLoop()

#Cerrando los ciclos
if __name__ == "__main__":
        main()
    loop.quit()   
    Gtk.main()


En la próxima entrada mostrare una primera aplicación que usa este tipo de ciclos en un primer ejemplo simple de Gstreamer.

6 feb. 2012

Crear Elementos Aleatorios en GtkTreeview

Si han revisado el blog y están haciendo el Tutorial de Python y GTK3 se habrán dado cuenta que en GTK3 no existe order(index) para el Treeview. Esto permitía reordenar una lista, pero en GTK3 no existe y por lo tanto elimina la opción de crear una lista aleatoria con facilidad.

Más información.

Es por ello que para GTK3 hay que implementar un método distinto para poder solucionar este pequeño problema que deja el paso de GTK2 a GTK3.

El usuario Gustavo Angel me ha pedido amablemente buscar una solución y me he puesto a pensar de una forma relativamente eficiente de realizar esto sin comprometer demasiado el código tratando de que el nuevo código sea compatible para GTK2 y GTK3


Código ANTIGUO únicamente valido para GTK2
     #Si se activa Aleatorio
         if var == 0:
             n = 0
             for musicfiles in self.medialist:
                 n += 1
             index = range(0, n)
             shuffle(index)
     #Reorganiza la lista de musica llamada medialist
             if len(index) > 1:
                 self.medialist.reorder(index)

Código NUEVO valido para GTK2 y GKT3
    #Nuevo sistema de aleatorio
    def aleatorio(self, widget, data=None):
        lista = []
        for allfiles in self.liststore1:
            for media_files in allfiles:
                lista.append(media_files)
        random.shuffle(lista)
        self.liststore1.clear()
        for f in lista:
            self.liststore1.append([f])

Este código nuevo no esta aplicado al programa del tutorial es solo un ejemplo que he creado para un programa sencillo que importa archivos de audio, crea una lista Treeview y después se encarga de volver la lista aleatoria.

Dejo el link de descarga del programa para que lo miren y lo prueben y si alguien tiene una mejor idea de como crear el Aleatorio nos deje el comentario.



Pronto actualizare el tutorial en PDF con el nuevo código aleatorio para gtk2 y gtk3

Actualización 12 de Febrero 2012: Al parecer este método solo es útil cuando se tiene una sola columna en el Gtk.liststore por lo que aunque es una forma valida de realizar el proceso es incompleta cuando se tienen mas columnas, seguiré buscando una solución mas completa para solucionar este problema.

Actualización 13 de Febrero 2012: Después de dedicar casi todo mi domingo a romperme la cabeza para solucionar este pequeño dolor de cabeza que son los Treeviews he encontrado una nueva solución mas general de la que comento en esta entrada. Esta nueva solución esta lejos de ser perfecta y para mi es mas bien un Hack que soluciona el problema pero hace que el código se vea algo maluco.

Dejo sin embargo la información en miras de si alguien encuentra una solución mas completa y bonita la pueda colocar en los comentarios y así actualizare esta entrada.

Este nuevo código asume que son dos listas o dos columnas con las que trabajamos por lo tanto asume también que ya no se trabaja con un solo gtk.liststore pero con dos para el caso de dos columnas. Si fueran 3 columnas entonces tocaría agregar una lista3 y un self.liststore3.

    def aleatorio(self, widget, data=None):
        lista1 = []
        lista2 = []
        for files in self.liststore1:
            for a in files:
                lista1.append(a)
        for uri in self.liststore2:
            for b in uri:
                lista2.append(b)
        listas = zip(lista1,lista2)
        random.shuffle(listas)
        u = [x[0] for x in listas]
        v = [x[1] for x in listas]
        self.liststore1.clear()
        self.liststore2.clear()
        for f in u:
            self.liststore1.append([f])   
        for f in v:
            self.liststore2.append([f])

Si encuentro una nueva solución que evite el uso continuo de tantos for y el zip, actualizare esta entrada. Por ahora se me ocurren opciones pero solo podre revisar hasta el sábado que tenga suficiente tiempo.