diff --git a/README.md b/README.md index 447d532..e33ed7c 100644 --- a/README.md +++ b/README.md @@ -40,8 +40,9 @@ Lee y gestiona de manera accesible el chat de youtube en tus propios directos o ## futuras actualizaciones: He agregado para futuras actualizaciones próximas -- el poder traducir facilmente la aplicación a otros idiomas. -- Posibbilidad de mostrar información de la persona que está chateando desde la interfaz invisible: +- la creación de distintas categorías de mensajes en la interfaz invisible. + - poder ordenar el contenido por distintas insignias tales como donativos, miembros moderadores. +- Posibilidad de mostrar información de la persona que está chateando desde la interfaz invisible: - El usuario es moderador? - Nombre del canal del usuario - Entre muchas cosas mas. @@ -66,4 +67,4 @@ Con tu apoyo contribuyes a que este programa siga en crecimiento. [¿Te unes a nuestra causa?](https://www.paypal.com/donate/?hosted_button_id=5ZV23UDDJ4C5U) -[Descarga el programa desde aquí](https://github.com/metalalchemist/VeTube/files/8586368/VeTube.zip) +[Descarga el programa desde aquí](https://github.com/metalalchemist/VeTube/files/8586368/VeTube.zip) \ No newline at end of file diff --git a/VeTube.pot b/VeTube.pot new file mode 100644 index 0000000..6af8c16 --- /dev/null +++ b/VeTube.pot @@ -0,0 +1,357 @@ +# Translations template for PROJECT. +# Copyright (C) 2022 ORGANIZATION +# This file is distributed under the same license as the PROJECT project. +# FIRST AUTHOR , 2022. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: PROJECT VERSION\n" +"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" +"POT-Creation-Date: 2022-05-19 01:06-0700\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.10.1\n" + +#: chat/languageHandler.py:75 +msgid "Idioma del sistema" +msgstr "" + +#: chat/vetube.py:108 chat/vetube.py:112 +msgid "Espere por favor...\n" +msgstr "" + +#: chat/vetube.py:108 chat/vetube.py:112 +#, python-format +msgid "Descargando: %s" +msgstr "" + +#: chat/vetube.py:123 +msgid "" +"Se terminó la descarga de la nueva actualización.\n" +"el programa se cerrará para que usted reemplase los archivos de la nueva " +"actualización.\n" +"¿disfrute!" +msgstr "" + +#: chat/vetube.py:126 +msgid "" +"Algo salió mal.\n" +"Compruebe que tiene conexión a internet y vuelva a intentarlo.\n" +"el programa se cerrará." +msgstr "" + +#: chat/vetube.py:132 +msgid "Actualizando el programa..." +msgstr "" + +#: chat/vetube.py:139 chat/vetube.py:209 chat/vetube.py:344 +msgid "&Aceptar" +msgstr "" + +#: chat/vetube.py:142 chat/vetube.py:639 +msgid "&Cerrar" +msgstr "" + +#: chat/vetube.py:196 chat/vetube.py:657 +msgid "Una nueva versión de VeTube está disponible. ¿desea descargarla aora?" +msgstr "" + +#: chat/vetube.py:196 chat/vetube.py:208 chat/vetube.py:433 chat/vetube.py:448 +#: chat/vetube.py:453 chat/vetube.py:460 chat/vetube.py:657 +msgid "Atención:" +msgstr "" + +#: chat/vetube.py:208 +msgid "" +"Con tu apoyo contribuyes a que este programa siga siendo gratuito. ¿Te " +"unes a nuestra causa?" +msgstr "" + +#: chat/vetube.py:209 chat/vetube.py:342 chat/vetube.py:454 +msgid "&Cancelar" +msgstr "" + +#: chat/vetube.py:224 +msgid "&Más opciones" +msgstr "" + +#: chat/vetube.py:226 +msgid "Pulse alt para abrir el menú" +msgstr "" + +#: chat/vetube.py:230 +msgid "Inicio" +msgstr "" + +#: chat/vetube.py:232 +msgid "Escriba o pegue una URL de youtube" +msgstr "" + +#: chat/vetube.py:235 +msgid "Escribe o pega una URL" +msgstr "" + +#: chat/vetube.py:240 +msgid "&Acceder" +msgstr "" + +#: chat/vetube.py:244 +msgid "Borrar" +msgstr "" + +#: chat/vetube.py:259 +msgid "&Opciones" +msgstr "" + +#: chat/vetube.py:260 chat/vetube.py:281 +msgid "Configuración" +msgstr "" + +#: chat/vetube.py:262 +msgid "Restablecer los ajustes" +msgstr "" + +#: chat/vetube.py:264 +msgid "&Ayuda" +msgstr "" + +#: chat/vetube.py:265 +msgid "Únete a nuestra &causa" +msgstr "" + +#: chat/vetube.py:267 +msgid "&Visita nuestra página de github" +msgstr "" + +#: chat/vetube.py:269 +msgid "&buscar actualizaciones" +msgstr "" + +#: chat/vetube.py:271 +msgid "Acerca de" +msgstr "" + +#: chat/vetube.py:283 +msgid "Categorías" +msgstr "" + +#: chat/vetube.py:288 +msgid "General" +msgstr "" + +#: chat/vetube.py:290 +msgid "Opciones de la app" +msgstr "" + +#: chat/vetube.py:292 +msgid "Idioma de VeTube (Requiere reiniciar)" +msgstr "" + +#: chat/vetube.py:297 +msgid "Modalidad del chat: " +msgstr "" + +#: chat/vetube.py:299 +msgid "Todos los chats" +msgstr "" + +#: chat/vetube.py:299 +msgid "solo chats de miembros y donativos." +msgstr "" + +#: chat/vetube.py:303 +msgid "Reproducir sonidos." +msgstr "" + +#: chat/vetube.py:309 +msgid "Voz" +msgstr "" + +#: chat/vetube.py:311 +msgid "Opciones del habla" +msgstr "" + +#: chat/vetube.py:313 +msgid "Voz sapy" +msgstr "" + +#: chat/vetube.py:317 +msgid "Voz: " +msgstr "" + +#: chat/vetube.py:323 +msgid "Tono: " +msgstr "" + +#: chat/vetube.py:328 +msgid "Volumen: " +msgstr "" + +#: chat/vetube.py:333 +msgid "Velocidad: " +msgstr "" + +#: chat/vetube.py:338 +msgid "&Reproducir prueba." +msgstr "" + +#: chat/vetube.py:357 +msgid "" +"Hola, soy la voz que te acompañará de ahora en adelante a leer los " +"mensajes de tus canales favoritos." +msgstr "" + +#: chat/vetube.py:358 +msgid "Creadores del proyecto:" +msgstr "" + +#: chat/vetube.py:358 +msgid "" +"Descripción:\n" +" Lee en voz alta los mensajes de los directos en youtube, ajusta tus " +"preferencias como quieras y disfruta más tus canales favoritos." +msgstr "" + +#: chat/vetube.py:358 chat/vetube.py:669 +msgid "Información" +msgstr "" + +#: chat/vetube.py:383 +msgid "" +"No se puede acceder porque el campo de texto está vacío, debe escribir" +" algo." +msgstr "" + +#: chat/vetube.py:396 +msgid "Chat en vivo" +msgstr "" + +#: chat/vetube.py:398 +msgid "Lectura del chat en vivo..." +msgstr "" + +#: chat/vetube.py:402 +msgid "historial de mensajes: " +msgstr "" + +#: chat/vetube.py:408 +msgid "Limpiar historial" +msgstr "" + +#: chat/vetube.py:411 +msgid "&Detener" +msgstr "" + +#: chat/vetube.py:420 +msgid "Ingresando al chat." +msgstr "" + +#: chat/vetube.py:429 +msgid "" +"¡Parece que el enlace al cual está intentando acceder no es un enlace " +"válido." +msgstr "" + +#: chat/vetube.py:433 +msgid "¿Desea salir de esta ventana y detener la lectura de los mensajes?" +msgstr "" + +#: chat/vetube.py:439 +msgid "ha finalizado la lectura del chat." +msgstr "" + +#: chat/vetube.py:448 +msgid "" +"Es necesario reiniciar el programa para aplicar el nuevo idioma. ¿desea " +"reiniciarlo ahora?" +msgstr "" + +#: chat/vetube.py:453 +msgid "Está apunto de eliminar del historial aproximadamente " +msgstr "" + +#: chat/vetube.py:453 +msgid " elementos, ¿desea proceder? Esta acción no se puede desacer." +msgstr "" + +#: chat/vetube.py:454 +msgid "&Eliminar" +msgstr "" + +#: chat/vetube.py:455 +msgid "No hay elementos que borrar" +msgstr "" + +#: chat/vetube.py:460 +msgid "" +"Estás apunto de reiniciar la configuración a sus valores predeterminados," +" ¿Deseas proceder?" +msgstr "" + +#: chat/vetube.py:488 +msgid " reproducciones" +msgstr "" + +#: chat/vetube.py:503 chat/vetube.py:504 chat/vetube.py:506 +msgid " se a conectado al chat. " +msgstr "" + +#: chat/vetube.py:510 chat/vetube.py:511 chat/vetube.py:512 +msgid "Moderador " +msgstr "" + +#: chat/vetube.py:516 chat/vetube.py:517 chat/vetube.py:518 +msgid "Miembro " +msgstr "" + +#: chat/vetube.py:520 chat/vetube.py:521 chat/vetube.py:522 +msgid " (usuario verificado): " +msgstr "" + +#: chat/vetube.py:538 chat/vetube.py:543 chat/vetube.py:551 chat/vetube.py:556 +#: chat/vetube.py:564 chat/vetube.py:569 chat/vetube.py:576 chat/vetube.py:581 +#: chat/vetube.py:589 chat/vetube.py:594 +msgid "no hay elementos en el historial" +msgstr "" + +#: chat/vetube.py:567 chat/vetube.py:572 +msgid "¡Copiado!" +msgstr "" + +#: chat/vetube.py:607 +msgid "Todos los chats." +msgstr "" + +#: chat/vetube.py:607 +msgid "Miembros y donativos." +msgstr "" + +#: chat/vetube.py:614 +msgid "Voz activada." +msgstr "" + +#: chat/vetube.py:614 +msgid "Voz desactivada." +msgstr "" + +#: chat/vetube.py:628 +msgid "¿está seguro que desea salir del programa?" +msgstr "" + +#: chat/vetube.py:628 +msgid "¡atención!:" +msgstr "" + +#: chat/vetube.py:635 +msgid "mensaje" +msgstr "" + +#: chat/vetube.py:669 +msgid "Al parecer tienes la última versión del programa" +msgstr "" + diff --git a/VeTube.py b/VeTube.py index 775e7a0..45688b3 100644 --- a/VeTube.py +++ b/VeTube.py @@ -1,48 +1,37 @@ #!/usr/bin/python # -*- coding: -*- -import pytchat, json,wx,threading,keyboard,gettext,webbrowser,urllib.request +import json,wx,threading,keyboard,webbrowser,urllib.request,languageHandler,socket,os,time,restart from playsound import playsound -from os import path from accessible_output2.outputs import auto, sapi5 from youtube_dl import YoutubeDL from pyperclip import copy -#pronto se dejará de usar pytchat from chat_downloader import ChatDownloader +os.system('rmdir %temp%\gen_py /Q/S') voz=0 configchat=1 tono=0 volume=100 -mensajes=0 -buffer=100 speed=0 sapy=True todos=True sonidos=True -version=1.2 -idiomas = [] +idioma="system" +version="v1.2" miembros=[] favoritos=[] leer=sapi5.SAPI5() lector=auto.Auto() lista=leer.list_voices() -t = gettext.translation('VeTube', 'locales',fallback=True,) -_ = t.gettext -def asignarBuffer(): - if mensajes==0 : buffer=100 - elif mensajes==1 : buffer=300 - elif mensajes==2 : buffer=500 - elif mensajes==3 : buffer=1000 def asignarConfiguracion(): - global voz,tono,volume,speed,configchat,sapy,mensajes,sonidos,buffer + global voz,tono,volume,speed,configchat,sapy,sonidos,idioma voz=0 configchat=1 tono=0 volume=100 speed=0 sapy=True - mensajes=0 sonidos=True - buffer=100 + idioma="system" leer.set_rate(speed) leer.set_pitch(tono) leer.set_voice(lista[voz]) @@ -55,53 +44,184 @@ def escribirConfiguracion(): "volume": volume, "speed": speed, 'sapy':sapy, -'mensajes': mensajes, -'sonidos': sonidos} +'sonidos': sonidos, +'idioma': idioma +} with open('data.json', 'w+') as file: json.dump(data, file) def leerConfiguracion(): - if path.exists("data.json"): + if os.path.exists("data.json"): with open ("data.json") as file: resultado=json.load(file) - global voz,configchat,tono,volume,speed,sapy,sonidos,mensajes + global voz,configchat,tono,volume,speed,sapy,sonidos,idioma voz=resultado['voz'] configchat=resultado['configchat'] tono=resultado['tono'] volume=resultado['volume'] speed=resultado['speed'] sapy=resultado['sapy'] - mensajes=resultado['mensajes'] sonidos=resultado['sonidos'] + idioma=resultado['idioma'] else: escribirConfiguracion() leerConfiguracion() -asignarBuffer() +languageHandler.setLanguage(idioma) +idiomas = languageHandler.getAvailableLanguages() +langs = [] +[langs.append(i[1]) for i in idiomas] +codes = [] +[codes.append(i[0]) for i in idiomas] +codes.reverse() +langs.reverse() leer.set_rate(speed) leer.set_pitch(tono) leer.set_voice(lista[voz]) leer.set_volume(volume) +db_fichero_complemento = os.path.join(os.getcwd(), 'vetube.zip') +class HiloActualizacionComplemento(threading.Thread): + def __init__(self, frame): + super(HiloActualizacionComplemento, self).__init__() + self.frame = frame + self.tiempo = 30 + self.daemon = True + threading.Thread.__init__(self) + def humanbytes(self, B): # Convierte bytes + B = float(B) + KB = float(1024) + MB = float(KB ** 2) # 1,048,576 + GB = float(KB ** 3) # 1,073,741,824 + TB = float(KB ** 4) # 1,099,511,627,776 + if B < KB: + return '{0} {1}'.format(B,'Bytes' if 0 == B > 1 else 'Byte') + elif KB <= B < MB: + return '{0:.2f} KB'.format(B/KB) + elif MB <= B < GB: + return '{0:.2f} MB'.format(B/MB) + elif GB <= B < TB: + return '{0:.2f} GB'.format(B/GB) + elif TB <= B: + return '{0:.2f} TB'.format(B/TB) + def __call__(self, block_num, block_size, total_size): + readsofar = block_num * block_size + if total_size > 0: + percent = readsofar * 1e2 / total_size + wx.CallAfter(self.frame.next, percent) + time.sleep(1 / 995) + wx.CallAfter(self.frame.TextoRefresco, _("Espere por favor...\n") + _("Descargando: %s") % self.humanbytes(readsofar)) + if readsofar >= total_size: # Si queremos hacer algo cuando la descarga termina. + pass + else: # Si la descarga es solo el tamaño + wx.CallAfter(self.frame.TextoRefresco, _("Espere por favor...\n") + _("Descargando: %s") % self.humanbytes(readsofar)) + def run(self): + while self.tiempo==30: + try: + socket.setdefaulttimeout(self.tiempo) # Dara error si pasan 30 seg sin internet + try: + req = urllib.request.Request(urlDescarga, headers={'User-Agent': 'Mozilla/5.0'}) + obj = urllib.request.urlopen(req).geturl() + urllib.request.urlretrieve(obj, db_fichero_complemento, reporthook=self.__call__) + except Exception as e: + urllib.request.urlretrieve(obj, db_fichero_complemento, reporthook=self.__call__) + wx.CallAfter(self.frame.done, _("Se terminó la descarga de la nueva actualización.\nel programa se cerrará para que usted reemplase los archivos de la nueva actualización.\n¿disfrute!")) + self.tiempo=0 + except: + wx.CallAfter(self.frame.error, _("Algo salió mal.\nCompruebe que tiene conexión a internet y vuelva a intentarlo.\nel programa se cerrará.")) + self.tiempo=0 +class ActualizacionDialogo(wx.Dialog): + def __init__(self, frame): + WIDTH = 550 + HEIGHT = 400 + super(ActualizacionDialogo, self).__init__(None, -1, title=_("Actualizando el programa..."), size = (WIDTH, HEIGHT)) + self.CenterOnScreen() + self.frame = frame + self.Panel = wx.Panel(self) + self.progressBar=wx.Gauge(self.Panel, wx.ID_ANY, range=100, style = wx.GA_HORIZONTAL) + self.textorefresco = wx.TextCtrl(self.Panel, wx.ID_ANY, style =wx.TE_MULTILINE|wx.TE_READONLY) + self.textorefresco.Bind(wx.EVT_CONTEXT_MENU, self.skip) + self.AceptarTRUE = wx.Button(self.Panel, wx.NewIdRef(), _("&Aceptar")) + self.Bind(wx.EVT_BUTTON, self.onAceptarTRUE, id=self.AceptarTRUE.GetId()) + self.AceptarTRUE.Disable() + self.AceptarFALSE = wx.Button(self.Panel, wx.NewIdRef(), _("&Cerrar")) + self.Bind(wx.EVT_BUTTON, self.onAceptarFALSE, id=self.AceptarFALSE.GetId()) + self.AceptarFALSE.Disable() + self.Bind(wx.EVT_CLOSE, self.onNull) + sizer = wx.BoxSizer(wx.VERTICAL) + sizer_botones = wx.BoxSizer(wx.HORIZONTAL) + sizer.Add(self.progressBar, 0, wx.EXPAND) + sizer.Add(self.textorefresco, 1, wx.EXPAND) + sizer_botones.Add(self.AceptarTRUE, 2, wx.CENTER) + sizer_botones.Add(self.AceptarFALSE, 2, wx.CENTER) + sizer.Add(sizer_botones, 0, wx.EXPAND) + self.Panel.SetSizer(sizer) + self.textorefresco.SetFocus() + global act + act=HiloActualizacionComplemento(self) + act.start() + def skip(self, event): + return + def onNull(self, event): + pass + def next(self, event): + self.progressBar.SetValue(event) + def TextoRefresco(self, event): + self.textorefresco.Clear() + self.textorefresco.AppendText(event) + def done(self, event): + self.AceptarTRUE.Enable() + self.textorefresco.Clear() + self.textorefresco.AppendText(event) + self.textorefresco.SetInsertionPoint(0) + act.join() + def error(self, event): + self.AceptarFALSE.Enable() + self.textorefresco.Clear() + self.textorefresco.AppendText(event) + self.textorefresco.SetInsertionPoint(0) + act.join() + def onAceptarTRUE(self, event): + if self.IsModal(): + self.EndModal(event.EventObject.Id) + else: + self.Destroy() + exit() + def onAceptarFALSE(self, event): + if self.IsModal(): + self.EndModal(event.EventObject.Id) + else: + self.Destroy() + exit() class MyFrame(wx.Frame): def __init__(self, *args, **kwds): + r = urllib.request.urlopen('https://api.github.com/repos/metalalchemist/VeTube/releases').read() + gitJson = json.loads(r.decode('utf-8')) + if gitJson[0]["tag_name"] != version: + dlg = wx.MessageDialog(None, _("Una nueva versión de VeTube está disponible. ¿desea descargarla aora?"), _("Atención:"), wx.YES_NO | wx.ICON_ASTERISK) + if dlg.ShowModal()==wx.ID_YES: + global urlDescarga + urlDescarga = gitJson[0]['body'] + urlDescarga=urlDescarga.split('](') + urlDescarga=urlDescarga[1] + urlDescarga=urlDescarga.split(')') + urlDescarga=urlDescarga[0] + dlg.Destroy + dlg = ActualizacionDialogo(self) + result = dlg.ShowModal() + else: dlg.Destroy() + dlg = wx.MessageDialog(None, _("Con tu apoyo contribuyes a que este programa siga siendo gratuito. ¿Te unes a nuestra causa?"), _("Atención:"), wx.YES_NO | wx.ICON_ASTERISK) + dlg.SetYesNoLabels(_("&Aceptar"), _("&Cancelar")) + if dlg.ShowModal()==wx.ID_YES: + webbrowser.open('https://www.paypal.com/donate/?hosted_button_id=5ZV23UDDJ4C5U') + dlg.Destroy() + else: dlg.Destroy() self.contarmiembros=0 self.contador=0 self.dentro=False - self.msg="" - self.hilo1 = threading.Thread(target=self.capturarTeclas) - self.hilo1.daemon = True - self.hilo1.start() kwds["style"] = kwds.get("style", 0) | wx.DEFAULT_FRAME_STYLE wx.Frame.__init__(self, *args, **kwds) - self.donar = wx.Dialog(self, wx.ID_ANY, _("información")) - dlg = wx.MessageDialog(self.donar, _("Con tu apoyo contribuyes a que este programa siga siendo gratuito. ¿Te unes a nuestra causa?"), _(u"Atención:"), wx.YES_NO | wx.ICON_ASTERISK) - dlg.SetYesNoLabels(_("&Aceptar"), _("&Cancelar")) - if dlg.ShowModal()==wx.ID_YES: - webbrowser.open('https://www.paypal.com/donate/?hosted_button_id=5ZV23UDDJ4C5U') - self.donar.Destroy() - else: self.donar.Destroy() self.SetSize((800, 600)) self.SetTitle("VeTube") self.SetWindowStyle(wx.RESIZE_BORDER | wx.CAPTION | wx.CLOSE_BOX | wx.CLIP_CHILDREN) self.panel_1 = wx.Panel(self, wx.ID_ANY) sizer_1 = wx.BoxSizer(wx.VERTICAL) - self.menu_1 = wx.Button(self.panel_1, wx.ID_ANY, _(u"&Más opciones")) + self.menu_1 = wx.Button(self.panel_1, wx.ID_ANY, _("&Más opciones")) self.menu_1.Bind(wx.EVT_BUTTON, self.opcionesMenu) self.menu_1.SetToolTip(_(u"Pulse alt para abrir el menú")) self.notebook_1 = wx.Notebook(self.panel_1, wx.ID_ANY) @@ -121,7 +241,7 @@ def __init__(self, *args, **kwds): self.button_1.Bind(wx.EVT_BUTTON, self.acceder) self.button_1.Disable() sizer_2.Add(self.button_1, 0, 0, 0) - self.button_2 = wx.Button(self.tap_1, wx.ID_ANY, _(u"Borrar")) + self.button_2 = wx.Button(self.tap_1, wx.ID_ANY, _("Borrar")) self.button_2.Bind(wx.EVT_BUTTON, self.borrarContenido) self.button_2.Disable() sizer_2.Add(self.button_2, 0, 0, 0) @@ -137,16 +257,18 @@ def opcionesMenu(self, event): self.opciones = wx.Menu() self.ayuda = wx.Menu() self.menu.AppendSubMenu(self.opciones, _(u"&Opciones")) - self.opcion_1 = self.opciones.Append(wx.ID_ANY, _(u"Configuración")) + self.opcion_1 = self.opciones.Append(wx.ID_ANY, _("Configuración")) self.Bind(wx.EVT_MENU, self.appConfiguracion, self.opcion_1) - self.opcion_2 = self.opciones.Append(wx.ID_ANY, _(u"Restablecer los ajustes")) + self.opcion_2 = self.opciones.Append(wx.ID_ANY, _("Restablecer los ajustes")) self.Bind(wx.EVT_MENU, self.restaurar, self.opcion_2) self.menu.AppendSubMenu(self.ayuda, _("&Ayuda")) - self.apoyo = self.ayuda.Append(wx.ID_ANY, _(u"Únete a nuestra &causa")) + self.apoyo = self.ayuda.Append(wx.ID_ANY, _("Únete a nuestra &causa")) self.Bind(wx.EVT_MENU, self.donativo, self.apoyo) - self.itemPageMain = self.ayuda.Append(wx.ID_ANY, _(u"&Visita nuestra página de github")) + self.itemPageMain = self.ayuda.Append(wx.ID_ANY, _("&Visita nuestra página de github")) self.Bind(wx.EVT_MENU, self.pageMain, self.itemPageMain) - self.acercade = self.menu.Append(wx.ID_ANY, _(u"Acerca de")) + self.actualizador = self.ayuda.Append(wx.ID_ANY, _("&buscar actualizaciones")) + self.Bind(wx.EVT_MENU, self.updater, self.actualizador) + self.acercade = self.menu.Append(wx.ID_ANY, _("Acerca de")) self.Bind(wx.EVT_MENU, self.infoApp, self.acercade) self.salir = self.menu.Append(wx.ID_EXIT) self.Bind(wx.EVT_MENU, self.cerrarVentana, self.salir) @@ -167,18 +289,17 @@ def appConfiguracion(self, event): sizer_4 = wx.BoxSizer(wx.HORIZONTAL) box_1 = wx.StaticBox(self.treeItem_1, -1, _("Opciones de la app")) boxSizer_1 = wx.StaticBoxSizer(box_1,wx.VERTICAL) + label_language = wx.StaticText(self.treeItem_1, wx.ID_ANY, _("Idioma de VeTube (Requiere reiniciar)")) + boxSizer_1.Add(label_language) + self.choice_language = wx.Choice(self.treeItem_1, wx.ID_ANY, choices=langs) + self.choice_language.SetSelection(codes.index(idioma)) + boxSizer_1.Add(self.choice_language) label_7 = wx.StaticText(self.treeItem_1, wx.ID_ANY, _("Modalidad del chat: ")) boxSizer_1.Add(label_7) self.choice_3 = wx.Choice(self.treeItem_1, wx.ID_ANY, choices=[_("Todos los chats"), _("solo chats de miembros y donativos.")]) self.choice_3.Bind(wx.EVT_CHOICE, self.cambiarModoChat) self.choice_3.SetSelection(configchat-1) boxSizer_1.Add(self.choice_3) - label_3 = wx.StaticText(self.treeItem_1, wx.ID_ANY, _("tamaño del bufer de mensajes: ")) - boxSizer_1.Add(label_3) - self.choice_4 = wx.Choice(self.treeItem_1, wx.ID_ANY, choices=["100","300","500","1000"]) - self.choice_4.Bind(wx.EVT_CHOICE, self.cambiarMensajes) - self.choice_4.SetSelection(mensajes) - boxSizer_1.Add(self.choice_4) self.check_2 = wx.CheckBox(self.treeItem_1, wx.ID_ANY, _("Reproducir sonidos.")) self.check_2.SetValue(sonidos) self.check_2.Bind(wx.EVT_CHECKBOX, self.reproducirSonidos) @@ -257,10 +378,6 @@ def checar(self, event): global sapy if event.IsChecked(): sapy=True else: sapy=False - def cambiarMensajes(self, event): - global mensajes,buffer - mensajes=self.choice_4.GetSelection() - buffer=int(self.choice_4.GetString(self.choice_4.GetSelection())) def acceder(self, event): if self.text_ctrl_1.GetValue() == "": wx.MessageBox(_("No se puede acceder porque el campo de texto está vacío, debe escribir algo."), "error.", wx.ICON_ERROR) @@ -271,14 +388,10 @@ def acceder(self, event): pag=pag.split("/") pag=pag[-2] self.text_ctrl_1.SetValue("https://www.youtube.com/watch?v="+pag) - try: chat = ChatDownloader().get_chat(self.text_ctrl_1.GetValue()) - except Exception as e: - wx.MessageBox(_("¡Parece que el enlace al cual está intentando acceder no es un enlace válido."+str(e)), "error.", wx.ICON_ERROR) - return try: self.contador=0 self.contarmiembros=0 - self.chat = pytchat.create(self.text_ctrl_1.GetValue(),interruptable=False) + self.chat=ChatDownloader().get_chat(self.text_ctrl_1.GetValue(),message_groups=["messages", "superchat"]) self.dentro=True self.dialog_mensaje = wx.Dialog(self, wx.ID_ANY, _("Chat en vivo")) sizer_mensaje_1 = wx.BoxSizer(wx.VERTICAL) @@ -308,14 +421,16 @@ def acceder(self, event): self.hilo2 = threading.Thread(target=self.iniciarChat) self.hilo2.daemon = True self.hilo2.start() - if not self.hilo1.is_alive(): self.hilo1.start() + self.hilo1 = threading.Thread(target=self.capturarTeclas) + self.hilo1.daemon = True + self.hilo1.start() self.dialog_mensaje.ShowModal() except Exception as e: wx.MessageBox(_("¡Parece que el enlace al cual está intentando acceder no es un enlace válido."), "error.", wx.ICON_ERROR) self.text_ctrl_1.SetFocus() def borrarContenido(self, event): self.text_ctrl_1.SetValue("") def detenerLectura(self, event): - dlg_mensaje = wx.MessageDialog(self.dialog_mensaje, _(u"¿Desea salir de esta ventana y detener la lectura de los mensajes?"), _(u"Atención:"), wx.YES_NO | wx.ICON_ASTERISK) + dlg_mensaje = wx.MessageDialog(self.dialog_mensaje, _("¿Desea salir de esta ventana y detener la lectura de los mensajes?"), _("Atención:"), wx.YES_NO | wx.ICON_ASTERISK) if dlg_mensaje.ShowModal() == wx.ID_YES: self.dentro=False leer.silence() @@ -323,17 +438,26 @@ def detenerLectura(self, event): self.text_ctrl_1.SetFocus() leer.speak(_("ha finalizado la lectura del chat.")) def guardar(self, event): + global idioma, rest + rest=False + if idioma!=codes[self.choice_language.GetSelection()]: + idioma=codes[self.choice_language.GetSelection()] + rest=True escribirConfiguracion() + if rest: + dlg = wx.MessageDialog(None, _("Es necesario reiniciar el programa para aplicar el nuevo idioma. ¿desea reiniciarlo ahora?"), _("Atención:"), wx.YES_NO | wx.ICON_ASTERISK) + if dlg.ShowModal()==wx.ID_YES: restart.restart_program() + else: dlg.Destroy() self.dialogo_2.Close() def borrarEnlace(self,event): - dlg_2 = wx.MessageDialog(self.dialog_mensaje, _(u"Está apunto de eliminar del historial aproximadamente ")+str(self.list_box_1.GetCount())+_(" elementos, ¿desea proceder? Esta acción no se puede desacer."), _(u"Atención:"), wx.YES_NO | wx.ICON_ASTERISK) + dlg_2 = wx.MessageDialog(self.dialog_mensaje, _("Está apunto de eliminar del historial aproximadamente ")+str(self.list_box_1.GetCount())+_(" elementos, ¿desea proceder? Esta acción no se puede desacer."), _("Atención:"), wx.YES_NO | wx.ICON_ASTERISK) dlg_2.SetYesNoLabels(_("&Eliminar"), _("&Cancelar")) if self.list_box_1.GetCount() <= 0: wx.MessageBox(_("No hay elementos que borrar"), "Error", wx.ICON_ERROR) elif dlg_2.ShowModal()==wx.ID_YES: self.list_box_1.Clear() self.list_box_1.SetFocus() def restaurar(self, event): - self.dlg_3 = wx.MessageDialog(self, _(u"Estás apunto de reiniciar la configuración a sus valores predeterminados, ¿Deseas proceder?"), _(u"Atención:"), wx.YES_NO | wx.ICON_ASTERISK) + self.dlg_3 = wx.MessageDialog(self, _("Estás apunto de reiniciar la configuración a sus valores predeterminados, ¿Deseas proceder?"), _("Atención:"), wx.YES_NO | wx.ICON_ASTERISK) if self.dlg_3.ShowModal()==wx.ID_YES: asignarConfiguracion() escribirConfiguracion() @@ -363,45 +487,63 @@ def iniciarChat(self): with YoutubeDL(ydlop) as ydl: info_dict = ydl.extract_info(self.text_ctrl_1.GetValue(), download=False) self.label_dialog.SetLabel(info_dict.get('title')+', '+str(info_dict["view_count"])+_(' reproducciones')) self.text_ctrl_1.SetValue("") - while self.chat.is_alive(): - for c in self.chat.get().sync_items(): - if c.type == "superChat" or c.type == "superSticker": - miembros.append(f"{c.currency}{c.amountString}, {c.author.name}: {c.message}") - self.list_box_1.Append(f"{c.currency}{c.amountString}, {c.author.name}: {c.message}") - if sonidos: playsound("sounds/donar.mp3",False) - if sapy: leer.speak(f"{c.currency}{c.amountString}, {c.author.name}: {c.message}") - elif c.type == "newSponsor": - miembros.append(_("Nuevo Miembro: %s")%c.author.name) - self.list_box_1.Append(_("Nuevo Miembro: %s")%c.author.name) - if sonidos: playsound("sounds/miembros.mp3",False) - if sapy: leer.speak(_("Nuevo Miembro: %s")%c.author.name) - elif c.author.isChatSponsor: - miembros.append(_("Miembro %s: %s")%(c.author.name,c.message)) - self.list_box_1.Append(_("Miembro %s: %s")%(c.author.name,c.message)) - if sapy: leer.speak(_("Miembro %s: %s")%(c.author.name,c.message)) + for message in self.chat: + if message['message_type']=='paid_message' or message['message_type']=='paid_sticker': + if message['message']!=None: + miembros.append(str(message['money']['amount'])+message['money']['currency']+ ', '+message['author']['name'] +': ' +message['message']) + self.list_box_1.Append(str(message['money']['amount'])+message['money']['currency']+ ', '+message['author']['name'] +': ' +message['message']) + if sapy: leer.speak(str(message['money']['amount'])+message['money']['currency']+ ', '+message['author']['name'] +': ' +message['message']) + else: + miembros.append(str(message['money']['amount'])+message['money']['currency']+ ', '+message['author']['name']) + self.list_box_1.Append(str(message['money']['amount'])+message['money']['currency']+ ', '+message['author']['name']) + if sapy: leer.speak(str(message['money']['amount'])+message['money']['currency']+ ', '+message['author']['name']) + if sonidos and self.chat.status!="past": playsound("sounds/donar.mp3",False) + if 'header_secondary_text' in message: + for t in message['author']['badges']: + miembros.append(message['author']['name']+ _(' se a conectado al chat. ')+t['title']) + self.list_box_1.Append(message['author']['name']+ _(' se a conectado al chat. ')+t['title']) + if sonidos and self.chat.status!="past": playsound("sounds/miembros.mp3",False) + if sapy: leer.speak(message['author']['name']+ _(' se a conectado al chat. ')+t['title']) + if 'badges' in message['author']: + for t in message['author']['badges']: + if 'Moderator' in t['title']: + miembros.append(_('Moderador ')+message['author']['name'] +': ' +message['message']) + self.list_box_1.Append(_('Moderador ')+message['author']['name'] +': ' +message['message']) + if sapy: leer.speak(_('Moderador ')+message['author']['name'] +': ' +message['message']) + if 'Member' in t['title']: + if message['message'] == None: pass + else: + miembros.append(_('Miembro ')+message['author']['name'] +': ' +message['message']) + self.list_box_1.Append(_('Miembro ')+message['author']['name'] +': ' +message['message']) + if sapy: leer.speak(_('Miembro ')+message['author']['name'] +': ' +message['message']) + if 'Verified' in t['title']: + miembros.append(message['author']['name'] +_(' (usuario verificado): ') +message['message']) + self.list_box_1.Append(message['author']['name'] +_(' (usuario verificado): ') +message['message']) + if sapy: leer.speak(message['author']['name'] +_(' (usuario verificado): ') +message['message']) + if sonidos and self.chat.status!="past": playsound("sounds/chatmiembro.mp3",False) + else: + if message['message_type']=='paid_message' or message['message_type']=='paid_sticker': pass else: - if self.dentro: self.list_box_1.Append(f"{c.author.name}: {c.message}") + if self.dentro: + if configchat==1: + if sapy: leer.speak(message['author']['name'] +': ' +message['message']) + if sonidos and self.chat.status!="past": playsound("sounds/chat.mp3",False) + self.list_box_1.Append(message['author']['name'] +': ' +message['message']) else: exit() self.hilo2.join() - if configchat==1: - if sapy: leer.speak(f"{c.author.name}: {c.message}") - if self.list_box_1.GetCount()> buffer: self.list_box_1.Delete(0) - if len(miembros)> buffer: miembros.pop(0) def elementoAnterior(self): if self.dentro: if todos: if self.list_box_1.GetCount() <= 0: lector.speak(_("no hay elementos en el historial")) else: if self.contador>0: self.contador-=1 - self.msg=(self.list_box_1.GetString(self.contador)) - lector.speak(self.msg) + lector.speak(self.list_box_1.GetString(self.contador)) else: if len(miembros) <= 0: lector.speak(_("no hay elementos en el historial")) else: if self.contarmiembros>0: self.contarmiembros-=1 - self.msg=(miembros[self.contarmiembros]) - lector.speak(self.msg) + lector.speak(miembros[self.contarmiembros]) if sonidos: self.reproducirMsg() def elementoSiguiente(self): if self.dentro: @@ -409,33 +551,37 @@ def elementoSiguiente(self): if self.list_box_1.GetCount() <= 0: lector.speak(_("no hay elementos en el historial")) else: if self.contador0 and todos: return self.list_box_1.GetString(self.contador) elif todos==False and len(miembros)>0: return miembros[self.contarmiembros] @@ -506,12 +650,28 @@ def reproducirMsg(self): else: if self.contarmiembros==0 or self.contarmiembros==len(miembros)-1: playsound("sounds/orilla.mp3",False) else: playsound("sounds/msj.mp3",False) + def updater(self,event): + r = urllib.request.urlopen('https://api.github.com/repos/metalalchemist/VeTube/releases').read() + gitJson = json.loads(r.decode('utf-8')) + if gitJson[0]["tag_name"] != version: + dlg = wx.MessageDialog(None, _("Una nueva versión de VeTube está disponible. ¿desea descargarla aora?"), _("Atención:"), wx.YES_NO | wx.ICON_ASTERISK) + if dlg.ShowModal()==wx.ID_YES: + global urlDescarga + urlDescarga = gitJson[0]['body'] + urlDescarga=urlDescarga.split('](') + urlDescarga=urlDescarga[1] + urlDescarga=urlDescarga.split(')') + urlDescarga=urlDescarga[0] + dlg.Destroy + dlg = ActualizacionDialogo(self) + result = dlg.ShowModal() + else: dlg.Destroy() + else: wx.MessageBox(_("Al parecer tienes la última versión del programa"), _("Información"), wx.ICON_INFORMATION) class MyApp(wx.App): def OnInit(self): self.frame = MyFrame(None, wx.ID_ANY, "") self.SetTopWindow(self.frame) self.frame.Show() return True -# end of class MyApp app = MyApp(0) app.MainLoop() \ No newline at end of file diff --git a/changelog.txt b/changelog.txt index 50603cd..a32aee3 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,10 +1,18 @@ V1.2 -corregido el que la interfaz se congelara al momento de ingresar al chat. +(programadores): se agrega el catálogo de VeTube (lenguage original en españñol) para poderse traducir a varios idiomas. el archivo se llama VeTube.pot. +(programadores): se quita pytchat para reemplazarlo con chat_downloader. esto para soporte de varias plataformas. +ahora se escoge el idioma que tienes por defecto en tu configuración. +si el programa está actualizado ahora VeTube avisará si tienes la última versión del mismo si buscas actualizaciones. +se agregan sonidos que avisan cuando llega un chat tanto como de un miembro como un chat normal +se agrega un actualizador. comprueba una actualización al iniciar. pero también se lo puede encontrar en mas opciones & ayuda. +se quitó el límite de mensajes +experimental: VeTube puede detectar el idioma del sistema y si hay un catálogo disponible, lo cargará. +el programa no iniciaba en algunas ocaciones por lo que cada ves que se abra el mismo se borran los archivos temporales de %temp/gempy +corregido el que la interfaz invisible se congelara al momento de ingresar al chat. corregido el no poder ir al inicio del chat en la lista principal. agregado sonidos al navegar por el chat y al llegar al borde, gracias Glein Agregamos un sonido para indicar que se a ingresado al chat. gracias Johan G. Agregado el fácil acceso a nuestra página de github para poder colaborar con nosotros desde github, así como también una opción de apoyo en la sección de ayuda. gracias Johan G -Ahora el mensaje se copia de manera precisa. VeTube ahora reconoce un link de YouTube Studio cuando es en vivo, por lo cual ya no es necesario sacar el link de youtube *** v1.1 diff --git a/doc/en/readme-en.md b/doc/en/readme-en.md index 08d81c9..eee68b4 100644 --- a/doc/en/readme-en.md +++ b/doc/en/readme-en.md @@ -4,13 +4,13 @@ Read and manage youtube chat in an accessible way in your own live shows or thos - Automatic mode: Read chat messages in real time using the sapy5 voice - Invisible interface: Manage chats from any window using simple key commands. It is necessary to have an active screen reader. - Supported readers: -- NVDA -- JAWS -- Window-Eyes -- SuperNova -- System Access -- PC Talker -- ZDSR + - NVDA + - JAWS + - Window-Eyes + - SuperNova + - System Access + - PC Talker + - ZDSR - Possibility of configuring according to the user's needs. - activates or deactivates the sounds of the program. - Turn automatic mode on or off. @@ -30,8 +30,9 @@ Read and manage youtube chat in an accessible way in your own live shows or thos |Copy the current message | alt shift c | |Turn on or off automatic mode | alt shift m | |Shows the current message in a text box |alt shift v | -| Displays information about the user who sent the message | alt shift i | + ### In the chat history: |action |key combination | | ------------------------- | ------------- | -|Play selected message |space \ No newline at end of file +|Play selected message |space +| Delete selected message | Delete | diff --git a/doc/es/readme.md b/doc/es/readme.md index 11eaef9..e33ed7c 100644 --- a/doc/es/readme.md +++ b/doc/es/readme.md @@ -40,8 +40,9 @@ Lee y gestiona de manera accesible el chat de youtube en tus propios directos o ## futuras actualizaciones: He agregado para futuras actualizaciones próximas -- el poder traducir facilmente la aplicación a otros idiomas. -- Posibbilidad de mostrar información de la persona que está chateando desde la interfaz invisible: +- la creación de distintas categorías de mensajes en la interfaz invisible. + - poder ordenar el contenido por distintas insignias tales como donativos, miembros moderadores. +- Posibilidad de mostrar información de la persona que está chateando desde la interfaz invisible: - El usuario es moderador? - Nombre del canal del usuario - Entre muchas cosas mas. diff --git a/languageHandler.py b/languageHandler.py new file mode 100644 index 0000000..10d71de --- /dev/null +++ b/languageHandler.py @@ -0,0 +1,172 @@ +from __future__ import unicode_literals +from future import standard_library +standard_library.install_aliases() +from builtins import zip +from builtins import str +import builtins +import os +import sys +import ctypes +import locale +import gettext +import platform +#a few Windows locale constants +LOCALE_SLANGUAGE=0x2 +LOCALE_SLANGDISPLAYNAME=0x6f +curLang="en" +def localeNameToWindowsLCID(localeName): + """Retreave the Windows locale identifier (LCID) for the given locale name + @param localeName: a string of 2letterLanguage_2letterCountry or or just 2letterLanguage + @type localeName: string + @returns: a Windows LCID + @rtype: integer + """ + #Windows Vista is able to convert locale names to LCIDs + func_LocaleNameToLCID=getattr(ctypes.windll.kernel32,'LocaleNameToLCID',None) + if func_LocaleNameToLCID is not None: + localeName=localeName.replace('_','-') + LCID=func_LocaleNameToLCID(str(localeName),0) + else: #Windows doesn't have this functionality, manually search Python's windows_locale dictionary for the LCID + localeName=locale.normalize(localeName) + if '.' in localeName: + localeName=localeName.split('.')[0] + LCList=[x[0] for x in locale.windows_locale.items() if x[1]==localeName] + if len(LCList)>0: + LCID=LCList[0] + else: + LCID=0 + return LCID + +def getLanguageDescription(language): + """Finds out the description (localized full name) of a given local name""" + desc=None + if platform.system() == "Windows": + LCID=localeNameToWindowsLCID(language) + if LCID!=0: + buf=ctypes.create_unicode_buffer(1024) + if '_' not in language: + res=ctypes.windll.kernel32.GetLocaleInfoW(LCID,LOCALE_SLANGDISPLAYNAME,buf,1024) + else: + res=0 + if res==0: + res=ctypes.windll.kernel32.GetLocaleInfoW(LCID,LOCALE_SLANGUAGE,buf,1024) + desc=buf.value + return desc +def getAvailableLanguages(): + """generates a list of locale names, plus their full localized language and country names. + @rtype: list of tuples + """ + #Make a list of all the locales found in NVDA's locale dir + l=[x for x in os.listdir('locales') if not x.startswith('.')] + l=[x for x in l if os.path.isfile(os.path.join('locales', '%s/LC_MESSAGES/%s.po' % (x, 'VeTube')))] + #Make sure that en (english) is in the list as it may not have any locale files, but is default + if 'en' not in l: + l.append('en') + l.sort() + #For each locale, ask Windows for its human readable display name + d=[] + for i in l: + desc=getLanguageDescription(i) + label="%s, %s"%(desc,i) if desc else i + d.append(label) + #include a 'user default, windows' language, which just represents the default language for this user account + l.append("system") + # Translators: the label for the Windows default NVDA interface language. + d.append(_("Idioma del sistema")) + #return a zipped up version of both the lists (a list with tuples of locale,label) + return list(zip(l,d)) + +def makePgettext(translations): + """Obtaina pgettext function for use with a gettext translations instance. + pgettext is used to support message contexts, + but Python 2.7's gettext module doesn't support this, + so NVDA must provide its own implementation. + """ + if isinstance(translations, gettext.GNUTranslations): + def pgettext(context, message): + message = str(message) + try: + # Look up the message with its context. + return translations._catalog["%s\x04%s" % (context, message)] + except KeyError: + return message + else: + def pgettext(context, message): + return str(message) + return pgettext + +def setLanguage(lang): + system = platform.system() + global curLang + try: + if lang=="system": + if system == "Windows": + windowsLCID=ctypes.windll.kernel32.GetUserDefaultUILanguage() + localeName=locale.windows_locale[windowsLCID] + trans=gettext.translation('VeTube', localedir='locales', languages=[localeName]) + curLang=localeName + else: + trans=gettext.translation('VeTube', localedir='locales', languages=[lang]) + curLang=lang + localeChanged=False + if system == "Windows": + locale.setlocale(locale.LC_ALL, langToWindowsLocale(lang)) + localeChanged=True + else: + locale.setlocale(locale.LC_ALL, lang) + localeChanged=True + if not localeChanged and '_' in lang: + try: + locale.setlocale(locale.LC_ALL, lang.split('_')[0]) + except: + pass + if system == "Windows": + LCID=localeNameToWindowsLCID(lang) + ctypes.windll.kernel32.SetThreadLocale(LCID) + except IOError: + trans=gettext.translation('VeTube', fallback=True) + curLang="en" + if sys.version[0] == "3": + trans.install() + else: + trans.install(unicode=True) + +def getLanguage(): + return curLang + +def normalizeLanguage(lang): + """ + Normalizes a language-dialect string in to a standard form we can deal with. + Converts any dash to underline, and makes sure that language is lowercase and dialect is upercase. + """ + lang=lang.replace('-','_') + ld=lang.split('_') + ld[0]=ld[0].lower() + #Filter out meta languages such as x-western + if ld[0]=='x': + return None + if len(ld)>=2: + ld[1]=ld[1].upper() + return "_".join(ld) + +def langToWindowsLocale(lang): + languages = {"en": "eng", + "ar": "ara", + "ca": "cat", + "de": "deu", + "es": "esp", + "fi": "fin", + "fr": "fre_FRA", + "gl": "glc", + "eu": "euq", + "hu": "hun", + "hr": "hrv", + "it": "ita", + "ja": "jpn", + "pl": "plk", + "pt": "ptb", + "ru": "rus", + "tr": "trk", + "sr": "eng", + } + return languages[lang] diff --git a/locales/en/LC_MESSAGES/VeTube by UlisesMonge.po b/locales/en/LC_MESSAGES/VeTube by UlisesMonge.po new file mode 100644 index 0000000..54316c2 --- /dev/null +++ b/locales/en/LC_MESSAGES/VeTube by UlisesMonge.po @@ -0,0 +1,352 @@ +# English translations for PROJECT. +# Copyright (C) 2022 ORGANIZATION +# This file is distributed under the same license as the PROJECT project. +# FIRST AUTHOR , 2022. +# +msgid "" +msgstr "" +"Project-Id-Version: PROJECT VERSION\n" +"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" +"POT-Creation-Date: 2022-05-19 00:27-0700\n" +"PO-Revision-Date: 2022-05-19 00:28-0700\n" +"Last-Translator: FULL NAME \n" +"Language: en\n" +"Language-Team: en \n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.10.1\n" + +#: chat/languageHandler.py:75 +msgid "Idioma del sistema" +msgstr "System language" + +#: chat/vetube.py:108 chat/vetube.py:112 +msgid "Espere por favor...\n" +msgstr "Please wait..." + +#: chat/vetube.py:108 chat/vetube.py:112 +#, python-format +msgid "Descargando: %s" +msgstr "Downloading: %s" + +#: chat/vetube.py:123 +msgid "" +"Se terminó la descarga de la nueva actualización.\n" +"el programa se cerrará para que usted reemplase los archivos de la nueva actualización.\n" +"¿disfrute!" +msgstr "" +"the New update was downloaded.\n" +"the program will be closed for replase new files.\n" +"¿enjoy!" + +#: chat/vetube.py:126 +msgid "" +"Algo salió mal.\n" +"Compruebe que tiene conexión a internet y vuelva a intentarlo.\n" +"el programa se cerrará." +msgstr "" +"Error.\n" +"Check internet connection and try again.\n" +"the program will be closed." + +#: chat/vetube.py:132 +msgid "Actualizando el programa..." +msgstr "Updating the program" + +#: chat/vetube.py:139 chat/vetube.py:209 chat/vetube.py:344 +msgid "&Aceptar" +msgstr "&OK" + +#: chat/vetube.py:142 chat/vetube.py:639 +msgid "&Cerrar" +msgstr "&Close" + +#: chat/vetube.py:196 chat/vetube.py:657 +msgid "Una nueva versión de VeTube está disponible. ¿desea descargarla aora?" +msgstr "A new version of Vetube is available. do you want to download it now?" + +#: chat/vetube.py:196 chat/vetube.py:208 chat/vetube.py:433 chat/vetube.py:448 +#: chat/vetube.py:453 chat/vetube.py:460 chat/vetube.py:657 +msgid "Atención:" +msgstr "¡alert!" + +#: chat/vetube.py:208 +msgid "Con tu apoyo contribuyes a que este programa siga siendo gratuito. ¿Te unes a nuestra causa?" +msgstr "With your support you contribute to keep this program will be free. do you want to join to our cause?" + +#: chat/vetube.py:209 chat/vetube.py:342 chat/vetube.py:454 +msgid "&Cancelar" +msgstr "&Cancel" + +#: chat/vetube.py:224 +msgid "&Más opciones" +msgstr "&more options" + +#: chat/vetube.py:226 +msgid "Pulse alt para abrir el menú" +msgstr "Press alt to open the menu" + +#: chat/vetube.py:230 +msgid "Inicio" +msgstr "Home" + +#: chat/vetube.py:232 +msgid "Escriba o pegue una URL de youtube" +msgstr "Write or paste a youtube URL" + +#: chat/vetube.py:235 +msgid "Escribe o pega una URL" +msgstr "Write or paste an URL" + +#: chat/vetube.py:240 +msgid "&Acceder" +msgstr "&Enter" + +#: chat/vetube.py:244 +msgid "Borrar" +msgstr "Delete" + +#: chat/vetube.py:259 +msgid "&Opciones" +msgstr "&Options" + +#: chat/vetube.py:260 chat/vetube.py:281 +msgid "Configuración" +msgstr "Settings" + +#: chat/vetube.py:262 +msgid "Restablecer los ajustes" +msgstr "Set settings to default options" + +#: chat/vetube.py:264 +msgid "&Ayuda" +msgstr "&Help" + +#: chat/vetube.py:265 +msgid "Únete a nuestra &causa" +msgstr "Join to our &cause" + +#: chat/vetube.py:267 +msgid "&Visita nuestra página de github" +msgstr "&Visit our github page" + +#: chat/vetube.py:269 +msgid "&buscar actualizaciones" +msgstr "&Search for updates" + +#: chat/vetube.py:271 +msgid "Acerca de" +msgstr "&abouth" + +#: chat/vetube.py:283 +msgid "Categorías" +msgstr "Categories" + +#: chat/vetube.py:288 +msgid "General" +msgstr "General" + +#: chat/vetube.py:290 +msgid "Opciones de la app" +msgstr "App options" + +#: chat/vetube.py:292 +msgid "Idioma de VeTube (Requiere reiniciar)" +msgstr "VeTube Language (requires restart)" + +#: chat/vetube.py:297 +msgid "Modalidad del chat: " +msgstr "Chat mode" + +#: chat/vetube.py:299 +msgid "Todos los chats" +msgstr "All chats" + +#: chat/vetube.py:299 +msgid "solo chats de miembros y donativos." +msgstr "Members and donattors" + +#: chat/vetube.py:303 +msgid "Reproducir sonidos." +msgstr "play sounds" + +#: chat/vetube.py:309 +msgid "Voz" +msgstr "Voice" + +#: chat/vetube.py:311 +msgid "Opciones del habla" +msgstr "Speak options" + +#: chat/vetube.py:313 +msgid "Voz sapy" +msgstr "Sapy voice" + +#: chat/vetube.py:317 +msgid "Voz: " +msgstr "Voice" + +#: chat/vetube.py:323 +msgid "Tono: " +msgstr "Pitch" + +#: chat/vetube.py:328 +msgid "Volumen: " +msgstr "Volume" + +#: chat/vetube.py:333 +msgid "Velocidad: " +msgstr "Speed" + +#: chat/vetube.py:338 +msgid "&Reproducir prueba." +msgstr "&Play test" + +#: chat/vetube.py:357 +msgid "Hola, soy la voz que te acompañará de ahora en adelante a leer los mensajes de tus canales favoritos." +msgstr "hi, i'm the voice that will read the messages of your favorites chanels" + +#: chat/vetube.py:358 +msgid "Creadores del proyecto:" +msgstr "project creators:" + +#: chat/vetube.py:358 +msgid "" +"Descripción:\n" +" Lee en voz alta los mensajes de los directos en youtube, ajusta tus preferencias como quieras y disfruta más tus canales favoritos." +msgstr "" +"Description:\n" +"Read aloud the messages of live broadcasts on youtube, adjust your preferences as you wish and enjoy your favorite channels more." + +#: chat/vetube.py:358 chat/vetube.py:669 +msgid "Información" +msgstr "Information" + +#: chat/vetube.py:383 +msgid "No se puede acceder porque el campo de texto está vacío, debe escribir algo." +msgstr "Cannot access because the text field is empty, you must type something." + +#: chat/vetube.py:396 +msgid "Chat en vivo" +msgstr "live chat" + +#: chat/vetube.py:398 +msgid "Lectura del chat en vivo..." +msgstr "live chat reading..." + +#: chat/vetube.py:402 +msgid "historial de mensajes: " +msgstr "message history" + +#: chat/vetube.py:408 +msgid "Limpiar historial" +msgstr "Clear history" + +#: chat/vetube.py:411 +msgid "&Detener" +msgstr "&Stop" + +#: chat/vetube.py:420 +msgid "Ingresando al chat." +msgstr "Entering to the chat" + +#: chat/vetube.py:429 +msgid "¡Parece que el enlace al cual está intentando acceder no es un enlace válido." +msgstr "It looks like the link you are trying to access is not a valid link." + +#: chat/vetube.py:433 +msgid "¿Desea salir de esta ventana y detener la lectura de los mensajes?" +msgstr "Do you want to exit this window and stop reading messages?" + +#: chat/vetube.py:439 +msgid "ha finalizado la lectura del chat." +msgstr "chat reading was ended" + +#: chat/vetube.py:448 +msgid "Es necesario reiniciar el programa para aplicar el nuevo idioma. ¿desea reiniciarlo ahora?" +msgstr "Is necessary restart the program to apply the new language. do you want to restart it now?" + +#: chat/vetube.py:453 +msgid "Está apunto de eliminar del historial aproximadamente " +msgstr "You are about to remove from history approximately " + +#: chat/vetube.py:453 +msgid " elementos, ¿desea proceder? Esta acción no se puede desacer." +msgstr " elements, do you want to proceed? This action cannot be undone." + +#: chat/vetube.py:454 +msgid "&Eliminar" +msgstr "&Delete" + +#: chat/vetube.py:455 +msgid "No hay elementos que borrar" +msgstr "There are no items to delete" + +#: chat/vetube.py:460 +msgid "Estás apunto de reiniciar la configuración a sus valores predeterminados, ¿Deseas proceder?" +msgstr "You are about to reset the settings to their default values, do you want to proceed?" + +#: chat/vetube.py:488 +msgid " reproducciones" +msgstr "Views" + +#: chat/vetube.py:503 chat/vetube.py:504 chat/vetube.py:506 +msgid " se a conectado al chat. " +msgstr "Entered to the chat. " + +#: chat/vetube.py:510 chat/vetube.py:511 chat/vetube.py:512 +msgid "Moderador " +msgstr "Moderator " + +#: chat/vetube.py:516 chat/vetube.py:517 chat/vetube.py:518 +msgid "Miembro " +msgstr "Member " + +#: chat/vetube.py:520 chat/vetube.py:521 chat/vetube.py:522 +msgid " (usuario verificado): " +msgstr " (Verified user): " + +#: chat/vetube.py:538 chat/vetube.py:543 chat/vetube.py:551 chat/vetube.py:556 +#: chat/vetube.py:564 chat/vetube.py:569 chat/vetube.py:576 chat/vetube.py:581 +#: chat/vetube.py:589 chat/vetube.py:594 +msgid "no hay elementos en el historial" +msgstr "no items in history" + +#: chat/vetube.py:567 chat/vetube.py:572 +msgid "¡Copiado!" +msgstr "¡Copied!" + +#: chat/vetube.py:607 +msgid "Todos los chats." +msgstr "All chats" + +#: chat/vetube.py:607 +msgid "Miembros y donativos." +msgstr "Members and donattors" + +#: chat/vetube.py:614 +msgid "Voz activada." +msgstr "Voice on" + +#: chat/vetube.py:614 +msgid "Voz desactivada." +msgstr "Voice off" + +#: chat/vetube.py:628 +msgid "Are you sure you want to exit the program?" +msgstr "" + +#: chat/vetube.py:628 +msgid "¡atención!:" +msgstr "¡Alert!" + +#: chat/vetube.py:635 +msgid "mensaje" +msgstr "message" + +#: chat/vetube.py:669 +msgid "Al parecer tienes la última versión del programa" +msgstr "Al parecer tienes la última versión del programa" + diff --git a/locales/en/LC_MESSAGES/VeTube.mo b/locales/en/LC_MESSAGES/VeTube.mo new file mode 100644 index 0000000..28bdcb1 Binary files /dev/null and b/locales/en/LC_MESSAGES/VeTube.mo differ diff --git a/requirements.txt b/requirements.txt index 42838e8..2ed3698 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,5 @@ wx playsound==1.2.2 -pytchat youtube_dl keyboard accessible_output2 diff --git a/restart.py b/restart.py new file mode 100644 index 0000000..a78aef6 --- /dev/null +++ b/restart.py @@ -0,0 +1,15 @@ +# -*- coding: cp1252 +from __future__ import unicode_literals +import sys, os + +def restart_program(): + """ Function that restarts the application if is executed.""" + args = sys.argv[:] + if not hasattr(sys, "frozen"): + args.insert(0, sys.executable) + if sys.platform == 'win32': + args = ['"%s"' % arg for arg in args] + pidpath = os.path.join(os.getenv("temp"), "{}.pid".format('VeTube')) + if os.path.exists(pidpath): + os.remove(pidpath) + os.execv(sys.executable, args) diff --git a/sounds/chat.mp3 b/sounds/chat.mp3 new file mode 100644 index 0000000..c129b1e Binary files /dev/null and b/sounds/chat.mp3 differ diff --git a/sounds/chatmiembro.mp3 b/sounds/chatmiembro.mp3 new file mode 100644 index 0000000..dee1c0b Binary files /dev/null and b/sounds/chatmiembro.mp3 differ