martes, 30 de julio de 2013

Muy rápido, muy fácil, reproductor de audio con Python


En la entrada anterior pudimos ver cómo con muy poco código fue posible obtener un simple pero poderoso navegador web con el lenguaje de programación Python, PySide (Los bindings de Qt, uno de los  frameworks gráficos del lenguaje C++) y poco más de 70 líneas de código. En esta entrega veremos cómo usando el diseñador de interfaces y algo de código podemos hacer un muy simple reproductor de audio, el cual será extendido en próximas entregas.
Primero que todo es necesario decir que Qt ofrece una herramienta para diseñar interfaces de usuario. Esta se llama QtDesigner y viene incluida en el instalador de la libreria PySide de windows (para los entornos linux hay que instalarlo a mano). Esta herramienta es muy útil, puesto que con ella podemos hacer la interfaz y luego exportarla al lenguaje que vayamos a usar para realizar la aplicación (en la actualidad Qt tiene bindings para varios lenguajes, entre ellos Java, Ruby, Python y Php, entre otros), además de proveer un sistema de enlace entre objetos y métodos mediante eventos (en Qt se habla de slots y signals), lo cual facilita en cierta manera la tarea del programador. Esta herramienta es una herramienta WYSIWYG, la cual nos permite arrastrar, soltar y acomodar hasta obtener la interfaz como se desea. A continuación la imagen de la interfaz gráfica del reproductor:


La interfaz gráfica contiene unos cuantos QLabel para mostrar información, un QLCDNumber para mostrar cuánto tiempo ha transcurrido, un par de botones y unos espaciadores para conservar las proporciones al cambiar el tamaño de la ventana. A pesar de que sería posible "compilar" la interfaz a el lenguaje nativo con herramientas como el pyside-uic no lo vamos a hacer.

Ahora vamos a ver el código Python:

#!/usr/bin/env python

from PySide import QtCore, QtGui, phonon, QtUiTools
from PySide.phonon import Phonon

class Reproductor(QtGui.QWidget):

    def __init__(self):
        super(Reproductor, self).__init__(parent=None)
        self.setWindowTitle("Python Simple Player")
        self.gridLayout = QtGui.QGridLayout(self)
        loader = QtUiTools.QUiLoader()
        file = QtCore.QFile("repro.ui")
        file.open(QtCore.QFile.ReadOnly)
        self.ui = loader.load(file, self)
        file.close()
        self.gridLayout.addWidget(self.ui, 0, 0, 1, 1)
        # seccion multimedia
        self.audioOutput = Phonon.AudioOutput(Phonon.MusicCategory, self)
        self.mediaObject = Phonon.MediaObject(self)
        Phonon.createPath(self.mediaObject, self.audioOutput)
        self.mediaObject.setTickInterval(1000)
        #
        self.ui.lcdAvance.display("00:00")
        QtCore.QObject.connect(
            self.ui.btnArchivo,
            QtCore.SIGNAL("clicked()"),
            self.openFile)
        QtCore.QObject.connect(
            self.ui.btnPlay,
            QtCore.SIGNAL("clicked()"),
            self.play)
        self.mediaObject.tick.connect(self.alAvanzar)
        self.mediaObject.stateChanged.connect(self.alCambiarEstado)

    def alAvanzar(self, tiempo):
        displayTime = QtCore.QTime(
            0, (tiempo / 60000) %
            60, (tiempo / 1000) %
            60)
        self.ui.lcdAvance.display(displayTime.toString('mm:ss'))

    def alCambiarEstado(self, new, old):
        if self.mediaObject.state() == Phonon.State.StoppedState:
            self.ui.btnPlay.setEnabled(True)
        elif self.mediaObject.state() == Phonon.State.PlayingState:
            self.ui.btnPlay.setText("||")  # .setEnabled(False)

    def play(self):
        if self.mediaObject.state() == Phonon.State.PlayingState:
            self.mediaObject.pause()
            self.ui.btnPlay.setText(">")
        else:
            self.mediaObject.play()
            #///////////////////////////////////
            meta = self.mediaObject.metaData()
            meta["TITLE"] = meta["TITLE"] if "TITLE" in meta else "Indefinido"
            meta["ARTIST"] = meta[
                "ARTIST"] if "ARTIST" in meta else "SinNombre"
            label = "{TITLE} by {ARTIST}".format(**meta)
            self.ui.lblNombre.setText(label)
            #///////////////////////////////////
            self.ui.btnPlay.setText("||")

    def openFile(self):
        fileName = QtGui.QFileDialog.getOpenFileName(
            self,
            u"Abrir Archivo",
            u"/",
            u"Archivos de Audio (*.mp3 *.wav *.ogg)")
        if fileName[1]:
            self.path = fileName[0]
            self.mediaObject.setCurrentSource(
                Phonon.MediaSource(self.path))
            self.ui.btnPlay.setText(">")
            self.ui.lblNombre.setText("...")
            self.ui.lcdAvance.display("00:00")

if __name__ == "__main__":
    import sys
    app = QtGui.QApplication(sys.argv)
    rep = Reproductor()
    rep.show()
    sys.exit(app.exec_())

En el código anterior podemos ver cómo mediante el uso del modulo QtUiTools es posible usar la clase QUiLoader para cargar en tiempo de ejecución el archivo de interfaz gráfica (repro.ui), y obtenemos un objeto QWidget, el cual contiene toda la jerarquía de objetos que habíamos hecho en el diseñador de interfaces, los cuales quedan listos para ser usados a gusto. Después de esto acoplamos el QWidget obtenido mediante un gridLayout. Seguidamente creamos las instancias de la salida de audio (audioOutput) y del reproductor (mediaObject), los cuales son enlazados mediante la instrucción:
Phonon.createPath(self.mediaObject, self.audioOutput)
Esta instrucción enlaza el reproductor con la salida de audio. Después de esto se establece cada cuánto se va a lanzar una señal de actualización (es para que pueda medirse el tiempo transcurrido y hacer cosas de acuerdo a este) mediante el tickInterval, el cual se establece cada 1000 milisegundos (un segundo) y se conectan objetos como botones a los eventos correspondientes para que antes de finalizar el constructor se enlacen las señales del reproductor a los métodos encargados de modificar el avance y cambiar la etiqueta del botón de reproducción.
Los métodos siguientes se encargan de cambiar el valor de las etiquetas, de iniciar o detener la reproducción o cambiar el título de la pista.

 El código correspondiente esta en GitHub para quienes estén interesados.

4 comentarios: