domingo, 13 de octubre de 2019

EVITAR FATIGA AL USAR TU PC (aplicación).




En esta era tecnológica, estar frente a la pantalla de una computadora por un periodo prolongado de tiempo es en ocaciones inevitable, ya sea que seas un programador, ingeniero o estudiante.

La fatiga visual trae consigo una serie de síntomas desagradables producidos tanto por el uso desmedido de nuestros ojos, así como por la sequedad ocular producida por dejar de parpadear inconscientemente. Dichos síntomas suelen ser:
  • Ojos llorosos.
  • Ojos rojos.
  • Picazón.
  • Dolor de ojos.
  • Visión borrosa.
  • Dolores de cabeza.
  • Sensibilidad a la luz.
Por otra parte, es habitual que no prestemos mucha atención a nuestra postura. Una mala postura puede ocasionar:
  • Dolor de cuello o cervicalgia
  • Dolor de espalda.
  • Dolor de cabeza.
Existe algo que se llama la regla 20 20 20, que consiste en apartar la vista de la pantalla cada 20 minutos, unos 20 segundos, y voltear hacia algún objeto que este aproximadamente a 20 pies (6 metros) de distancia (algunos optan por cubrir sus ojos de la luz con las palmas de las manos). Pero en la realidad, cuando estas concentrado estudiando, en tu trabajo o pensando muchas otras cosas cosas, es difícil  acordarse de esta regla, por lo que no la aplicas y resulta lo mismo que no conocerla.
Para tener referencia de una buena postura. Podemos basarnos en la imagen de abajo. (tomada del Instituto Mexicano del Seguro Social) .


APLICACIÓN: RECORDATORIO


Es por estas razones que desarrollé una pequeña aplicación en python, que muestra un mensaje en pantalla y reproduce un sonido cada 20 minutos para recordarte que debes descansar tus ojos y cuidar tu postura. Actualizacion: este programa ahora dice la hora cada 30 minutos (sincronizado con el reloj de tu computadora). Es algo muy util para tener noción del tiempo.

La aplicación en Windows 7 se vé como en las imágenes de abajo. En Linux con LXDE o XFCE tambien funciona correctamente, solamente con GNOME no muestra el icono de la barra (gracias GNOME, por quitar esa funcion tan util),  para eso debes instalar una extensión que muestra los iconos tipo "legacy". La extensión que yo uso es esta https://extensions.gnome.org/extension/1031/topicons/.


Icono de barra de tareas.
Notificación de escritorio.

APLICACIÓN EJECUTABLE

Aqui te dejo el ejecutable de 32bit que generé para windows usando pyinstaller. Todo esta contenido en un solo archivo ejecutable. Por lo que solo tienes que correrlo. Si quieres el código fuente de la aplicación, lo dejo mas abajo.
 


CÓDIGO FUENTE

Necesitas tener python 3 (el programa fué probado con python 3.6 y 3.7).También necesitas las librerías conocidas como: wxpython y playsound. Lo mas fácil es usar "pip" para instalarlas. 

El icono puede ser cualquier imagen pequeña (tamaño icono) con formato png. El sonido puede ser cualquier archivo de sonido en formato wav, de preferencia uno con duración pequeña (algunos segundos).

La imagen debe llamarse "appicon.png", y el sonido "sonido.wav". Podrías guardar el script de python con extension ".pyw" si lo usas en windows, para que no muestre la línea de comandos al correr. Los 3 archivos: script, icono y sonidos deben de estar en el mismo folder.

Como siempre, dejo algunos comentarios en el código fuente para ayudarte a entender que esta pasando.

Modifique este programa para que tambien diga la hora cada 30 minutos. Los archivos de sonido pre-grabados estan incluidos en el zip, estos fueron generados en:  https://ttsmp3.com/ y editados con audacity un excelente editor de sonido gratuito  https://www.audacityteam.org/
Los iconos fueron obtenidos de la pagina https://www.fatcow.com/free-icons, un pack que contien cientos de iconos en formato .ico y png tipo royal free, el cual puedes descargar.

ANUNCIO: si quieres encontrar mas software gratuito visita mi otro post:

Sin mas preambulo. aqui esta todo el proyecto en un arzhivo comprimido zip.

Para convertir este programa a ejecutable nesecitas pyinstaller. Desarrollé una interfase gráfica para pyintaller, de ese modo se puede  usar de una forma mas sencilla. El enlace esta abajo.




lunes, 1 de julio de 2019

GUARDAR BUSQUEDA DE ARCHIVOS COMO TEXTO

Este es un programita (escrito en python) buscador de archivos que guarda el resultado de la búsqueda en un archivo de texto (.txt). Este programa surgió debido a que un día tube la necesidad de averiguar cuantos y cuales archivos con extención ".upf" existían en una maquina de SMT. Puede serle útil a alguna otra persona. Creo que el programa se expica por si mismo.
 
Lo modifiqué un poco para que pudieses buscar mas nombres o extenciones, separando cada uno por una coma. Al terminar la búsqueda, muestra en la barra de estado cuantos archivos encontró.


El código fuente fué probado con python 3.7 (Requiere wxpython).

import wx
from   os import walk
from   os import getcwd

class MyFrame(wx.Frame):
    def __init__(self, parent, title ="Listador de archivos"):
        super().__init__(parent, title = title , size = (600,240))
        self.SetMinSize(size = (600,240)) #Set minimum size to current
        self.locale   = wx.Locale(wx.LANGUAGE_ENGLISH) # Fix locale unknown error
        self.Dir    = getcwd() #directorio por defecto
        self.DirSve = getcwd()+"\DefaultName.txt" #directorio por defecto
        #--------------------------------------------------
        #                   Widgets
        #---------------------------------------------------
        self.CreateStatusBar()
        self.SetStatusText("")

        panel = wx.Panel(self, wx.ID_ANY)

        BtnDirSrch = wx.Button(panel, label="Dir. Busqueda")
        self.TxtDirSrch = wx.TextCtrl( panel, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, 0 )
        self.TxtDirSrch.SetEditable(False)
        self.TxtDirSrch.SetValue(self.Dir)

        BtnDirRes = wx.Button(panel, label="Dir. Guardar")
        self.TxtDirRes = wx.TextCtrl( panel, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, 0 )
        self.TxtDirRes.SetEditable(False)
        self.TxtDirRes.SetValue(self.DirSve)
        
        self.TxtFleToSrch = wx.TextCtrl( panel, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, 0 )
        self.TxtFleToSrch.SetEditable(True)
        LblNombre = wx.StaticText(panel)
        LblNombre.SetLabel("extencion o nombre") 
        
        BtnProcesar = wx.Button(panel, label="Buscar y crear lista (txt)")

        #--------------------------------------------------
        #                   BINDS
        #--------------------------------------------------
        BtnProcesar.Bind(wx.EVT_BUTTON, self.onSearch )
        BtnDirSrch .Bind(wx.EVT_BUTTON, self.onDirSrch)
        BtnDirRes  .Bind(wx.EVT_BUTTON, self.onDirSve )
        #--------------------------------------------------
        #                   LAYOUT
        #--------------------------------------------------
        MainSizer = wx.BoxSizer(wx.VERTICAL)

        sizrGrid = wx.FlexGridSizer(rows=3, cols=2, hgap=5, vgap=5)
        sizrGrid.AddGrowableCol(1, 1)

        sizrGrid.Add(BtnDirSrch, 0, wx.CENTER)
        sizrGrid.Add(self.TxtDirSrch,   1, wx.CENTER|wx.EXPAND)
        sizrGrid.Add(BtnDirRes,  0, wx.CENTER)
        sizrGrid.Add(self.TxtDirRes,    1, wx.ALL|wx.CENTER|wx.EXPAND)
        sizrGrid.Add(LblNombre,  0, wx.CENTER)
        sizrGrid.Add(self.TxtFleToSrch, 0, wx.CENTER|wx.EXPAND)
        
        MainSizer.Add(sizrGrid,          0,wx.ALL|wx.EXPAND, 5)
        MainSizer.Add(BtnProcesar,       1,wx.ALL|wx.EXPAND,5)
        panel.SetSizer(MainSizer)

    def cleanText(self):
        #-------------------------------------------
        #     Palabras no necesarias
        #-------------------------------------------
        quitar = ['',  #en blanco 
                  'de',
                  'del',
                  'el',
                  'la',
                  'los',
                  'las',
                  'y',
                  'en',
                  'con',
                  'para',
                  'por',
                  'a',
                  'desde',
                  'hacia',
                  'excepto',
                  'entre',
                  'sin',
                  'sobre',
                  'tras',
                  'contra',
                  'hasta',
                  'durante',
                  'mediante',
                  'también',
                  'tambien']
        
        FindValue = self.TxtFleToSrch.GetValue()  # obtener texto
        #----------------------------------------------
        #               Tokenizar
        #----------------------------------------------
        FindValue = FindValue.replace(" ",",")
        words = FindValue.split(',')
        for x in quitar:
                while x in words: # quitar de la lista palaras inesesarias
                    words.remove(x)
        return words
        
    def onSearch (self, event):
        DirValuesFound = []
        List = self.cleanText()
        
        GetBusy = wx.BusyInfo("Procesandio, por favor espere", self) #Mostrar Leyenda (ocupado)
        for root, dirs, files in walk(self.Dir):
            for file in files:
                for value in List:
                    if str(file).find(value) != -1:
                        #----- Revisar radio buttons ------
                        DirValuesFound.append(root+'/'+str(file)) # Direccion completa
            
        file = open(self.DirSve,'w')
        for x in DirValuesFound:
            file.write(x+"\n")
        file.close()
        self.SetStatusText("Se encontraron " + str(len(DirValuesFound))+ " archivos.")
        del GetBusy # quitar leyenda (ocupado)
        

    def onDirSrch(self, event):
        dlg = wx.DirDialog(self, "Elegir directorio de busqueda:",
                           style=wx.DD_DEFAULT_STYLE
                           #| wx.DD_DIR_MUST_EXIST
                           #| wx.DD_CHANGE_DIR
                           )
        if dlg.ShowModal() == wx.ID_OK:
            self.Dir = dlg.GetPath()
            self.TxtDirSrch.SetValue(self.Dir)
        dlg.Destroy()

    def onDirSve(self, event):
        wildcard = "Text Files (*.txt)|*.txt"
        dlg = wx.FileDialog(
            self, message="Elegir dereccion y numbre", 
            defaultDir=self.DirSve, 
            defaultFile="", wildcard=wildcard, style=wx.FD_SAVE
            )
        if dlg.ShowModal() == wx.ID_OK:
            self.DirSve = dlg.GetPath()
            self.TxtDirRes.SetValue(self.DirSve)
        dlg.Destroy()
               
app = wx.App(0)
frame = MyFrame(None)
app.SetTopWindow(frame)
frame.Show()
app.MainLoop()

sábado, 1 de junio de 2019

SINTETIZADOR DE VOZ EN ESPAÑOL PARA PYTHON 3 (OFFLINE)





En este post describiré cómo instalar pyttsx3 con espeak  y pondré unos programas ejemplo, donde el primero es un ejemplo del uso básico, y el segundo es una pequeña aplicación que dice la hora o alguna frace pequeña cada vez que se presiona un botón.

pytssx3 es una librería de interfase entre python 3 y los motores de texto a voz tales como SAPI5 (windows), espeak (Linux o windows) y NSSS (Mac). Ahunque existen otras librerías de conversión de texto a voz como gTTS (Google Text to Speech) con la cual se obtiene una voz mucho más realista, solo se pueden realizar 50 sintetizaciones de voz por día (no good) y requiere de una conexión permanente a internet (no good). Otra diferencia es que gTTS solo puede generar archivos de sonido que uno debe reproducir para escuchar, mientras que en pytssx3 se puede generar la señal audible (hacer hablar) y opcionalmente guardar como archivo de sonido.

Se recomienda usar la versión de python mas reciente. A continuación pondré los pasos a seguir para instalar lo necesario.

1. Instalar espeak.

En FEDORA, correr como root (super usuario) el siguiente comando en una terminal:

      dnf install espeak

con esto se instalan espeak y todas las voces con sus variantes de las voces disponibles.

En UBUNTU o RASPBIAN (Raspberry pi 3) correr como root (super usuario) el siguiente comando en una terminal:
     
      sudo apt install espeak

con esto se instalan espeak y todas las voces con sus variantes de las voces disponibles..

En Windows se debe instalar con el archivo ejecutable, lo puede descargar de la siguiente dirección:

 http://espeak.sourceforge.net/download.html

En el instalador se requiere especificar que voces se quieren instalar. Por ejemplo si quiero instalar el idioma español latino agrego spanish-latin-am. Además existen varios modificadores de voz, como por ejemplo +f5 (variante femenia de en este idioma). Si quisiera instalar una la voz con esa variante,  podría poner spanish-latin-am+f5 en el instalador, como se muestra en la imagen de abajo.

Tambien esta el español de españa que es spanish. Los modificadores aplicables a cualquier voz son los siguientes:
corak, f1, f2, f3, f4, f5, m1, m2, m3, m4, m6, m7, whisper, whisperf, klatt, klatt2, klatt3, klatt4.



Ejemplo: instalasr español latino con modificador +f5  y español latino normal

Si quieres instalar todas las voces en español con todas las variantes, copia y pega esto: 

spanish-latin-am+corak spanish-latin-am+whisper spanish-latin-am+whisperf spanish-latin-am+f1 spanish-latin-am+f2 spanish-latin-am+f3 spanish-latin-am+f4 spanish-latin-am+f5 spanish-latin-am+m1 spanish-latin-am+m2 spanish-latin-am+m3 spanish-latin-am+m4 spanish-latin-am+m5 spanish-latin-am+m6 spanish-latin-am+m7 spanish-latin-am+klatt spanish-latin-am+klatt2 spanish-latin-am+klatt3 panish-latin-am+klatt4 spanish+corak spanish+whisper spanish+whisperf spanish+f1 spanish+f2 spanish+f3 spanish+f4 spanish+f5 spanish+m1 spanish+m2 spanish+m3 spanish+m4 spanish+m5 spanish+m6 spanish+m7 spanish+klatt spanish+klatt2 spanish+klatt3 spanish+klatt4

2.- Instalar librerías de Python 3:

En FEDORA o UBUNTU o RASPBIAN o Windows poner en una terminal o linea de comandos:

       pip3 install pyttsx3

Posiblemente nesecites correrlo como root (super usuario).

En WINDOWS podrías necesitar instalar también pypiwin32 si arroja un error al importar la librería en python, pero es mejor ir a lo seguro e instalarla desde el inicio.

      pip3 install  pypiwin32

EJEMPLO 1 (Uso básico):

En el programa que se muestra más abajo, se ejemplifica el uso de pyttsx3. En este ejemplo se inicia por averiguar cuáes voces que están disponibles. Por ejemplo: en linux la voz en español latino es "spanish-latin-am", en windows es algo parecido a "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Speech\Voices\Tokens\eSpeak_2" (es posible que tengas que cambiarlo en el programa de abajo, debido a que el nombre depende de cuantos idiomas y variantes hayas instalado). Después se elige la voz en español, en seguida se ajusta la velocidad y volumen de la voz elegida y por último pronuncia la célebre frase "Hola mundo".


#!/usr/bin/env python3
#-*- coding: utf-8 -*-
#------------------------------------------------------
#  Desarrollado por Carlos Alberto Rodriguez Martinez
#  http://mecatroncharly.blogspot.com/p/blog-page.html
#
#  Este es un programa que dice "Hola mundo" con voz audible
#------------------------------------------------------

from   platform import system
import pyttsx3

ThisOS = system() # averiguar Sistema operativo actual
engine = pyttsx3.init()    # inicializar motor de voz
#---------------------------------------
#     Encoontrar voces isponibles
#---------------------------------------
voices = engine.getProperty('voices')
male_language_count   = 0
female_language_count = 0
none_language_count   = 0
for voice in voices:
    print("------------------------")
    print(" - ID: %s"        % voice.id)
    print(" - Nombre: %s"    % voice.name)
    print(" - Lenguajes: %s" % voice.languages)
    print(" - Genero: %s"    % voice.gender)
    print(" - Edad: %s"      % voice.age)
    if voice.gender == 'male':
        male_language_count += 1
    if voice.gender == 'female':
        female_language_count +=1
    if voice.gender == 'none':
        none_language_count += 1
print ('Voces Masculinas : %d' % male_language_count)
print ('Voces femeninas  : %d' % female_language_count)
print ('Voces neutras    : %d' % none_language_count)
#--------------------------------------
#      CONFIGURAR VOZ E IDIOMA
#--------------------------------------
if ThisOS == 'Linux':
    engine.setProperty('voice', 'spanish-latin-am+f5') #ASIGNAR Español latino
    # agregar +f5 para simular voz femenina (1-5)
if ThisOS == 'Windows':
    engine.setProperty('voice','HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Speech\Voices\Tokens\eSpeak_4')
engine.setProperty('rate'  ,140)   # Aumentar velocidad 40%
engine.setProperty('volume',1.0)   # Poner el volumen al 100%: Minimo:0, Maximo: 1
#--------------------------------------
#               HABLAR
#--------------------------------------
engine.say("Hola mundo!")          # Hablar
engine.runAndWait()                # Esperar a que terine de Hablar
engine.stop()                      # Detener motor tts


EJEMPLO 2

Este programa es un poco mas complicado pero mas cool,  la aplicación dice la hora actual o dice cualquier otra cosa que se le indique. Se utiliza una cola FIFO (librería "queue") donde se ponen en cola las fraces (que podría ser la hora o la frase que se indique) cada vez que se presiona el botón correspondiente. La librería "threading" se utiliza para crear un hilo separado del hilo principal. Este hilo se encarga de revisar constantemente si hay fraces en cola, si es asi, llama a la funcion que dice las fraces que haya en cola, dependiendo del numero de veces que se haya presionado el botón. El programa también tiene su control de velocidad, de volumen y selección de voces en español.

Interfase gráfica en windows.
La interfase en Linux cambia un poco debido a que se pueden combinar los mods con las voces.

Interfase gráfica en Linux.



Para correr este ejemplo nesesitas tener instaladas las librerías: "wxpython" y "queue". Este programa fué probado en windows 7 y Linux (Fedora 30) con python 3.6 y 3.7.

Te dejo comentarios en el código fuente para ayudatre a entender que es lo que sucede.


#!/usr/bin/env python3
#-*- coding: utf-8 -*-
#------------------------------------------------------
#  Desarrollado por Carlos Alberto rodriguez Martinez
#  http://mecatroncharly.blogspot.com/p/blog-page.html
#
#  Este es un programa que dice la hora con voz audible
#------------------------------------------------------

import wx
import pyttsx3
from   platform  import system
from   datetime  import datetime
from   queue     import Queue
from   time      import sleep
from   threading import Thread

class SayTimeApp(wx.Frame):
    def __init__(self):
        wx.Frame.__init__(self,None,-1,"Di la hora")
        self.ThisOS = system()        # averiguar Sistema operativo actual.
        self.createGUI()              # Crear los elementos de la GUI
        self.Lista = Queue()          # Una cola FIFO para que no se traslapen las funciones
        self.thread = Thread(target = self.WrkrThred) # poner funcion WrkrThred en un hilo
        self.thread.daemon = True     # Poner hilo en modo daemon, si el programa termina, tambien el hilo
        self.thread.start()           # Iniciar hilo
        self.VoiceNames = []          # Para guardar los nombres de las voces
        self.voicesIDs  = []          # para guardar los IDs de las voces (winndows)
        self.getVoiceList()           # Averiguar voces disponibles y llenar combobox
  
    def createGUI(self):
        MainPanel = wx.Panel(self) # contenedor de Frames
                               #es util para separar el frame principal de las barras de herramientas y
                               #la barra de estado
        BtnDiHora = wx.Button(MainPanel,
                           label = "Decir la hora",
                           pos   = (125, 10),
                           size  = (300, 50))
        #----------------------------------------------------------------
        BtnDiTexto = wx.Button(MainPanel,
                           label = "Decir esto",
                           pos   = wx.DefaultPosition,
                           size  = wx.DefaultSize)
        self.TxTDecir = wx.TextCtrl(MainPanel,
                                    wx.ID_ANY,
                                    wx.EmptyString,
                                    wx.DefaultPosition,
                                    wx.DefaultSize,
                                    0)
        DecirAlgoSizr = wx.BoxSizer(wx.HORIZONTAL)
        DecirAlgoSizr.Add(BtnDiTexto,  0, wx.ALL, 5)
        DecirAlgoSizr.Add(self.TxTDecir, 1, wx.ALL|wx.CENTER|wx.EXPAND, 5)
        #----------------------------------------------------------------
        self.ComboVoices = wx.ComboBox(MainPanel,
                                       choices = [],
                                       style   = wx.CB_READONLY|wx.ALIGN_CENTER)
        StticBoxVoices  = wx.StaticBox(MainPanel, -1, 'Voz')
        StticBVoicsSizr = wx.StaticBoxSizer(StticBoxVoices, wx.VERTICAL)
        StticBVoicsSizr.Add(self.ComboVoices, 0, wx.ALL|wx.CENTER|wx.EXPAND, 5)
        #----------------------------------------------------------------
        self.SpeedSlider = wx.Slider(MainPanel,
                                     value    = 140,
                                     minValue = 50,
                                     maxValue = 300,
                                     style    = wx.SL_HORIZONTAL|wx.SL_LABELS)
        StticBoxSpeed    = wx.StaticBox(MainPanel, -1, '% Velocidad')
        StticBxSpeedSizr = wx.StaticBoxSizer(StticBoxSpeed, wx.VERTICAL)
        StticBxSpeedSizr.Add(self.SpeedSlider, 0, wx.ALL|wx.CENTER|wx.EXPAND, 5)
        #----------------------------------------------------------------
        self.VolumSlider = wx.Slider(MainPanel,
                                     value    = 100,
                                     minValue = 0,
                                     maxValue = 100,
                                     style    = wx.SL_HORIZONTAL|wx.SL_LABELS)
        StticBoxVolume    = wx.StaticBox(MainPanel, -1, '% Volumen')
        StticBxVolumeSizr = wx.StaticBoxSizer(StticBoxVolume, wx.VERTICAL)
        StticBxVolumeSizr.Add(self.VolumSlider, 0, wx.ALL|wx.CENTER|wx.EXPAND, 5)
        #----------------------------------------------------------------
        if self.ThisOS == 'Linux':   #en linux
            mods = ['f5','f4','f3','f2','f1','klatt4','klatt3','klatt2','klatt',
                    'm7','m6','m5','m4','m3','m2','m1','whisper','whispef','croak',]
            self.ComboMods = wx.ComboBox(MainPanel,
                                       choices = mods,
                                       style   = wx.CB_READONLY|wx.ALIGN_CENTER)
            StticBoxMods  = wx.StaticBox(MainPanel, -1, 'Mods')
            StticBoxModsSizr = wx.StaticBoxSizer(StticBoxMods, wx.VERTICAL)
            StticBoxModsSizr.Add(self.ComboMods, 0, wx.ALL|wx.CENTER|wx.EXPAND, 5)
        #------------------------------------------------------------
        #                    BINDS
        #------------------------------------------------------------
        BtnDiHora .Bind(wx.EVT_BUTTON, self.DiHora)
        BtnDiTexto.Bind(wx.EVT_BUTTON, self.DiTexto)
        #------------------------------------------------------------
        #                    Main layout
        #------------------------------------------------------------
        MainSizer = wx.BoxSizer(wx.VERTICAL)
        MainSizer.Add(BtnDiHora,       0, wx.ALL|wx.CENTER|wx.EXPAND, 5)
        MainSizer.Add(DecirAlgoSizr,   0, wx.ALL|wx.CENTER|wx.EXPAND, 5)
        MainSizer.Add(StticBVoicsSizr, 0, wx.ALL|wx.CENTER|wx.EXPAND, 5)

        if self.ThisOS == 'Linux':   #en linux
            MainSizer.Add(StticBoxModsSizr,   0, wx.ALL|wx.CENTER|wx.EXPAND, 5)
            
        MainSizer.Add(StticBxSpeedSizr,  0, wx.ALL|wx.CENTER|wx.EXPAND, 5)
        MainSizer.Add(StticBxVolumeSizr, 0, wx.ALL|wx.CENTER|wx.EXPAND, 5)
        MainPanel.SetSizer(MainSizer)
        
        MainPanel.Layout()
        MainPanel.Fit()
        self.Layout()
        self.Fit()

    def DiTexto(self,evt):
        Texto = self.TxTDecir.GetValue()
        if Texto != "": #S no hay algo, no hacer nada
            self.Lista.put(Texto)

    def DiHora(self, evt):
        # obtener frace 
        self.Lista.put(self.getcurrTime()) # poner esta frace en cola.
        
    def getcurrTime(self): 
        currentTime   = datetime.now()             #Que hora es ahora
        currminuto    = currentTime.strftime('%M') #obtener minuto como string
        hora   = currentTime.strftime('%I')        #obtener hora como string
        if hora[0] == '0':
            hora = hora[0 : 0 : ] + hora[0 + 1 : :]   #si hay cero a la izquierda, quitarlo
        if currminuto[0] == '0':
            currminuto = currminuto[0 : 0 : ] + currminuto[0 + 1 : :] #si hay cero a la izquierda, quitarlo
        if currminuto == '0':
            currminuto = ''

        texto = ""
        if hora == '1':
            texto = "Es la una "+ str(currminuto)
        else:
            texto = "Son las "+str(hora)+" "+ str(currminuto)
            
        return texto

    def WrkrThred(self): #Esta es la funcion del ilo (thread)
        while True:
            sleep(0.1)
            if self.Lista.empty() == False:           # Mientras que la lista no este vacia
                self.SaySomething(self.Lista.get())   # Decir todo lo que este en cola
            
    def SetupVoice (self):  # configurar voz
        if self.ThisOS == 'Linux':   #en linux
            self.engine.setProperty('voice', self.ComboVoices.GetStringSelection()+"+"+self.ComboMods.GetValue())

        if self.ThisOS == 'Windows': #en windows
            index = self.ComboVoices.GetSelection()                # obtener el indice de seleccion del combobox
            self.engine.setProperty('voice',self.voicesIDs[index]) # obtener el nombe ID de la voz y asignarla

        self.engine.setProperty('rate'  ,self.SpeedSlider.GetValue())     # Asignar % velocidad 
        self.engine.setProperty('volume',self.VolumSlider.GetValue()/100) # Asignar volumen: Minimo:0, Maximo: 1 (por eso /100)
        
    def SaySomething(self, something):
        self.engine = pyttsx3.init()   # inicializar motor de voz.
        self.SetupVoice()              # inicializar voz
        self.engine.say(something)     # Indicar que es lo que se va a decir
        self.engine.runAndWait()       # Esperar a que terine de Hablar
        self.engine.stop()             # Detener motor

    def getVoiceList(self):
        engine = pyttsx3.init()   # inicializar un motor de voz localmente en esta funcion
        voices = engine.getProperty('voices')
        engine.stop()             # Parar el motor

        for voice in voices:
            if voice.name.lower().find("spanish") !=-1: # Restringir las voces al espanol
                self.VoiceNames.append(voice.name)
                self.voicesIDs .append(voice.id)
        self.ComboVoices.SetItems(self.VoiceNames)
        self.ComboVoices.SetSelection(0)

if __name__ == '__main__':
    app = wx.App()
    frame = SayTimeApp()
    frame.Show()
    app.MainLoop()