diff --git a/.github/workflows/betube32.yml b/.github/workflows/betube32.yml new file mode 100644 index 0000000..5e8dd87 --- /dev/null +++ b/.github/workflows/betube32.yml @@ -0,0 +1,69 @@ +name: VeTube-x86 + +on: + push: + tags: ["*"] + branches: [ master , piper ] + pull_request: + branches: [ master , piper ] + workflow_dispatch: + +jobs: + build: + runs-on: windows-latest + + steps: + - name: Source checkout + uses: actions/checkout@v3 + + - name: Configure Python + uses: actions/setup-python@v4 + with: + python-version: 3.10.11 + architecture: x86 + + - name: Install dependencies + run: | + pip install --upgrade pip wheel setuptools + pip install -r requirements.txt + pip install pyinstaller gdown + pip install --upgrade pyzmq httpx httpcore future + git clone https://github.com/mush42/espeak-phonemizer-windows + + - name: Compiling + run: | + pyinstaller VeTube.py + gdown 1ZtF6zus0A7kC9Lwr_kTUbw0MiOoZq29H -O dist/VeTube/bootstrap.exe + cp -R doc dist/VeTube/ + cp -R locales dist/VeTube/ + cp -R readme dist/VeTube/ + cp -R sounds dist/VeTube/ + cp -R espeak-phonemizer-windows/espeak_phonemizer dist/VeTube/ + + - name: Create zip + run: | + cd dist + 7z a ../VeTube-x86.zip VeTube/ + cd .. + + - name: Upload zip + uses: actions/upload-artifact@v3 + with: + name: VeTube-x86 + path: dist + if-no-files-found: error + + vetube_release: + runs-on: windows-latest + if: ${{ startsWith(github.ref, 'refs/tags/') }} + needs: ["build"] + steps: + - uses: actions/checkout@v3 + - name: download + uses: actions/download-artifact@v3 + - name: Release + uses: softprops/action-gh-release@v1 + with: + files: VeTube-x86.zip + fail_on_unmatched_files: true + prerelease: ${{ contains(github.ref, '-') }} diff --git a/.github/workflows/betube64.yml b/.github/workflows/betube64.yml new file mode 100644 index 0000000..93bd48a --- /dev/null +++ b/.github/workflows/betube64.yml @@ -0,0 +1,69 @@ +name: VeTube-x64 + +on: + push: + tags: ["*"] + branches: [ master , piper ] + pull_request: + branches: [ master , piper ] + workflow_dispatch: + +jobs: + build: + runs-on: windows-latest + + steps: + - name: Source checkout + uses: actions/checkout@v3 + + - name: Configure Python + uses: actions/setup-python@v4 + with: + python-version: 3.10.11 + architecture: x64 + + - name: Install dependencies + run: | + pip install --upgrade pip wheel setuptools + pip install -r requirements.txt + pip install pyinstaller gdown + pip install --upgrade pyzmq httpx httpcore future + git clone https://github.com/mush42/espeak-phonemizer-windows + + - name: Compiling + run: | + pyinstaller VeTube.py + gdown 1ZtF6zus0A7kC9Lwr_kTUbw0MiOoZq29H -O dist/VeTube/bootstrap.exe + cp -R doc dist/VeTube/ + cp -R locales dist/VeTube/ + cp -R readme dist/VeTube/ + cp -R sounds dist/VeTube/ + cp -R espeak-phonemizer-windows/espeak_phonemizer dist/VeTube/ + + - name: Create zip + run: | + cd dist + 7z a ../VeTube-x64.zip VeTube/ + cd .. + + - name: Upload zip + uses: actions/upload-artifact@v3 + with: + name: VeTube-x64 + path: dist + if-no-files-found: error + + vetube_release: + runs-on: windows-latest + if: ${{ startsWith(github.ref, 'refs/tags/') }} + needs: ["build"] + steps: + - uses: actions/checkout@v3 + - name: download + uses: actions/download-artifact@v3 + - name: Release + uses: softprops/action-gh-release@v1 + with: + files: VeTube-x64.zip + fail_on_unmatched_files: true + prerelease: ${{ contains(github.ref, '-') }} diff --git a/.gitignore b/.gitignore index 7bfcd7a..673c6dd 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,9 @@ __pycache__/ -*.pyc \ No newline at end of file +*.pyc +*.onnx +*.onnx.json +piper/voices/* +piper/voices/*/*.onnx +piper/voices/*/*.onnx.json +data.json +keys.txt \ No newline at end of file diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index 6721dc2..0000000 --- a/.gitmodules +++ /dev/null @@ -1,3 +0,0 @@ -[submodule "plugins/tacotron2-for-VeTube"] - path = plugins/tacotron2-for-VeTube - url = https://github.com/rmcpantoja/tacotron2-for-VeTube diff --git a/TTS/Piper/__init__.py b/TTS/Piper/__init__.py new file mode 100644 index 0000000..da31a9e --- /dev/null +++ b/TTS/Piper/__init__.py @@ -0,0 +1,137 @@ +import io +import json +import logging +import wave +from dataclasses import dataclass +from pathlib import Path +from typing import List, Mapping, Optional, Sequence, Union + +import numpy as np +import onnxruntime +from espeak_phonemizer import Phonemizer + +_LOGGER = logging.getLogger(__name__) + +_BOS = "^" +_EOS = "$" +_PAD = "_" + + +@dataclass +class PiperConfig: + num_symbols: int + num_speakers: int + sample_rate: int + espeak_voice: str + length_scale: float + noise_scale: float + noise_w: float + phoneme_id_map: Mapping[str, Sequence[int]] + + +class Piper: + def __init__( + self, + model_path: Union[str, Path], + config_path: Optional[Union[str, Path]] = None, + use_cuda: bool = False, + ): + if config_path is None: + config_path = f"{model_path}.json" + + self.config = load_config(config_path) + self.phonemizer = Phonemizer(self.config.espeak_voice) + self.model = onnxruntime.InferenceSession( + str(model_path), + sess_options=onnxruntime.SessionOptions(), + providers=["CPUExecutionProvider"] + if not use_cuda + else ["CUDAExecutionProvider"], + ) + + def synthesize( + self, + text: str, + speaker_id: Optional[int] = None, + length_scale: Optional[float] = None, + noise_scale: Optional[float] = None, + noise_w: Optional[float] = None, + ) -> bytes: + """Synthesize WAV audio from text.""" + if length_scale is None: + length_scale = self.config.length_scale + + if noise_scale is None: + noise_scale = self.config.noise_scale + + if noise_w is None: + noise_w = self.config.noise_w + + phonemes_str = self.phonemizer.phonemize(text) + phonemes = [_BOS] + list(phonemes_str) + phoneme_ids: List[int] = [] + + for phoneme in phonemes: + if phoneme in self.config.phoneme_id_map: + phoneme_ids.extend(self.config.phoneme_id_map[phoneme]) + phoneme_ids.extend(self.config.phoneme_id_map[_PAD]) + else: + _LOGGER.warning("No id for phoneme: %s", phoneme) + + phoneme_ids.extend(self.config.phoneme_id_map[_EOS]) + + phoneme_ids_array = np.expand_dims(np.array(phoneme_ids, dtype=np.int64), 0) + phoneme_ids_lengths = np.array([phoneme_ids_array.shape[1]], dtype=np.int64) + scales = np.array( + [noise_scale, length_scale, noise_w], + dtype=np.float32, + ) + + if (self.config.num_speakers > 1) and (speaker_id is not None): + # Default speaker + speaker_id = 0 + + sid = None + + if speaker_id is not None: + sid = np.array([speaker_id], dtype=np.int64) + + # Synthesize through Onnx + audio = self.model.run( + None, + { + "input": phoneme_ids_array, + "input_lengths": phoneme_ids_lengths, + "scales": scales, + "sid": sid, + }, + )[0].squeeze((0, 1)) + audio = audio_float_to_int16(audio.squeeze()) + return audio, self.config.sample_rate + + +def load_config(config_path: Union[str, Path]) -> PiperConfig: + with open(config_path, "r", encoding="utf-8") as config_file: + config_dict = json.load(config_file) + inference = config_dict.get("inference", {}) + + return PiperConfig( + num_symbols=config_dict["num_symbols"], + num_speakers=config_dict["num_speakers"], + sample_rate=config_dict["audio"]["sample_rate"], + espeak_voice=config_dict["espeak"]["voice"], + noise_scale=inference.get("noise_scale", 0.667), + length_scale=inference.get("length_scale", 1.0), + noise_w=inference.get("noise_w", 0.8), + phoneme_id_map=config_dict["phoneme_id_map"], + ) + + +def audio_float_to_int16( + audio: np.ndarray, max_wav_value: float = 32767.0 +) -> np.ndarray: + """Normalize audio and convert to int16 range""" + audio_norm = audio * (max_wav_value / max(0.01, np.max(np.abs(audio)))) + audio_norm = np.clip(audio_norm, -max_wav_value, max_wav_value) + audio_norm = audio_norm.astype("int16") + return audio_norm diff --git a/TTS/Piper/speaker.py b/TTS/Piper/speaker.py new file mode 100644 index 0000000..652ff61 --- /dev/null +++ b/TTS/Piper/speaker.py @@ -0,0 +1,48 @@ +import logging +from functools import partial +from pathlib import Path +import sounddevice as sd +from . import Piper + +class piperSpeak: + def __init__(self, model_path): + self.model_path = model_path + self.speaker_id = None + self.length_scale = 1 + self.noise_scale = 0.667 + self.noise_w = 0.8 + self.synthesize = None + self.voice = None + + def load_model(self): + if self.voice: + return self.voice + self.voice = Piper(self.model_path) + + def set_rate(self, new_scale): + self.length_scale = new_scale + + def set_speaker(self, sid): + self.speaker_id = sid + + def is_multispeaker(self): + return self.voice.config.num_speakers > 1 + + def list_speakers(self): + if self.is_multispeaker(): + return self.voice.config.speaker_id_map + else: + raise Exception("This is not a multispeaker model!") + + def speak(self, text): + self.synthesize = self.load_model() + if self.speaker_id is None and self.is_multispeaker(): + self.set_speaker(0) + audio_norm, sample_rate = self.voice.synthesize( + text, + self.speaker_id, + self.length_scale, + self.noise_scale, + self.noise_w + ) + sd.play(audio_norm, sample_rate) \ No newline at end of file diff --git a/TTS/lector.py b/TTS/lector.py new file mode 100644 index 0000000..12380f6 --- /dev/null +++ b/TTS/lector.py @@ -0,0 +1,27 @@ +# lector: +from accessible_output2.outputs import auto, sapi5 +from .Piper import Piper, speaker +import glob +""" +Esto es un gestionador de TTS. Permite manejar el uso de diferentes motores de texto a voz como: +1. accessible output2 +2. Piper +""" +def configurar_tts(lector): + if lector == "auto": + return auto.Auto() + elif lector == "sapi5": + return sapi5.SAPI5() + elif lector == "piper": + return speaker + else: + raise Exception("Lector no soportado.") + +def detect_onnx_models(path): + onnx_models = glob.glob(path + '/*/*.onnx') + if len(onnx_models) > 1: + return onnx_models + elif len(onnx_models) == 1: + return onnx_models[0] + else: + return None diff --git a/TTS/list_voices.py b/TTS/list_voices.py new file mode 100644 index 0000000..467d2fb --- /dev/null +++ b/TTS/list_voices.py @@ -0,0 +1,34 @@ +import os +import tarfile +from .lector import detect_onnx_models +from .Piper import speaker +import wx +def extract_tar(file, destination): + if not os.path.exists(destination): + os.makedirs(destination) + with tarfile.open(file, 'r:gz') as tar: + tar.extractall(destination) + +def install_piper_voice(config, reader): + abrir_tar = wx.FileDialog(None, _("Selecciona un paquete de voz"), wildcard=_("Archivos tar.gz (*.tar.gz)|*.tar.gz"), style=wx.FD_OPEN | wx.FD_FILE_MUST_EXIST) + if abrir_tar.ShowModal() == wx.ID_CANCEL: + wx.MessageBox(_('Para usar piper como sistema TTS, necesitas tener al menos una voz. Si quieres hacerlo de forma manual, extrae el paquete de voz en la carpeta "piper/voices/voice-nombre_de_paquete" en VeTube.'), _("No se instaló ninguna voz"), wx.ICON_ERROR) + return + paquete = abrir_tar.GetPath() + nombre_paquete = os.path.splitext(os.path.basename(paquete))[0] + destino = os.path.join(os.getcwd(), "piper", "voices", nombre_paquete[:-3]) + extract_tar(paquete, destino) + wx.MessageBox(_("¡Voz instalada satosfactoriamente! esta será establecida en VeTube ahora. Para cambiar de modelo de voz, puedes hacerlo a través de las configuraciones."), _("Listo"), wx.ICON_INFORMATION) + reader=speaker.piperSpeak(f"{destino}/{nombre_paquete}.onnx") + config['voz'] = 0 + abrir_tar.Destroy() + return config, reader + +def piper_list_voices(): + voice_list = detect_onnx_models("piper/voices") + # fix paths: + if isinstance(voice_list, str): + voice_list = [os.path.basename(voice_list)] + elif isinstance(voice_list, list): + voice_list = [os.path.basename(path) for path in voice_list] + return voice_list \ No newline at end of file diff --git a/VeTube.py b/VeTube.py index 58c640f..ad8bf65 100644 --- a/VeTube.py +++ b/VeTube.py @@ -3,18 +3,39 @@ import json,wx,wx.adv,threading,languageHandler,restart,translator,time,funciones,google_currency,fajustes,ajustes from keyboard_handler.wx_handler import WXKeyboardHandler from playsound import playsound -from accessible_output2.outputs import auto, sapi5 +from TTS.lector import configurar_tts, detect_onnx_models +from TTS.list_voices import install_piper_voice from yt_dlp import YoutubeDL from pyperclip import copy from chat_downloader import ChatDownloader from update import updater,update -from os import path,remove +from os import path,remove,getcwd, makedirs #from TikTokLive import TikTokLiveClient #from TikTokLive.types.events import CommentEvent, ConnectEvent from menu_accesible import Accesible + yt=0 -lector=auto.Auto() -leer=sapi5.SAPI5() +# revisar la configuración primero, ya que necesitamos determinar el sistema TTS a través de ella. +if not path.exists("data.json"): fajustes.escribirConfiguracion() +config=fajustes.leerConfiguracion() +lector=configurar_tts(config['sistemaTTS']) +leer=configurar_tts("sapi5") + +def configurar_piper(carpeta_voces): + global config, lector + onnx_models = detect_onnx_models(carpeta_voces) + if onnx_models is None: + sinvoces = wx.MessageDialog(None, _('Necesitas al menos una voz para poder usar el sintetizador Piper. ¿Quieres abrir nuestra carpeta de Drive para descargar algunos modelos? Si pulsas sí, se abrirá nuestra carpeta seguido de una ventana para instalar una una vez la descargues.'), _("No hay voces instaladas"), wx.YES_NO | wx.ICON_QUESTION) + abrir_modelos = sinvoces.ShowModal() + if abrir_modelos == wx.ID_YES: + wx.LaunchDefaultBrowser("https://drive.google.com/drive/folders/1zFJRTI6CpVw9NkrTiNYOKGga0yn4JXzv?usp=drive_link") + config, lector = install_piper_voice(config, lector) + sinvoces.Destroy() + elif isinstance(onnx_models, str) or isinstance(onnx_models, list): + config['voz'] = 0 + +carpeta_voces = path.join(getcwd(), "piper", "voices") + def escribirTeclas(): with open('keys.txt', 'w+') as arch: arch.write("""{ "control+p": leer.silence, @@ -40,17 +61,19 @@ def leerTeclas(): with open ("keys.txt",'r') as arch: mis_teclas=arch.read() else: escribirTeclas() -if not path.exists("data.json"): fajustes.escribirConfiguracion() -config=fajustes.leerConfiguracion() pos=[] favorite=funciones.leerJsonLista('favoritos.json') mensajes_destacados=funciones.leerJsonLista('mensajes_destacados.json') leer.set_rate(config['speed']) leer.set_pitch(config['tono']) -leer.set_voice(ajustes.lista_voces[config['voz']]) +leer.set_voice(leer.list_voices()[0]) leer.set_volume(config['volume']) favs=funciones.convertirLista(favorite,'titulo','url') msjs=funciones.convertirLista(mensajes_destacados,'mensaje','titulo') +# establecer la voz del lector en piper: +if config['sistemaTTS'] == "piper": + lector=lector.piperSpeak(f"piper/voices/voice-{ajustes.lista_voces[config['voz']][:-4]}/{ajustes.lista_voces[config['voz']]}") +# establecer idiomas: languageHandler.setLanguage(config['idioma']) idiomas = languageHandler.getAvailableLanguages() langs = [] @@ -77,6 +100,9 @@ def __init__(self, *args, **kwds): if self.instance.IsAnotherRunning(): wx.MessageBox(_('VeTube ya se encuentra en ejecución. Cierra la otra instancia antes de iniciar esta.'), 'Error', wx.ICON_ERROR) return False + # configurar TTS: + if config['sistemaTTS'] == "piper": + configurar_piper(carpeta_voces) if config['donations']: update.donation() self.dentro=False self.dst ="" @@ -557,6 +583,9 @@ def guardar(self): if google_currency.CODES[k] == monedita[0]: self.divisa = k break + # verificar voces: + if config['sistemaTTS'] == "piper": + configurar_piper(carpeta_voces) leer=ajustes.prueba def borrarHistorial(self,event): 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) diff --git a/ajustes.py b/ajustes.py index 02dbcc5..594d8dd 100644 --- a/ajustes.py +++ b/ajustes.py @@ -2,16 +2,23 @@ from google_currency import CODES from translator import LANGUAGES from accessible_output2.outputs import sapi5 +from TTS.lector import configurar_tts, detect_onnx_models +from TTS.list_voices import piper_list_voices, install_piper_voice +from TTS.Piper import Piper, speaker from os import path from playsound import playsound -prueba=sapi5.SAPI5() +if not path.exists("data.json"): fajustes.escribirConfiguracion() +config=fajustes.leerConfiguracion() + +prueba=configurar_tts("sapi5") +prueba_piper=configurar_tts("piper") lista_voces=prueba.list_voices() +lista_voces_piper = piper_list_voices() rutasonidos=["sounds/chat.mp3","sounds/chatmiembro.mp3","sounds/miembros.mp3","sounds/donar.mp3","sounds/moderators.mp3","sounds/verified.mp3","sounds/abrirchat.wav","sounds/propietario.mp3","sounds/buscar.wav"] class configuracionDialog(wx.Dialog): def __init__(self, parent): - global config - if not path.exists("data.json"): fajustes.escribirConfiguracion() - config=fajustes.leerConfiguracion() + global config, lista_voces, prueba_piper + # idioma: languageHandler.setLanguage(config['idioma']) idiomas = languageHandler.getAvailableLanguages() langs = [] @@ -24,6 +31,12 @@ def __init__(self, parent): monedas=[_('Por defecto')] for k in CODES: monedas.append(f'{CODES[k]}, ({k})') for k in LANGUAGES: idiomas_disponibles.append(LANGUAGES[k]) + # voces: + if config['sistemaTTS'] == "piper": + if not lista_voces_piper is None: + lista_voces = lista_voces_piper + else: + lista_voces = [_("No hay voces instaladas")] mensajes_categorias=[_('Miembros'),_('Donativos'),_('Moderadores'),_('Usuarios Verificados'),_('Favoritos')] mensajes_sonidos=[_('Sonido cuando llega un mensaje'),_('Sonido cuando habla un miembro'),_('Sonido cuando se conecta un miembro'),_('Sonido cuando llega un donativo'),_('Sonido cuando habla un moderador'),_('Sonido cuando habla un usuario verificado'),_('Sonido al ingresar al chat'),_('Sonido cuando habla el propietario del canal'),_('sonido al terminar la búsqueda de mensajes')] super().__init__(parent, title=_("Configuración")) @@ -68,8 +81,18 @@ def __init__(self, parent): boxSizer_2 = wx.StaticBoxSizer(box_2,wx.VERTICAL) self.check_1 = wx.CheckBox(self.treeItem_2, wx.ID_ANY, _("Usar voz sapi en lugar de lector de pantalla.")) self.check_1.SetValue(config['sapi']) - self.check_1.Bind(wx.EVT_CHECKBOX, lambda event: self.checar(event,'sapi')) + self.check_1.Bind(wx.EVT_CHECKBOX, lambda event: self.checar_sapi(event)) boxSizer_2.Add(self.check_1) + label_tts = wx.StaticText(self.treeItem_2, wx.ID_ANY, _("Sistema TTS a usar: ")) + boxSizer_2 .Add(label_tts) + self.seleccionar_TTS= wx.Choice(self.treeItem_2, wx.ID_ANY, choices=["auto", "piper", "sapi5"]) + self.seleccionar_TTS.SetStringSelection(config['sistemaTTS']) + if config['sapi']: + self.seleccionar_TTS.Disable() + else: + self.seleccionar_TTS.Enable() + self.seleccionar_TTS.Bind(wx.EVT_CHOICE, self.cambiar_sintetizador) + boxSizer_2 .Add(self.seleccionar_TTS) self.chk1 = wx.CheckBox(self.treeItem_2, wx.ID_ANY, _("Activar lectura de mensajes automática")) self.chk1.SetValue(config['reader']) self.chk1.Bind(wx.EVT_CHECKBOX, lambda event: self.checar(event,'reader')) @@ -80,6 +103,15 @@ def __init__(self, parent): self.choice_2.SetSelection(config['voz']) self.choice_2.Bind(wx.EVT_CHOICE, self.cambiarVoz) boxSizer_2 .Add(self.choice_2) + if config['sistemaTTS'] == "piper": + if len(lista_voces) == 1: + prueba_piper = speaker.piperSpeak(f"piper/voices/voice-{lista_voces[0][:-4]}/{lista_voces[0]}") + config['voz'] = 0 + self.instala_voces = wx.Button(self.treeItem_2, wx.ID_ANY, label=_("Instalar un paquete de voz...")) + self.instala_voces.Bind(wx.EVT_BUTTON, self.instalar_voz_piper) + boxSizer_2.Add(self.instala_voces) + if config['sistemaTTS'] == "piper": + self.instala_voces.Disable() label_8 = wx.StaticText(self.treeItem_2, wx.ID_ANY, _("Tono: ")) boxSizer_2 .Add(label_8) self.slider_1 = wx.Slider(self.treeItem_2, wx.ID_ANY, config['tono']+10, 0, 20) @@ -90,6 +122,12 @@ def __init__(self, parent): self.slider_2 = wx.Slider(self.treeItem_2, wx.ID_ANY, config['volume'], 0, 100) self.slider_2.Bind(wx.EVT_SLIDER, self.cambiarVolumen) boxSizer_2 .Add(self.slider_2) + # desactivar tono/volumen para piper, por ahora no soportados: + if config['sistemaTTS'] == "piper": + label_8.Disable() + self.slider_1.Disable() + label_9.Disable() + self.slider_2.Disable() label_10 = wx.StaticText(self.treeItem_2, wx.ID_ANY, _("Velocidad: ")) boxSizer_2 .Add(label_10) self.slider_3 = wx.Slider(self.treeItem_2, wx.ID_ANY, config['speed']+10, 0, 20) @@ -141,12 +179,39 @@ def __init__(self, parent): self.treeItem_2.SetSizer(sizer_6) self.SetSizer(sizer_5) self.Center() + def cambiar_sintetizador(self, event): + global lista_voces + config['sistemaTTS']=self.seleccionar_TTS.GetStringSelection() + if config['sistemaTTS'] == "piper": + if not lista_voces_piper is None: + lista_voces = lista_voces_piper + else: + lista_voces = [_("No hay voces instaladas")] + self.instala_voces.Enable() + else: + lista_voces = prueba.list_voices() + self.instala_voces.Disable() + self.choice_2.Clear() + self.choice_2.AppendItems(lista_voces) + def instalar_voz_piper(self, event): + global config, prueba_piper + config, prueba_piper = install_piper_voice(config, prueba_piper) def reproducirPrueva(self, event): - prueba.silence() - prueba.speak(_("Hola, soy la voz que te acompañará de ahora en adelante a leer los mensajes de tus canales favoritos.")) + if not ".onnx" in self.choice_2.GetStringSelection(): + prueba.silence() + prueba.speak(_("Hola, soy la voz que te acompañará de ahora en adelante a leer los mensajes de tus canales favoritos.")) + else: + prueba_piper.speak(_("Hola, soy la voz que te acompañará de ahora en adelante a leer los mensajes de tus canales favoritos.")) + def porcentaje_a_escala(self, porcentaje): + scale = 2.00 + (1 - ((porcentaje - -10) / (10 - -10))) * (0.50 - 2.00) + return scale + def cambiarVelocidad(self, event): value=self.slider_3.GetValue()-10 - prueba.set_rate(value) + if not ".onnx" in lista_voces[self.choice_2.GetSelection()]: + prueba.set_rate(value) + else: + prueba_piper.set_rate(self.porcentaje_a_escala(value)) config['speed']=value def cambiarTono(self, event): value=self.slider_1.GetValue()-10 @@ -165,6 +230,14 @@ def mostrarSonidos(self,event): self.soniditos.Disable() self.reproducir.Disable() def checar(self, event,key): config[key]=True if event.IsChecked() else False + def checar_sapi(self, event): + config['sapi']=True if event.IsChecked() else False + self.seleccionar_TTS.Enable(not event.IsChecked()) + def cambiarVoz(self, event): - config['voz']=event.GetSelection() - prueba.set_voice(lista_voces[event.GetSelection()]) \ No newline at end of file + global prueba_piper, lista_voces + config['voz']=self.choice_2.GetSelection() + if config['sistemaTTS'] == "piper": + prueba_piper = speaker.piperSpeak(f"piper/voices/voice-{lista_voces[config['voz']][:-4]}/{lista_voces[config['voz']]}") + else: + prueba.set_voice(lista_voces[config['voz']]) \ No newline at end of file diff --git a/fajustes.py b/fajustes.py index 49a2b66..babf7a8 100644 --- a/fajustes.py +++ b/fajustes.py @@ -1,17 +1,20 @@ import json def escribirConfiguracion(): - data={'voz': 0, -"tono": 0, -"volume": 100, -"speed": 0, -'sapi':True, -'sonidos': True, -'idioma': "system", -'categorias': [True, True, False, False, False], -'listasonidos': [True, True, True, True, True, True, True, True, True], -'reader': True, -'donations': True, -'updates': True} + data={ + "sistemaTTS": "auto", + 'voz': 0, + "tono": 0, + "volume": 100, + "speed": 0, + 'sapi':True, + 'sonidos': True, + 'idioma': "system", + 'categorias': [True, True, False, False, False], + 'listasonidos': [True, True, True, True, True, True, True, True, True], + 'reader': True, + 'donations': True, + 'updates': True + } with open('data.json', 'w+') as file: json.dump(data, file) def leerConfiguracion(): with open ("data.json") as file: return json.load(file) \ No newline at end of file diff --git a/plugins/tacotron2-for-VeTube b/plugins/tacotron2-for-VeTube deleted file mode 160000 index 038b063..0000000 --- a/plugins/tacotron2-for-VeTube +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 038b0630aa38b0c08c6040335034a9e99ca70fc4 diff --git a/requirements.txt b/requirements.txt index 2e5277d..af300df 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,9 +1,13 @@ wxPython -playsound==1.2.2 +git+https://github.com/t1nky/playsound yt_dlp TikTokLive pyperclip chat_downloader git+https://github.com/metalalchemist/google-currency git+https://github.com/accessibleapps/accessible_output2 -git+https://github.com/StarkBotsIndustries/googletrans.git \ No newline at end of file +git+https://github.com/rmcpantoja/googletrans.git +numpy +onnxruntime==1.14.1 +git+https://github.com/mush42/espeak-phonemizer-windows +sounddevice \ No newline at end of file