jueves, 16 de abril de 2020

PYTHON +GOOGLE NOTICIAS MEXICO + GOOGLE TTS

Una voz sintetica en español que lee los encabezados de google noticias Mexico.



Google noticias es un excelente lugar para obtener información de noticias relevantes y recientes, debido a que dichas noticias son de diversas fuentes como: Diario noticias, Excelsior, El universal, entre otras. Este programa obtiene los encabezados de las noticias de google por medio de web scarping y hace el fraseo (parsing) del archivo xml. Cuando obtiene los encabezados, los pone en una lista y los manda al sintetizador de voz de Google para obtener los archivos de sonido de la conversión de texto a voz. En seguida pregunta al usuario si quiere escuchar los audios.

Cuando se realiza web scarping se debe evitar saturar el servidor al que se esté accsesando (enviando a cada instante la requisición de información de este) de lo contrario podrían bloquearnos. Este programa esta diseñado para correrse una sola vez, el programa intentará obtener la información del servidor (Google noticias de México), pero si está saturado o caído, esperará 10 minutos antes de volver a intentarlo. Si falla, esta vez esperara el doble (20 minutos), si falla de nuevo, espera el doble (40 minutos), de esta forma no se satura el servidor y se elimina la posibilidad de que nos bloqueen.

Una vez teniendo los titulares. estos  se dividen en grupos de 4 para mandar menos texto al sintetizador de Google, debido a que solo permite como máximo 100 caracteres por sintetización. 

Este programa fue probado en python 3.8. Para correr este programa necesitas las librerías: bs4, lxml ,gtts , pyautogui y playsound. Se instala fácilmente con pip.
 
SUGERENCIA: Puedes poner este programa en el folder "startup" (en windows). En la PC de mi trabajo hise esto para enterarme de las noticias más relevantes al prender la computadora.

En el programa están comentadas casi todas las lineas de código para que se entienda lo que sucede.
 

Recientemente gtts fallo debido a una actualizacion. se arregla corriendo en una terminal/Linea de comandos los siguiente:

python -m pip install --upgrade gtts
python -m pip install --upgrade gtts-token

#!/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 obtiene las noticias
#  desde google noticias Mexico y sintetiza el texto
#  a voz.
#------------------------------------------------------
import gtts
from os             import getcwd, listdir, path, remove
from pyautogui      import alert, confirm
from playsound      import playsound
from time           import sleep
from bs4            import BeautifulSoup as soup
from urllib.request import urlopen
from urllib.error   import HTTPError
from urllib.error   import URLError
#-------------------------------------------------
#                  Variables
#-------------------------------------------------
news_url="https://news.google.com.mx/news/rss" #pagina de noticias
T_waitWeb   = 600     # 10 minutos (600) segundos para pedir acceso al servidor
T_waitTTS   = 15      # 15 segundos en requerir siguiente TTS de google
Intentos    = 0       # variable para registro de intentos de acceso al servidor
WorkinDir   = getcwd()
#-------------------------------------------------
#                  Funciones
#-------------------------------------------------
def deleteFiles(Dir, extension):  #borrar los que hayan quedado 
    files = listdir(Dir)
    for file in files:
        if file.endswith(extension):
            remove(path.join(Dir,file))

def getFiles(Dir, extension):# obtener lista de archivos de audio para reproducir
    arr = []
    files = listdir(Dir)
    for file in files:
        if file.endswith(extension):
            arr.append(path.join(Dir,file))
    return arr

def playFiles():
    NombreArch = getFiles(WorkinDir,".mp3")
    for x in NombreArch:
        playsound(x) 
#-------------------------------------------------
#            PROGRAMA PRINCIPAL
#-------------------------------------------------
deleteFiles(WorkinDir, ".mp3") # Borrar archivos mp3 si hay.
while True:  # ciclo "infinito" (se rompe cuanto termina satisfactoriamente)
    try:
        Client = urlopen(news_url) #intentar abrir url

    except HTTPError as e:  # si ocurre un error por alguna causa desconocida
        Intentos += 1       # registro de intentos incrementa
        alert(text   = e,   # Mostrar mensaje
              title  = 'Error',
              button = 'OK')         
        T_waitWeb = T_waitWeb * 2 #tiepo de espera es el doble
        sleep(T_waitWeb)          #Esperar

    except URLError:   # si ocurre un error por direccion invalida o servidor caido
        Intentos += 1  # registro de intentos incrementa
                       # Mostrar mensaje
        alert(text   = "Servidor caido o dominio incorrecto. Intentos = "+str(Intentos),
              title  = "Error",
              button = 'OK')
        T_waitWeb = T_waitWeb * 2 #tiepo de espera es el doble
        sleep(T_waitWeb)          #Esperar
    
    else: # Si pudo acceder al servidor
        xml_page = Client.read() # Leer y guardar informacion en una variable
        Client.close()           # cerrar cliente (no hace falta que siga aierto)

        soup_page = soup(xml_page,"xml")      # Frasear archivo xml
        news_list = soup_page.findAll("item") # poner titulares en una lista
        #-----------------------------------------------------------
        #  Dividir titulares de Noticias en grupos de 4 para mandar a Google TTS.
        #-----------------------------------------------------------
        N_Noticias  = 4          # 4 encabezados de noticias
        inicio      = 0          # inicio de cada grupo de 4 (inicia en 0 e incrementa de 4 en 4)
        Acumulador  = N_Noticias # fin    de cada grupo de 4 (inicia en 4 e incrementa de 4 en 4)
        NumArch     = 1          # Variable para nombrae archivos con cuenta numerica Ej: 1.mp3, 2.mp3, etc

        print(len(news_list))
        while Acumulador < len(news_list):
            Texto = ''
            for x in range(inicio,Acumulador):
                Texto = Texto + 'Noticia número ' + str(x+1)+'. '+news_list[x].title.text+". "
                #--------------------------------------------------------------------
                #        Obtener otros datos de cada item si se quiere
                #--------------------------------------------------------------------
                #print(news_list[x].link.text)    # obtener link de la noticia
                #print(news_list[x].pubDate.text) # obtener la fecha de la noticia

            #print(Texto)      #Debug
            #print(inicio)     #Debug
            #print(Acumulador) #Debug
            
            inicio     += N_Noticias # incrementar para el inicio del siguiente grupo
            Acumulador += N_Noticias # incrementar para el fin del siguiente grupo

            #----------------------------------------------------
            #   Mandar texto a GTTS y guardar archivo de sonido
            #----------------------------------------------------
            tts = gtts.tts.gTTS(Texto, lang='es-us')
            
            try:
                NombreArch = str(NumArch)+".mp3"
                with open(NombreArch, "wb") as archivo:
                    tts.write_to_fp(archivo)
                    
                NumArch += 1
                sleep(T_waitTTS)
            except:
                pass
        #----------------------------------------------------
        # Preguntar al usuario si quiere escuchar los audios
        #----------------------------------------------------
        respuesta = confirm(text='Se generaron audios de noticias. Quieres escucharlas',
                            title='Escuchar?',
                            buttons=['Si', 'No'])
        if respuesta == 'Si':
            playFiles()
        break # romper ciclo while principal

viernes, 27 de marzo de 2020

MONITOREO DE MULTIPLES HILOS EN PYTHON 3


En este programa se ejemplifica cómo monitorear varios hilos y mostrar el progreso de cada uno en la interfase gráfica. Para saber mas sobre hilos, procesos y demás. puedes leer el post del link.


El programa utiliza unas barras de progreso (gauge) para representar cada hilo en la interfase gráfica. Cada hilo tiene un proceso, el cual consiste en contar de 0 a 100, con un retraso aleatorio de tiempo de entre 5 y 10 mili segundos en cada iteración para simular la variación de tiempos de proceso. Puedes  cambiar el numero de procesos en la variable num_procesos.  Corre este programa una vez y observa como las barras de progreso se llenan casi al mismo tiempo.

semáforo desactivado

En el código mostrado mas abajo hay 2 funciones comentadas. Estas funciones son threadLock.acquire() y threadLock.release(). Estas funciones funcionan (valga la redunancia) como un semáforo que pausa un hilo determinado mientras otro continua. Esto es útil para evitar corromper datos cuando 2 o mas hilos intentan acceder a la misma información para lectura o escritura al mismo tiempo. Des-comenta estas 2 funciones como experimento para observar como las barras de progreso se pausan entre si de modo que ya no se llenan de de forma uniforme.

Semáforo activado

En el código fuente te dejo comentarios comprender lo que sucede. Nesecitas la librería wxpython para correr este programa. Este programa fué probado en Python 3.7.


import wx
from time            import sleep
from random          import randint
from threading       import Thread
from threading       import Lock
from threading       import current_thread
from wx.lib.newevent import NewEvent
 
threadLock = Lock() # descomenta threadLock.acquire() y threadLock.release()
                    # (mas abajo) como experimento para ver que sucede.
MandaGaugeDatos, EVT_ACTUAL_GAUG = NewEvent() # evento que se usara para monitorear hilos y actualizar gauges
num_procesos = 5   # numero de hlos (y por ende, de gauges) pon aqui los que quieras

class TestThread(Thread): # Esto es un hilo.
    def __init__(self,parent,threadID):
        Thread.__init__(self)     # llamar al constructor de la clase
        self.threadID = threadID  # variable para identificar el hilo
        self.parent   = parent    # padre de el hilo (se usa para matar hilo cunando se sierre el programa)

    def run(self): # EL PROCESO
        #--------------------------------------------------
        #   El roceso consiste en contar de 1 a 100.
        #   se generan retardos aleatorios para simular tiempos variantes
        #--------------------------------------------------
        for i in range(1,100): # Cuenta de 1 a 100%
            
            #threadLock.acquire() # DESCOMENTA ESTO A VER QUE PASA.

            slp = randint(5, 10)/1000  # generar tiempo aleatorio entre 5 y 10ms
            sleep(slp)                 # realizar el retraso aleatorio
            #-------------------------------------------------------------
            # Enviar datos resultantes de cada iteracion del hilo a la GUI
            #  wx.postevent(parent, function) #generar evento
            #  MandaGaugeDatos(datos nombrados que quieras regresar)
            #--------------------------------------------------
            wx.PostEvent(self.parent,
                         MandaGaugeDatos(dato   = i,
                                         gauge  = self.threadID,
                                         thread = current_thread()))
            
            #threadLock.release() # DESCOMENTA ESTO A VER QUE PASA.
     
class MainFrame(wx.Frame):
    def __init__(self, parent, title =""):
        super().__init__(parent, title = title , size = (500,250))
        self.panel  = wx.Panel(self)
        
        self.gauges = []  # Arreglo de Gauges
        threads     = []  # arreglo de hilos
        #---------------------------------------------
        #                WIDGETS
        #---------------------------------------------
        #       llenar el arreglo de gauges
        #--------------------------------------------
        for gauge in range(num_procesos):
            self.gauges.append(wx.Gauge(self.panel, size=(300, 20)))
        #---------------------------------------------
        #                 BINDS
        #---------------------------------------------
        # Conectar señal del evento emitida con la funcion correspondiente
        #--------------------------------------------
        self.Bind(EVT_ACTUAL_GAUG, self.on_actualizar)
        #---------------------------------------------
        #                 LAYOUT
        #---------------------------------------------
        main_sizer = wx.BoxSizer(wx.VERTICAL)
        main_sizer.Add(self.panel,1, wx.CENTER|wx.ALL|wx.EXPAND)
        panel_sizer = wx.BoxSizer(wx.VERTICAL)
        self.panel.SetSizer(panel_sizer)
        for gauge in self.gauges:
            panel_sizer.Add(gauge , 0, wx.CENTER|wx.ALL|wx.EXPAND)
        self.SetSizer(main_sizer)
        #---------------------------------------------
        #  llenar arreglo de hilos e  Iniciarlos todos
        #---------------------------------------------
        for x in range(num_procesos):
            threads.append(TestThread(self, x))
            threads[x].daemon = True
            threads[x].start()
            
    def on_actualizar(self, evt):
        #Actualizar el gauge correspondiente en la GUI
        self.gauges[evt.gauge].SetValue(evt.dato)
 
if __name__ == '__main__':
    app = wx.App(0)
    frame = MainFrame(None)
    app.SetTopWindow(frame)
    frame.Show()
    app.MainLoop()

jueves, 19 de marzo de 2020

MEDICION MULTIPLE DE TIEMPOS EN PYTHON 3



Imagina que tienes varias estaciones donde se procesa algún tipo de  pieza y quieres medir cuanto tiempo permanece cada pieza en cada estación. Supongamos también que cada estación tiene su tiempo de ciclo (unas inician antes otras despues, otras al mismo tiempo).

En el siguiente programa muestro como medir una cantidad determinada de tiempos con un solo temporizador y un solo hilo (thread). El programa calcula minutos, segundos y centésimas de segundo de cada estacion. La interfase de usuario es dinámica, es decir, se puede determinar el numero de registros de tiempo que se deseen (yo puse 20 pero lo puedes cambiar la variable numtimers al numero que quieras), el programa genera los elementos nesesarios. Este programa apenas consume CPU (se podría usar en un Raspberry Pi).El programa inicia el conteo al presionar un botón correspondiente, cuando se presiona de nuevo detiene el conteo de tiempo y lo guarda en un archivo de texto.

Programa en windows

El programa tiene comentarios en la mayoría de las líneas de código para que se pueda entender lo que sucede.

AVISO: Cada temporizador crea un archivo de texto, si pones 50, se crean 50 archivos de texto en el lugar donde esta el script. Se recomienda correrlo dentro de su propio folder.

Este programa fue probado con python 3.7. También nesecitas wxpython para correr este programa.

import wx
from   time                 import sleep
from   threading            import Thread
from   wx.lib.scrolledpanel import ScrolledPanel

class TimeReg():   #clase para registrar conteo de tiempo y estado del conteo.
    def __init__(self):
        self.tiempo = [0,0,0]  #registro de minutos, segundos y decasegundos
        self.activado = False  #iniciar desactivado

    def update(self):
        self.tiempo[2] += 1         # incremento cada centisegundo (1/100)

        if (self.tiempo[2] >= 100): # Han pasado 100 centisegundos (1 segundo)?
            self.tiempo[2] =  0     # resetear cuenta de centisegundos.
            self.tiempo[1] += 1     # incrementar segundero
                
        if (self.tiempo[1] >= 60):  # Han pasado 60 segundos (1 minuto)?
            self.tiempo[0] += 1     # incrementar minutero
            self.tiempo[1] =  0     # resetear segundero

    def reset(self):
        self.tiempo = [0, 0, 0]     # 0 minutos, 0 segundos, 0 centisegundos
        

class MyForm(wx.Frame):
    def __init__(self):
        wx.Frame.__init__(self, None, wx.ID_ANY, "Timers")#llamar a la funcion constructor de Frame
        #---------------------------------------------------
        #                 VARIABLES
        #---------------------------------------------------
        self.numtimers       = 20        # cuantos timers quieres 20, 50, 200?
        self.CronometrArr    = []        # Arreglo de registro de tiempos
        self.TimeRegePattern = '{0:02d}:{1:02d}:{2:02d}'       # Darle formato al tiempo
        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.exitFlag        = False     # Bandera para parar el ciclo del hilo
        self.LblTimeRegeArr  = []        # Arreglo de etiquetas de tiempos
        self.BtnTimeRegrArr  = []        # Arreglo de botones.
        FntLblTimeRegrs      = wx.Font(16, wx.MODERN, wx.NORMAL, wx.BOLD)  #fuente a usar
        self.TiempoStr       = ""

        for i in range(self.numtimers):
            self.CronometrArr.append(TimeReg()) #Arreglo de objetos de registro de tiempos

        self.createFiles() #crear los archivos de texto nesesarios
        #---------------------------------------------------
        #                 WIDGETS
        #---------------------------------------------------
        PnlTimeRegpos = ScrolledPanel(self) #Panel de lecturas de tiempos
        
        for i in range(self.numtimers):
            # crear etiquetas y llenar arreglo
            self.LblTimeRegeArr.append(wx.StaticText(PnlTimeRegpos,
                                                -1,
                                                style = wx.ALIGN_CENTER|wx.ST_ELLIPSIZE_MIDDLE))
            self.LblTimeRegeArr[i].SetFont(FntLblTimeRegrs)     #asignar fuente
            self.LblTimeRegeArr[i].SetBackgroundColour('black') #asignar color de fondo
            self.LblTimeRegeArr[i].SetForegroundColour('white') #asignar color de letra
            self.LblTimeRegeArr[i].SetLabel("00:00:00")         #texto inicial

            # crear botones y llenar arreglo de botones
            self.BtnTimeRegrArr.append(wx.ToggleButton(PnlTimeRegpos ,
                                                  -1,
                                                  str(i),
                                                  size=(50,40)))
        #--------------------------------------------------
        #                 BINDS
        #--------------------------------------------------
        self.Bind(wx.EVT_CLOSE, self.onClosed) # trabajo de limpieza al salir de la aplicacion
        
        for i in range(self.numtimers):
            # poner bind a todos los botones a self.onTggleBtn
            self.BtnTimeRegrArr[i].Bind(wx.EVT_TOGGLEBUTTON,self.onTggleBtn)
 
        #---------------------------------------------------
        #                 MAIN LAYOUT
        #--------------------------------------------------
        MainSizer = wx.BoxSizer(wx.VERTICAL)
        #--------- Panel de lecturas ------
        TimeRegposSizrArr = [] #Arreglo de sizers
        PnlTimeRegposSizr = wx.BoxSizer(wx.VERTICAL)
        
        for i in range(self.numtimers):
            TimeRegposSizrArr.append(wx.BoxSizer(wx.HORIZONTAL))
            TimeRegposSizrArr[i].Add(self.BtnTimeRegrArr[i],0,wx.EXPAND|wx.ALL,2)
            TimeRegposSizrArr[i].Add(self.LblTimeRegeArr[i],1,wx.EXPAND|wx.ALL,2)
            PnlTimeRegposSizr.Add(TimeRegposSizrArr[i],1,wx.EXPAND|wx.ALL,0)
        PnlTimeRegpos.SetSizer(PnlTimeRegposSizr)
        PnlTimeRegpos.SetupScrolling() #calcular espacio para scroll bar
        #--------------------------------------------------
        MainSizer.Add(PnlTimeRegpos, 1,wx.EXPAND|wx.ALL,5)
        self.SetSizer(MainSizer)
        #---------------------------------------------------
        #            INICIAR HILOS AL ULTIMO
        #---------------------------------------------------         
        self.thread.start()       # Iniciar hilo
        
    def WrkrThred(self):
        while True: #loop "infinito"
            if self.exitFlag == False: # programa sigue corriendo?
                sleep(0.01)            # reterdo de 1 centi sedundo
                for i in range(self.numtimers):                    #iterar en los registros
                    if self.CronometrArr[i].activado == True:      #si esta activado
                        self.CronometrArr[i].update()              #actualizar registro i
                        self.TiempoStr = self.TimeRegePattern.format(self.CronometrArr[i].tiempo[0],
                                                                     self.CronometrArr[i].tiempo[1],
                                                                     self.CronometrArr[i].tiempo[2]) #Dar formato a los tiempos
                        wx.CallAfter(self.LblTimeRegeArr[i].SetLabel,self.TiempoStr)                 #Actualizar Texto correspondiente
                        #wx.callafter es un modo seguro de actualizar GUI desde un hilo.
            else:
                break #Romper el ciclo infinito

    def onTggleBtn(self, evt):
        BTN      = evt.GetEventObject()  #obtener el objeto boton que emite la señal
        BtnID    = BTN.GetLabelText()    #obtener el texto del boton
        BtnIdInt = int(BtnID)            #convertir el texto del boton en entero.
        if BTN.GetValue():
            self.CronometrArr[BtnIdInt].reset()          #reestablecer conteo
            self.CronometrArr[BtnIdInt].activado = True  #iniciar conteo
        else:
            self.savetimefile(BtnID)                     #Guardar conteo
            self.CronometrArr[BtnIdInt].activado = False #detener conteo

    def savetimefile(self,timer):
        f = open(timer+".txt", "a+")
        f.write(self.TiempoStr+"\n")
        f.close()
        
    def createFiles(self):
        for x in range(self.numtimers):
            open(str(x)+".txt","w+").close()

    def onClosed(self, evt):
        self.exitFlag = True #Salir del ciclo while del hilo
        self.Destroy()       #Destruir TODO

if __name__ == "__main__":
    app = wx.App()
    frame = MyForm().Show()
    app.MainLoop()

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()