domingo, 21 de febrero de 2010

Prototipados en Python - Fácil y sencillo

Vamos a crear prototipo de un sistema de reconocimiento de texto por voz en Python, y multiplataforma, que funcione tanto en Windows como en Linux.

Este programa es un prototipo de una aplicación que debería ser mucho más sofisticada, sobretodo en la reproducción del texto. Es importante, ya que aquí se está escribiendo una aplicación rápida, un prototipado. El resultado final no es de una calidad profesional como el software comercial que hay actualmente en el mercado (evidentemente). Una de las cosas fuertes que tiene Python es el prototipado de aplicaciones.

El propósito de crear esta aplicación es ver, primero, la realidad de la portabilidad de Python entre varias plataformas (en este caso Linux y Windows). En segundo lugar, la reproducción de sonidos en nuestros programas escritos en Python y en tercer lugar, el manejo de uno de los tipos de datos más importantes de Python, el diccionario.

Este proyecto lo voy a realizar con Python 2.6, en una máquina Linux Ubuntu 9.10. ¿Por qué? Pues por la sencilla razón que el 17 de Febrero de 2010, a las 9:45 de la mañana, mi flamante Windows XP SP3 dijo que no arrancaba. Estuve todo el día para poder obtener la información del disco, y al final lo conseguí. Puesto que tengo un disco de 100 Gb, he creado 6 particiones: 1 Windows 2000, 1 Windows XP, 1 Linux (y por ende 1 swap), y 2 particiones, en FAT32, para intercambiar información entre varios sistemas (Linux lee NTFS, pero Windows no lee ext4, ¡cosas de la vida!).
Este proyecto tenía en mente hacerlo con EDITRA, pero tengo un problema, no me deja ejecutarlo (en Ubuntu 9.10). Mientras que no consiga arreglar el problema voy a empezar con el proyecto en NetBeans, ya que tiene un plugin para Python.


Bien, lo primero de todo hay que ver qué es lo que queremos, para de esta manera segmentar el trabajo. A ver, todo lo voy a hacer yo, ya que es un prototipo (en donde el trabajo se realiza rápidamente), pero si no lo fuera, habría un equipo de trabajo en donde se planificaría el mismo: menos tiempo programando, ¡y no se está tan solo!.

OBJETIVO: Crear una aplicación que tome como entrada un fichero de texto plano en español. La aplicación irá reconociendo el texto, y reproducirá en el altavoz de la máquina el sonido de las palabras que encuentre. La relación entre las palabras y su sonido lo tendremos que generar nosotros previamente. Es decir, hay que grabar todas las palabras que queramos que se reproduzcan, en un grabador, para poder asociarlas a las palabras del texto.

Si fuéramos un equipo de trabajo habría que diferenciar 3 aspectos diferentes. Primero, la creación de estructuras y acceso al sistema de ficheros. Segundo, la creación de ficheros de sonido de palabras en español y su reproducción. Y tercero, las pruebas y ajustes de alguien que en principio no estuviera implicado en los dos anteriores pasos, para poder ser objetivo (un lingüista). Hay que tener en cuenta que esto debería estar supervisado por alguien que conociera perfectamente el objetivo final del mismo proyecto, para poder coordinar esfuerzos, motivación del grupo y rectificaciones del trabajo in situ.

Los puntos del desarrollo son los siguientes:
  • Sistema para poder obtener un fichero de texto y cargarlo en algún tipo de estructura.
  • Sistema para poder relacionar una palabra con un sonido.
  • Grabación de palabras y su reproducción.
  • Sistema de reproducción de las palabras que están en la estructura de datos que hemos creado.
  • Pruebas y ajustes.
  • Paso a producción.
PASO 1: Grabación de palabras.

Como antes hemos comentado, relacionamos cada palabra con un fichero de sonido, que tendremos que grabar. Una aplicación en Ubuntu para grabar ficheros de sonido OGG es el grabador de sonidos de Gnome, el gnome-sound-recorder. El fastidio aquí­ es que hay que grabar por cada palabra que queramos reproducir. Hay que tener en cuenta que esto es un proyecto muy básico (un prototipo) para ver la potencia de Python en desarrollar aplicaciones rápidamente. Podríamos hacerlo mucho más profesional, complicando el tema, pero no es el caso.


Como vemos en el screenshot (captura de pantalla) de arriba, vamos grabando palabras, en formato OGG. ¿Cómo creamos una relación de estas grabaciones? Creamos a continuación un fichero, llamado aquí lista_ficheros.ini, que contendrá por cada línea una palabra, un espacio en blanco, y el nombre del fichero OGG que hemos grabado con el sonido acorde a la palabra que le acompaña. En el screenshot de arriba se puede ver las palabras que he grabado, y la relación en el fichero.

PASO 2: Reproducción en Python.

¿Cómo vamos a reproducir un sonido en Python? Mirando por algunos blogs de geeks en Python me he decicido, para la plataforma Linux, pygame, el módulo para programar juegos de Python. Hay otras opciones que he estado probando, como el framework gstreamer ó pysonic, pero el más sencillo me ha parecido este. Decir que pygame no viene de serie en Python, por lo que hay que descargarlo, tanto en plataformas Linux como Windows (y también en Mac, pero no hablaré de ella). Para Linux, si estáis trabajando en Ubuntu, podéis elegir dos maneras para bajarlo de Internet, a saber, utilizando el gestor de paquetes de Synaptic ó bien hacerlo a la manera tradicional, esto es, "apt-get install python-pygame". Más información de este módulo en http://www.pygame.org/.

Un ejemplo de cómo funciona el módulo pygame es lo que vamos a ver a continuación. Lo único que hace este código es reproducir un sonido, y mientras que se está reproduciendo estará en un estado de bloqueo (get_busy() devuelve True mientras que se está reproduciendo algo).


import pygame
import os

pygame.mixer.init()
pygame.mixer.music.load(os.path.realpath("prueba.ogg"))
pygame.mixer.music.play()
while pygame.mixer.music.get_busy():
pass
print "Terminé con el sonido"

En este código importamos módulos necesarios, cargamos fichero de sonido, reproducimos y creamos un bucle que se estará ejecutando mientras que haya sonido reproduciéndose. Fácil y sencillo.

PASO 3: Módulo que implementa el tratamiento de sonido y la relación entre palabras y sus sonidos correspondientes.

He creado el módulo sonido.py, en donde creo un clase diccionario_palabras (con ciertos métodos claro!). Esta clase lo único que hacer es acceder a un fichero que tendrá una correspondencia entre una palabra y un fichero de sonido, y mediante sus métodos se puede manipular la información suministrada.

Esta relación la guardamos en una estructura diccionario, ya que en realidad es una tabla hashing (llave, dato asociado). Evidentemente la llave (palabra) no puede repetirse, aunque si el nombre del fichero. El código de sonido.py es el siguiente:

# -*- coding: cp1252 -*-

import os
import pygame

class diccionario_palabras(object):
def __init__(self,lista_fichero):
'''
Constructor. Se le pasa como parámetro el fichero que contendrá
la lista de correspondencia entre palabras y fichero de sonido .ogg.
El fichero lista tiene que tener la siguiente estructura:
palabra1 fichero_sonido_palabra1.ogg
palabra2 fichero_sonido_palabra2.ogg
...
palabraN fichero_sonido_palabraN.ogg
'''
self.__lista_palabras = []
self.diccionario = {}
try:
# Abrimos fichero de asociación de palabras.
fichero = open(lista_fichero,'r')
# Cargamos cada línea de fichero como una tupla (palabra, fichero wav)
while True:
linea = fichero.readline()
if not linea:
break
# Separamos el nombre de la palabra del fichero de sonido asociado a la palabra.
linea = linea.split()
# Insertamos en la lista de palabras la lista [palabra, nombre de fichero]
self.__lista_palabras.append(linea)
# Creamos un diccionario de palabras con su sonido asociado.
self.diccionario = dict(self.__lista_palabras)
# Cerramos el fichero.
fichero.close()
# Info.
except:
print "Ha habido un error a la hora de crear el diccionario!"

def reproducir_palabra(self, fichero):
'''
Reproduce el sonido del fichero pasado como parámetro.
'''
try:
# Iniciamos pygame.
pygame.mixer.init()
# Cargamos fichero con el sonido.
pygame.mixer.music.load(fichero)
# Reproducimos el sonido.
pygame.mixer.music.play()
# Esperamos a que termine.
while pygame.mixer.music.get_busy():
pass
except:
print "No puedo reproducir el fichero ", fichero

def obtener_sonido_palabra(self,palabra):
'''
Devuelve el nombre del fichero asociado a la palabra.
'''
ret = self.diccionario[palabra]
return ret

def palabra(self,palabra):
fichero = self.obtener_sonido_palabra(palabra)
self.reproducir_palabra(os.path.realpath(fichero))

def listar_diccionario(self):
print "El diccionario es :", self.diccionario

Como puede observarse, la clase es muy sencilla, y los métodos obvios. En el constructor obtenemos los datos que necesitamos, incluyéndolos en un diccionario y mediante el método reproducir_palabra reproducimos el fichero que se especifica.

PASO 4: Carga del fichero que contiene el texto plano en español en una estructura, para tratarlo.

Esto puede hacerse de manera sencilla mediante una función que recoga todas las líneas del fichero y las incluya en una lista de listas, donde cada lista sea una línea del fichero. Si lo hago de esta manera es para el tratamiento de la información, ya que mediante listas en Python es casi trivial, como veremos a continuación. La función, denominada cargar_fichero, es la siguiente:

def cargar_fichero():
'''
Carga un fichero en una estructura de datos. Devuelve una lista de listas,
donde cada lista es una línea

'''
nom_fich = raw_input("Introduce nombre del fichero de texto ==> ")
try:
# Creamos una lista de listas, esto es, una lista que contendrá
# listas (donde cada lista en una línea del fichero).
lineas = []
# Abrimos fichero.
fichero = open(os.path.realpath(nom_fich),'r')
# Tratamos cada línea del fichero.
while True:
linea = fichero.readline()
if not linea:
break
# Separamos las palabras en la línea.
linea = linea.split()
# Vamos añadiendo líneas a la lista.
lineas.append(linea)
# Cerramos fichero.
fichero.close()
except:
print '''

¡¡¡¡ NO HE PODIDO ABRIR EL FICHERO !!!!

'''
nom_fich = ""
continuar()
# Devolvemos la lista con las filas del fichero.
return lineas, nom_fich

La función está lo suficientemente comentada como para ver lo que hace. Esto también es bastante fácil. Como se podrá observar, por el momento no hemos hecho algo que sea de días de trabajo. Para prototipados, Python, sin lugar a dudas.

NOTA IMPORTANTE: Esta función, y otras que necesito, las voy a incluir en un fihcero aparte, para ser ordenados (esto es muy importante) y poder reutilizar código en un futuro. El fichero se llama recursos_voz.py, y el código viene a continuación (incluyo la función de carga del fichero de texto de arriba):

# -*- coding: cp1252 -*-

import os

def menu(fichero):
# Limpiamos pantalla.
if os.name == "posix":
os.system("clear")
if os.name == "nt":
os.system("cls")

print '''

***********************************
APLICACIÓN DE REPRODUCCIÓN DE TEXTO
***********************************
'''
if len(fichero)!=0:
print "\t"
print "\t","\t","Fichero ", fichero, " cargado!"

print '''

Elige una de las siguientes opciones:

1) Cargar fichero de texto.
2) Reproducir fichero.
3) Ayuda.
0) Salir.

Elige opción:
'''
opcion = raw_input(" ")
return opcion

def continuar():
raw_input("Pulsa cualquier tecla para continuar")

def ayuda():
print '''

Esta es una aplicación que lee, a partir de un fichero de texto
plano, el contenido del mismo, con ayuda de un diccionario de palabras,
que tiene una correspondencia entre palabras y sus sonidos correspondientes.

* La opción 1 sirve para cargar en la aplicación el fichero de texto que
se quiere analizar.

* La opción 2 sirve para analizar el fichero introducido en la opción 1,
reproduciendo las palabras que hay en dicho archivo. Si no encuentra la
palabra, simplemente no la reproduce. Si quieres añadir más palabras solo
tienes que editar el fichero diccionario adjunto a la aplicación, cuyo
formato es: palabra fichero_sonido_palabra.ogg

Graba una palabra con tu grabador favorito en formaro OGG, asóciala a la
palabra que quieras y listo!!!

* La opción 3 es esta ayuda.

* Para salir pulsa 0.

'''
continuar()

def cargar_fichero():
'''
Carga un fichero en una estructura de datos. Devuelve una lista de listas,
donde cada lista es una línea

'''
nom_fich = raw_input("Introduce nombre del fichero de texto ==> ")
try:
# Creamos una lista de listas, esto es, una lista que contendrá
# listas (donde cada lista en una línea del fichero).
lineas = []
# Abrimos fichero.
fichero = open(os.path.realpath(nom_fich),'r')
# Tratamos cada línea del fichero.
while True:
linea = fichero.readline()
if not linea:
break
# Separamos el nombre de la palabra del fichero de sonido asociado a la palabra.
linea = linea.split()
# vamos añadiendo líneas a la lista.
lineas.append(linea)
# Cerramos fichero.
fichero.close()
except:
print '''

¡¡¡¡ NO HE PODIDO ABRIR EL FICHERO !!!!

'''
nom_fich = ""
continuar()
# Devolvemos la lista con las filas del fichero.
return lineas, nom_fich

Este es un fichero de recursos, como bien indica su nombre. Podemos observar que nuestra aplicación tiene una interfaz en consola de comandos (ver función menu()). Podría ser interesante crear una interfaz en wxPython, pero por rapidez no lo he hecho. De todas formas, es muy importante que cuando se haga un proyecto de este tipo, sea lo más independiente de una plataforma gráfica (siempre que se pueda), ya que de esa manera se puede implementar de varias formas, a saber, Tkinter ó Qt, por poner ejemplos, como interfaces gráficas.

Otra cosa a indicar es que la portabilidad de la aplicación nos la aseguramos en la función menu(). Fijarse en que según la plataforma en la que nos encontremos, el comando de limpiar la pantalla cambia. Si queremos que funcione en Windows y Linux hay que fijarse en estos detalles.


   # Limpiamos pantalla.
if os.name == "posix":
os.system("clear")
if os.name == "nt":
os.system("cls")


PASO 5: Diseño de introducción de datos.

Por último hay que crear el módulo que se ejecutará. Lo llamamos voz.py, y en él se hace referencia a los módulos que hemos creado previamente, sonido.py y recursos_voz.py. El funcionamiento es muy simple. Aparece un menú, con opciones. La primera opción sirve para cargar un fichero de texto, la segunda para analizarlo. Una opción de ayuda y otra para salir de la aplicación. El código de voz.py se muestra a continuación:

# -*- coding: cp1252 -*-

import os.path
from sonido import diccionario_palabras
from recursos_voz import *

# Cargamos diccionario de correspondencia entre palabras y sonidos.
nombre_fichero = os.path.realpath('lista_ficheros.ini')
diccionario = diccionario_palabras(nombre_fichero)

opcion = ""
cabecera = ""
while opcion != "0":
opcion = menu(cabecera)
if opcion == "1":
# Indicamos el fichero de texto, para cargarlo en una estructura.
lineas = []
lineas, cabecera = cargar_fichero()

if opcion == "2":
# Procesamos el fichero cargado.
try:
for linea in lineas:
for palabra in linea:
try:
diccionario.palabra(palabra)
except:
print "Palabra ", palabra, " no está en diccionario!"
continuar()
except:
print "Pimero tienes que cargar un fichero!!!"
continuar()
if opcion == "3":
# Mostramos ayuda.
ayuda()

print "Hasta pronto!"

En este módulo hay que dar mención especial a la potencialidad de las listas, en donde podemos ver como tratar las palabras de las líneas del fichero. Véase a continuación:

try:
for linea in lineas:
for palabra in linea:
try:
diccionario.palabra(palabra)
except:
print "Palabra ", palabra, " no está en diccionario!"
continuar()
except:
print "Pimero tienes que cargar un fichero!!!"
continuar()

Sencillamente vamos tratando en cada línea (del total), el conjunto de palabras ue lo forman. Se puede observar que la sintaxis es muy sencilla de leer y entender. Otro punto a favor de Python!!!

PASO 6: Crear ejemplos para realizar pruebas.

Crear cualquier fichero de texto plano, e incluir una frase ó frases que contengan palabras que hemos grabado, eveidentemente, para hacer pruebas.

PASO 7: Paso a producción.

Ejecutamos la aplicación, mediante:

python voz.py

Una captura de cómo tiene que quedar el programa sería esta:

Os aseguro que funciona tanto en Windows como en Linux, ya que lo he probado en las dos plataformas.

Espero os haya gustado, lo fácil y sencillo que es programar prototipos en Python.

4 comentarios:

  1. Bufff, estoy liado con el proyecto de Fin de Carrera de Ingenieria informática y haciendo una aplicacion para psicologos ando haciendo.. En python, para usar Skype4Py en videoconferencias y demas..

    La verdad es que Python se entiende facil, pero al principio (es decir, ahora) me estoy liando mucho mucho para entender dicha filosofia de python..

    Un saludo y muchisimas gracias por tus aportes!!

    ResponderEliminar
  2. Hola Antonio! Pues muchas gracias a tí por interesarte en lo que escribo. Espero poder ayudarte en todo lo que pueda y más.
    Saludos.

    ResponderEliminar
  3. hola existen librerias para el reconocimiento de voz en phyton

    ResponderEliminar
  4. No se puede descargar el código fuente?

    ResponderEliminar