From 51fa4ce9e91dd8bc7e2dea0d05fe85247eaca606 Mon Sep 17 00:00:00 2001
From: erew123 <35898566+erew123@users.noreply.github.com>
Date: Mon, 1 Apr 2024 10:48:36 +0100
Subject: [PATCH] Cleaner Start-up & mini diags
Updated various bits of code to clean up the AllTalk start-up messages.
---
modeldownload.py | 145 ------
script.py | 1073 ---------------------------------------
tts_server.py | 1247 ----------------------------------------------
3 files changed, 2465 deletions(-)
diff --git a/modeldownload.py b/modeldownload.py
index b4edae5..e69de29 100644
--- a/modeldownload.py
+++ b/modeldownload.py
@@ -1,145 +0,0 @@
-import os
-from pathlib import Path
-import requests
-from tqdm import tqdm
-import importlib.metadata as metadata
-import json
-from packaging import version
-
-#################################################################
-#### LOAD PARAMS FROM confignew.json - REQUIRED FOR BRANDING ####
-#################################################################
-# STARTUP VARIABLE - Create "this_dir" variable as the current script directory
-this_dir = Path(__file__).parent.resolve()
-
-
-# load standard config file in and get settings
-def load_config(file_path):
- with open(file_path, "r") as config_file:
- config = json.load(config_file)
- return config
-
-
-config_file_path = this_dir / "confignew.json"
-params = load_config(config_file_path)
-
-# Define the path to the modeldownload config file file
-modeldownload_config_file_path = this_dir / "modeldownload.json"
-
-# Check if the JSON file exists
-if modeldownload_config_file_path.exists():
- with open(modeldownload_config_file_path, "r") as config_file:
- settings = json.load(config_file)
-
- # Extract settings from the loaded JSON
- base_path = Path(settings.get("base_path", ""))
- model_path = Path(settings.get("model_path", ""))
- files_to_download = settings.get("files_to_download", {})
-else:
- # Default settings if the JSON file doesn't exist or is empty
- print(
- f"[{params['branding']}Startup] \033[91mWarning\033[0m modeldownload.json is missing so please re-download it and save it in the coquii_tts main folder."
- )
- print(
- f"[{params['branding']}Startup] \033[91mWarning\033[0m API Local and XTTSv2 Local will error unless this is corrected."
- )
-
-# Read the version specifier from requirements_nvidia.txt
-with open(this_dir / "system" / "requirements" / "requirements_standalone.txt", "r") as req_file:
- requirements = req_file.readlines()
-
-tts_version_required = None
-for req in requirements:
- if req.startswith("TTS=="):
- tts_version_required = req.strip().split("==")[1]
- break
-
-if tts_version_required is None:
- raise ValueError(
- f"[{params['branding']}Startup] \033[91mWarning\033[0m Could not find TTS version specifier in requirements file"
- )
-
-
-def create_directory_if_not_exists(directory):
- if not directory.exists():
- directory.mkdir(parents=True)
-
-
-def download_file(url, destination):
- response = requests.get(url, stream=True)
- total_size_in_bytes = int(response.headers.get("content-length", 0))
- block_size = 1024 # 1 Kibibyte
-
- progress_bar = tqdm(total=total_size_in_bytes, unit="iB", unit_scale=True)
-
- with open(destination, "wb") as file:
- for data in response.iter_content(block_size):
- progress_bar.update(len(data))
- file.write(data)
-
- progress_bar.close()
-
-
-def check_tts_version():
- try:
- tts_version = metadata.version("tts")
- print(
- f"[{params['branding']}Startup] TTS version installed: \033[93m"
- + tts_version
- + "\033[0m"
- )
-
- if version.parse(tts_version) < version.parse(tts_version_required):
- print(
- f"[{params['branding']}Startup] \033[91mWarning\033[0m TTS version is too old. Please upgrade to version \033[93m"
- + tts_version_required
- + "\033[0m or later.\033[0m"
- )
- print(
- f"[{params['branding']}Startup] \033[91mWarning\033[0m At your terminal/command prompt \033[94mpip install --upgrade tts\033[0m"
- )
- else:
- print(f"[{params['branding']}Startup] TTS version is up to date.")
- except metadata.PackageNotFoundError:
- print(
- f"[{params['branding']}Startup] \033[91mWarning\033[0m TTS is not installed."
- )
-
-
-# Check and create directories
-if str(base_path) == "models":
- create_directory_if_not_exists(this_dir / base_path / model_path)
-else:
- create_directory_if_not_exists(base_path / model_path)
- print(
- f"[{params['branding']}Startup] \033[94mInfo\033[0m Custom path set in \033[93mmodeldownload.json\033[0m. Using the following settings:"
- )
- print(
- f"[{params['branding']}Startup] \033[94mInfo\033[0m Base folder Path:\033[93m",
- base_path,
- "\033[0m",
- )
- print(
- f"[{params['branding']}Startup] \033[94mInfo\033[0m Model folder Path:\033[93m",
- model_path,
- "\033[0m",
- )
- print(
- f"[{params['branding']}Startup] \033[94mInfo\033[0m Full Path:\033[93m",
- base_path / model_path,
- "\033[0m",
- )
-
-# Download files if they don't exist
-print("[" + params["branding"] + "Startup] Checking Model is Downloaded.")
-for filename, url in files_to_download.items():
- if str(base_path) == "models":
- destination = this_dir / base_path / model_path / filename
- else:
- destination = Path(base_path) / model_path / filename
-
- if not destination.exists():
- print(f"[{params['branding']}Startup] Downloading " + filename + "...")
- download_file(url, destination)
-
-check_tts_version()
diff --git a/script.py b/script.py
index 5b4066d..e69de29 100644
--- a/script.py
+++ b/script.py
@@ -1,1073 +0,0 @@
-import html
-import json
-import random
-import subprocess
-import time
-import os
-import requests
-import threading
-import signal
-import sys
-import atexit
-from pathlib import Path
-from datetime import datetime, timedelta
-import re
-import numpy as np
-import soundfile as sf
-import uuid
-
-######################################
-#### ALLTALK ALLOWED STARTUP TIME ####
-######################################
-startup_wait_time = 120
-
-# You can change the above setting to a larger number to allow AllTAlk more time to start up. The default setting is 120 seconds (2 minutes).
-# On some older systems you may need to allow AllTalk more time. So you could set it to 240 (4 minutes) which will give AllTalk more to load.
-
-#################################################################
-#### LOAD PARAMS FROM confignew.json - REQUIRED FOR BRANDING ####
-#################################################################
-# STARTUP VARIABLE - Create "this_dir" variable as the current script directory
-this_dir = Path(__file__).parent.resolve()
-
-
-# load config file in and get settings
-def load_config(file_path):
- with open(file_path, "r") as config_file:
- config = json.load(config_file)
- return config
-
-
-config_file_path = this_dir / "confignew.json"
-# Load the params dictionary from the confignew.json file
-params = load_config(config_file_path)
-
-# Required for sentence splitting
-try:
- from TTS.api import TTS
- from TTS.utils.synthesizer import Synthesizer
-except ModuleNotFoundError:
- logger.error(
- f"[{params['branding']}]\033[91mWarning\033[0m Could not find the TTS module. Make sure to install the requirements for the {params['branding']} extension."
- f"[{params['branding']}]\033[91mWarning\033[0m Linux / Mac:\npip install -r /alltalk_tts/requirements.txt\n"
- f"[{params['branding']}]\033[91mWarning\033[0m Windows:\npip install -r \\alltalk_tts\\requirements.txt\n"
- f"[{params['branding']}]\033[91mWarning\033[0m If you used the one-click installer, paste the command above in the terminal window launched after running the cmd_ script. On Windows, that's cmd_windows.bat."
- )
- raise
-
-# IMPORT - Attempt Importing DeepSpeed (required for displaying Deepspeed checkbox in gradio)
-try:
- import deepspeed
-
- deepspeed_installed = True
-except ImportError:
- deepspeed_installed = False
-
-# Import gradio if being used within text generation webUI
-try:
- import gradio as gr
-
- from modules import chat, shared, ui_chat
- from modules.logging_colors import logger
- from modules.ui import create_refresh_button
- from modules.utils import gradio
-
- # This is set to check if the script is being run within text generation webui or as a standalone script. False is running as part of text gen web ui or a gradio interface
- running_in_standalone = False
- output_folder_wav = params["output_folder_wav"]
-except ModuleNotFoundError:
- output_folder_wav = params["output_folder_wav_standalone"]
- print(f"[{params['branding']}Startup] Running script.py in standalone mode")
- # This is set to check if the script is being run within text generation webui or as a standalone script. true means standalone
- running_in_standalone = True
-
-###########################
-#### STARTUP VARIABLES ####
-###########################
-# STARTUP VARIABLE - Import languges file for Gradio to be able to display them in the interface
-with open(this_dir / "system" / "config" / "languages.json", encoding="utf8") as f:
- languages = json.load(f)
-# Create a global lock
-process_lock = threading.Lock()
-# Base setting for a possible FineTuned model existing and the loader being available
-tts_method_xtts_ft = False
-
-
-# Gather the voice files
-def get_available_voices():
- return sorted([voice.name for voice in Path(f"{this_dir}/voices").glob("*.wav")])
-
-
-#########################
-#### LICENSE DISPLAY ####
-#########################
-# STARTUP Display Licence Information
-print(f"[{params['branding']}Startup] \033[94mCoqui Public Model License\033[0m")
-print(f"[{params['branding']}Startup] \033[94mhttps://coqui.ai/cpml.txt\033[0m")
-
-
-############################################
-#### DELETE OLD OUTPUT WAV FILES IF SET ####
-############################################
-def delete_old_files(folder_path, days_to_keep):
- current_time = datetime.now()
- print(
- f"[{params['branding']}Startup] Deletion of old output folder WAV files is currently enabled and set at",
- delete_output_wavs_setting,
- )
- for file_name in os.listdir(folder_path):
- file_path = os.path.join(folder_path, file_name)
- if os.path.isfile(file_path):
- file_creation_time = datetime.fromtimestamp(os.path.getctime(file_path))
- age = current_time - file_creation_time
- if age > timedelta(days=days_to_keep):
- os.remove(file_path)
-
-
-# Extract settings using params dictionary
-delete_output_wavs_setting = params["delete_output_wavs"]
-output_folder_wav = os.path.normpath(output_folder_wav)
-
-# Check and perform file deletion
-if delete_output_wavs_setting.lower() == "disabled":
- print(
- "["
- + params["branding"]
- + "Startup] Old output wav file deletion is set to disabled."
- )
-else:
- try:
- days_to_keep = int(delete_output_wavs_setting.split()[0])
- delete_old_files(output_folder_wav, days_to_keep)
- except ValueError:
- print(
- f"[{params['branding']}Startup] Invalid setting for deleting old wav files. Please use 'Disabled' or 'X Days' format."
- )
-
-
-########################
-#### STARTUP CHECKS ####
-########################
-# STARTUP Checks routine
-def check_required_files():
- this_dir = Path(__file__).parent.resolve()
- download_script_path = this_dir / "modeldownload.py"
- subprocess.run(["python", str(download_script_path)])
- print(f"[{params['branding']}Startup] All required files are present.")
-
-
-# STARTUP Call Check routine
-check_required_files()
-
-##################################################
-#### Check to see if a finetuned model exists ####
-##################################################
-# Set the path to the directory
-trained_model_directory = this_dir / "models" / "trainedmodel"
-# Check if the directory "trainedmodel" exists
-finetuned_model = trained_model_directory.exists()
-# If the directory exists, check for the existence of the required files
-# If true, this will add a extra option in the Gradio interface for loading Xttsv2 FT
-if finetuned_model:
- required_files = ["model.pth", "config.json", "vocab.json"]
- finetuned_model = all(
- (trained_model_directory / file).exists() for file in required_files
- )
-if finetuned_model:
- print(f"[{params['branding']}Startup] Finetuned model \033[93mDetected\033[0m")
-
-####################################################
-#### SET GRADIO BUTTONS BASED ON confignew.json ####
-####################################################
-
-if params["tts_method_api_tts"] == True:
- gr_modelchoice = "API TTS"
-elif params["tts_method_api_local"] == True:
- gr_modelchoice = "API Local"
-elif params["tts_method_xtts_local"] == True:
- gr_modelchoice = "XTTSv2 Local"
-
-# Set the default for Narrated text without asterisk or quotes to be Narrator
-non_quoted_text_is = True
-
-
-######################
-#### GRADIO STUFF ####
-######################
-def remove_tts_from_history(history):
- for i, entry in enumerate(history["internal"]):
- history["visible"][i] = [history["visible"][i][0], entry[1]]
- return history
-
-
-def toggle_text_in_history(history):
- for i, entry in enumerate(history["visible"]):
- visible_reply = entry[1]
- if visible_reply.startswith("\n\n{reply}",
- ]
- else:
- history["visible"][i] = [
- history["visible"][i][0],
- f"{visible_reply.split('')[0]}",
- ]
- return history
-
-
-def history_modifier(history):
- # Remove autoplay from the last reply
- if len(history["internal"]) > 0:
- history["visible"][-1] = [
- history["visible"][-1][0],
- history["visible"][-1][1].replace("controls autoplay>", "controls>"),
- ]
-
- return history
-
-
-######################################
-#### SUBPROCESS/WEBSERVER STARTUP ####
-######################################
-base_url = f"http://{params['ip_address']}:{params['port_number']}"
-script_path = this_dir / "tts_server.py"
-
-
-def signal_handler(sig, frame):
- print(
- f"[{params['branding']}Shutdown] \033[94mReceived Ctrl+C, terminating subprocess\033[92m"
- )
- if process.poll() is None:
- process.terminate()
- process.wait() # Wait for the subprocess to finish
- sys.exit(0)
-
-
-# Attach the signal handler to the SIGINT signal (Ctrl+C)
-signal.signal(signal.SIGINT, signal_handler)
-# Check if we're running in docker
-if os.path.isfile("/.dockerenv"):
- print(
- f"[{params['branding']}Startup] \033[94mRunning in Docker. Please wait.\033[0m"
- )
-else:
- # Start the subprocess
- process = subprocess.Popen(["python", script_path])
- # Check if the subprocess has started successfully
- if process.poll() is None:
- print(f"[{params['branding']}Startup] TTS Subprocess starting")
- print(f"[{params['branding']}Startup]")
- print(
- f"[{params['branding']}Startup] \033[94m {params['branding']}Settings & Documentation:\033[00m",
- f"\033[92mhttp://{params['ip_address']}:{params['port_number']}\033[00m",
- )
- print(f"[{params['branding']}Startup]")
- else:
- print(
- f"[{params['branding']}Startup] \033[91mWarning\033[0m TTS Subprocess Webserver failing to start process"
- )
- print(
- f"[{params['branding']}Startup] \033[91mWarning\033[0m It could be that you have something on port:",
- params["port_number"],
- )
- print(
- f"[{params['branding']}Startup] \033[91mWarning\033[0m Or you have not started in a Python environement with all the necesssary bits installed"
- )
- print(
- f"[{params['branding']}Startup] \033[91mWarning\033[0m Check you are starting Text-generation-webui with either the start_xxxxx file or the Python environment with cmd_xxxxx file."
- )
- print(
- f"[{params['branding']}Startup] \033[91mWarning\033[0m xxxxx is the type of OS you are on e.g. windows, linux or mac."
- )
- print(
- f"[{params['branding']}Startup] \033[91mWarning\033[0m Alternatively, you could check no other Python processes are running that shouldnt be e.g. Restart your computer is the simple way."
- )
- # Cleanly kill off this script, but allow text-generation-webui to keep running, albeit without this alltalk_tts
- sys.exit(1)
-
- timeout = startup_wait_time # Gather timeout setting from startup_wait_time
-
- # Introduce a delay before starting the check loop
- time.sleep(26) # Wait 26 secs before checking if the tts_server.py has started up.
- start_time = time.time()
- while time.time() - start_time < timeout:
- try:
- response = requests.get(f"{base_url}/ready")
- if response.status_code == 200:
- break
- except requests.RequestException as e:
- # Print the exception for debugging purposes
- print(
- f"[{params['branding']}Startup] \033[91mWarning\033[0m TTS Subprocess has NOT started up yet, Will keep trying for {timeout} seconds maximum. Please wait."
- )
- time.sleep(5)
- else:
- print(
- f"\n[{params['branding']}Startup] Startup timed out. Full help available here \033[92mhttps://github.com/erew123/alltalk_tts#-help-with-problems\033[0m"
- )
- print(
- f"[{params['branding']}Startup] On older system you may wish to open and edit \033[94mscript.py\033[0m with a text editor and changing the"
- )
- print(
- f"[{params['branding']}Startup] \033[94mstartup_wait_time = 120\033[0m setting to something like \033[94mstartup_wait_time = 240\033[0m as this will allow"
- )
- print(
- f"[{params['branding']}Startup] AllTalk more time to try load the model into your VRAM. Otherise please visit the Github for"
- )
- print(
- f"[{params['branding']}Startup] a list of other possible troubleshooting options."
- )
- # Cleanly kill off this script, but allow text-generation-webui to keep running, albeit without this alltalk_tts
- sys.exit(1)
-
-
-#####################################
-#### MODEL LOADING AND UNLOADING ####
-#####################################
-# MODEL - Swap model based on Gradio selection API TTS, API Local, XTTSv2 Local
-def send_reload_request(tts_method):
- global tts_method_xtts_ft
- try:
- params["tts_model_loaded"] = False
- url = f"{base_url}/api/reload"
- payload = {"tts_method": tts_method}
- response = requests.post(url, params=payload)
- response.raise_for_status() # Raises an HTTPError for bad responses
- json_response = response.json()
- # Check if the reload operation was successful
- if json_response.get("status") == "model-success":
- # Update tts_tts_model_loaded to True if the reload was successful
- params["tts_model_loaded"] = True
- # Update local script parameters based on the tts_method
- if tts_method == "API TTS":
- params["tts_method_api_local"] = False
- params["tts_method_xtts_local"] = False
- params["tts_method_api_tts"] = True
- params["deepspeed_activate"] = False
- audio_path = this_dir / "system" / "at_sounds" / "apitts.wav"
- tts_method_xtts_ft = False
- elif tts_method == "API Local":
- params["tts_method_api_tts"] = False
- params["tts_method_xtts_local"] = False
- params["tts_method_api_local"] = True
- params["deepspeed_activate"] = False
- audio_path = this_dir / "system" / "at_sounds" / "apilocal.wav"
- tts_method_xtts_ft = False
- elif tts_method == "XTTSv2 Local":
- params["tts_method_api_tts"] = False
- params["tts_method_api_local"] = False
- params["tts_method_xtts_local"] = True
- audio_path = this_dir / "system" / "at_sounds" / "xttslocal.wav"
- tts_method_xtts_ft = False
- elif tts_method == "XTTSv2 FT":
- params["tts_method_api_tts"] = False
- params["tts_method_api_local"] = False
- params["tts_method_xtts_local"] = False
- audio_path = this_dir / "system" / "at_sounds" / "xttsfinetuned.wav"
- tts_method_xtts_ft = True
- return f''
- except requests.exceptions.RequestException as e:
- # Handle the HTTP request error
- print(
- f"[{params['branding']}Server] \033[91mWarning\033[0m Error during request to webserver process: {e}"
- )
- return {"status": "error", "message": str(e)}
-
-
-##################
-#### LOW VRAM ####
-##################
-# LOW VRAM - Gradio Checkbox handling
-def send_lowvram_request(low_vram):
- try:
- params["tts_model_loaded"] = False
- if low_vram:
- audio_path = this_dir / "system" / "at_sounds" / "lowvramenabled.wav"
- else:
- audio_path = this_dir / "system" / "at_sounds" / "lowvramdisabled.wav"
- url = f"{base_url}/api/lowvramsetting?new_low_vram_value={low_vram}"
- headers = {"Content-Type": "application/json"}
- response = requests.post(url, headers=headers)
- response.raise_for_status() # Raises an HTTPError for bad responses
- json_response = response.json()
- # Check if the low VRAM request was successful
- if json_response.get("status") == "lowvram-success":
- # Update any relevant variables or perform other actions on success
- params["tts_model_loaded"] = True
- return f''
- except requests.exceptions.RequestException as e:
- # Handle the HTTP request error
- print(
- f"[{params['branding']}Server] \033[91mWarning\033[0m Error during request to webserver process: {e}"
- )
- return {"status": "error", "message": str(e)}
-
-
-###################
-#### DeepSpeed ####
-###################
-# DEEPSPEED - Reload the model when DeepSpeed checkbox is enabled/disabled
-def send_deepspeed_request(deepspeed_param):
- try:
- params["tts_model_loaded"] = False
- if deepspeed_param:
- audio_path = this_dir / "system" / "at_sounds" / "deepspeedenabled.wav"
- else:
- audio_path = this_dir / "system" / "at_sounds" / "deepspeeddisabled.wav"
-
- url = f"{base_url}/api/deepspeed?new_deepspeed_value={deepspeed_param}"
- headers = {"Content-Type": "application/json"}
- response = requests.post(url, headers=headers)
- response.raise_for_status() # Raises an HTTPError for bad responses
- json_response = response.json()
- # Check if the deepspeed request was successful
- if json_response.get("status") == "deepspeed-success":
- # Update any relevant variables or perform other actions on success
- params["tts_model_loaded"] = True
- return f''
- except requests.exceptions.RequestException as e:
- # Handle the HTTP request error
- print(
- f"[{params['branding']}Server] \033[91mWarning\033[0m Error during request to webserver process: {e}"
- )
- return {"status": "error", "message": str(e)}
-
-
-# DEEPSPEED - Display DeepSpeed Checkbox Yes or No
-deepspeed_condition = params["tts_method_xtts_local"] == "True" and deepspeed_installed
-
-
-#############################################################
-#### TTS STRING CLEANING & PROCESSING PRE SENDING TO TTS ####
-#############################################################
-def new_split_into_sentences(self, text):
- sentences = self.seg.segment(text)
- if params["remove_trailing_dots"]:
- sentences_without_dots = []
- for sentence in sentences:
- if sentence.endswith(".") and not sentence.endswith("..."):
- sentence = sentence[:-1]
-
- sentences_without_dots.append(sentence)
-
- return sentences_without_dots
- else:
- return sentences
-
-
-Synthesizer.split_into_sentences = new_split_into_sentences
-
-
-# Check model is loaded and string isnt empty, before sending a TTS request.
-def before_audio_generation(string, params):
- # Check Model is loaded into cuda or cpu and error if not
- if not params["tts_model_loaded"]:
- print(
- f"[{params['branding']}Model] \033[91mWarning\033[0m Model is still loading, please wait before trying to generate TTS"
- )
- return
- string = html.unescape(string) or random_sentence()
- if string == "":
- return "*Empty string*"
- return string
-
-
-##################
-#### Narrator ####
-##################
-def combine(audio_files, output_folder, state):
- audio = np.array([])
-
- for audio_file in audio_files:
- audio_data, sample_rate = sf.read(audio_file)
- # Ensure all audio files have the same sample rate
- if audio.size == 0:
- audio = audio_data
- else:
- audio = np.concatenate((audio, audio_data))
-
- # Save the combined audio to a file with a specified sample rate
- if "character_menu" in state:
- output_file_path = os.path.join(
- output_folder, f'{state["character_menu"]}_{int(time.time())}_combined.wav'
- )
- else:
- output_file_path = os.path.join(
- output_folder, f"TTSOUT_{int(time.time())}_combined.wav"
- )
- sf.write(output_file_path, audio, samplerate=sample_rate)
- # Clean up unnecessary files
- for audio_file in audio_files:
- os.remove(audio_file)
-
- return output_file_path
-
-
-################################
-#### TTS PREVIEW GENERATION ####
-################################
-# PREVIEW VOICE - Generate Random Sentence if Voice Preview box is empty
-def random_sentence():
- with open(this_dir / "system" / "config" / "harvard_sentences.txt") as f:
- return random.choice(list(f))
-
-
-# PREVIEW VOICE- Generate TTS Function
-def voice_preview(string):
- if not params["activate"]:
- return string
- # Clean the string, capture model not loaded, and move model to cuda if needed
- cleaned_string = before_audio_generation(string, params)
- if cleaned_string is None:
- return
- string = cleaned_string
- # Setup the output file
- output_file = Path(params["output_folder_wav"]) / "voice_preview.wav"
- # Generate the audio
- language_code = languages.get(params["language"])
- temperature = params["local_temperature"]
- repetition_penalty = params["local_repetition_penalty"]
- # Convert the WindowsPath object to a string before using it in JSON payload
- output_file_str = output_file.as_posix()
- # Lock before making the generate request
- with process_lock:
- generate_response = send_generate_request(
- string,
- params["voice"],
- language_code,
- temperature,
- repetition_penalty,
- output_file_str,
- )
- # Check if lock is already acquired
- if process_lock.locked():
- print(
- f"[{params['branding']}Model] \033[91mWarning\033[0m Audio generation is already in progress. Please wait."
- )
- return
- if generate_response.get("status") == "generate-success":
- # Handle Gradio and playback
- autoplay = "autoplay" if params["autoplay"] else ""
- return f''
- else:
- # Handle the case where audio generation was not successful
- return f"[{params['branding']}Server] Audio generation failed. Status: {generate_response.get('status')}"
-
-
-#######################
-#### TEXT CLEANING ####
-#######################
-
-def process_text(text):
- # Normalize HTML encoded quotes
- text = html.unescape(text)
-
- # Replace ellipsis with a single dot
- text = re.sub(r"\.{3,}", ".", text)
-
- # Pattern to identify combined narrator and character speech
- combined_pattern = r'(\*[^*"]+\*|"[^"*]+")'
-
- # List to hold parts of speech along with their type
- ordered_parts = []
-
- # Track the start of the next segment
- start = 0
-
- # Find all matches
- for match in re.finditer(combined_pattern, text):
- # Add the text before the match, if any, as ambiguous
- if start < match.start():
- ambiguous_text = text[start : match.start()].strip()
- if ambiguous_text:
- ordered_parts.append(("ambiguous", ambiguous_text))
-
- # Add the matched part as either narrator or character
- matched_text = match.group(0)
- if matched_text.startswith("*") and matched_text.endswith("*"):
- ordered_parts.append(("narrator", matched_text.strip("*").strip()))
- elif matched_text.startswith('"') and matched_text.endswith('"'):
- ordered_parts.append(("character", matched_text.strip('"').strip()))
- else:
- # In case of mixed or improperly formatted parts
- if "*" in matched_text:
- ordered_parts.append(("narrator", matched_text.strip("*").strip('"')))
- else:
- ordered_parts.append(("character", matched_text.strip('"').strip("*")))
-
- # Update the start of the next segment
- start = match.end()
-
- # Add any remaining text after the last match as ambiguous
- if start < len(text):
- ambiguous_text = text[start:].strip()
- if ambiguous_text:
- ordered_parts.append(("ambiguous", ambiguous_text))
-
- return ordered_parts
-
-
-########################
-#### IMAGE CLEANING ####
-########################
-
-img_pattern = r']*src\s*=\s*["\'][^"\'>]+["\'][^>]*>'
-
-def extract_and_remove_images(text):
- """
- Extracts all image data from the text and removes it for clean TTS processing.
- Returns the cleaned text and the extracted image data.
- """
- img_matches = re.findall(img_pattern, text)
- img_info = "\n".join(img_matches) # Store extracted image data
- cleaned_text = re.sub(img_pattern, '', text) # Remove images from text
- return cleaned_text, img_info
-
-def reinsert_images(text, img_info):
- """
- Reinserts the previously extracted image data back into the text.
- """
- if img_info: # Check if there are images to reinsert
- text += f"\n\n{img_info}"
- return text
-
-#################################
-#### TTS STANDARD GENERATION ####
-#################################
-# STANDARD VOICE - Generate TTS Function
-def output_modifier(string, state):
- if not params["activate"]:
- return string
- img_info = ""
- cleaned_text, img_info = extract_and_remove_images(string)
- # print("Cleaned STRING IS:", cleaned_text)
- cleaned_string = before_audio_generation(cleaned_text, params)
- if cleaned_string is None:
- return
- language_code = languages.get(params["language"])
- temperature = params["local_temperature"]
- repetition_penalty = params["local_repetition_penalty"]
- # Create a list to store generated audio paths
- audio_files = []
- if process_lock.acquire(blocking=False):
- try:
- if params["narrator_enabled"]:
- processed_parts = process_text(cleaned_string)
-
- audio_files_all_paragraphs = []
- for part_type, part in processed_parts:
- # Skip parts that are too short
- if len(part.strip()) <= 3:
- continue
-
- # Determine the voice to use based on the part type
- if part_type == "narrator":
- voice_to_use = params["narrator_voice"]
- print(
- f"[{params['branding']}TTSGen] \033[92mNarrator\033[0m"
- ) # Green
- elif part_type == "character":
- voice_to_use = params["voice"]
- print(
- f"[{params['branding']}TTSGen] \033[36mCharacter\033[0m"
- ) # Yellow
- else:
- # Handle ambiguous parts based on user preference
- voice_to_use = (
- params["voice"]
- if non_quoted_text_is
- else params["narrator_voice"]
- )
- voice_description = (
- "\033[36mCharacter (Text-not-inside)\033[0m"
- if non_quoted_text_is
- else "\033[92mNarrator (Text-not-inside)\033[0m"
- )
- print(f"[{params['branding']}TTSGen] {voice_description}")
-
- # Replace multiple exclamation marks, question marks, or other punctuation with a single instance
- cleaned_part = re.sub(r"([!?.])\1+", r"\1", part)
- # Further clean to remove any other unwanted characters
- cleaned_part = re.sub(r'[^a-zA-Z0-9\s\.,;:!?\-\'"\u0100-\u017F\u0400-\u04FFÀ-ÿ\u0150\u0151\u0170\u0171\u4E00-\u9FFF\u3400-\u4DBF\u20000-\u2A6DF\u3000-\u303F\$]', '', cleaned_part)
- # Remove all newline characters (single or multiple)
- cleaned_part = re.sub(r"\n+", " ", cleaned_part)
-
- # Generate TTS and output to a file
- output_filename = get_output_filename(state)
- generate_response = send_generate_request(
- cleaned_part,
- voice_to_use,
- language_code,
- temperature,
- repetition_penalty,
- output_filename,
- )
- audio_path = generate_response.get("data", {}).get("audio_path")
- audio_files_all_paragraphs.append(audio_path)
-
- # Combine audio files across paragraphs
- final_output_file = combine(
- audio_files_all_paragraphs, params["output_folder_wav"], state
- )
- else:
- # Decode HTML entities first
- cleaned_part = html.unescape(cleaned_string)
- # Replace multiple instances of certain punctuation marks with a single instance
- cleaned_part = re.sub(r"([!?.])\1+", r"\1", cleaned_part)
- # Further clean to remove any other unwanted characters
- cleaned_part = re.sub(r'[^a-zA-Z0-9\s\.,;:!?\-\'"\u0100-\u017F\u0400-\u04FFÀ-ÿ\u0150\u0151\u0170\u0171\u4E00-\u9FFF\u3400-\u4DBF\u20000-\u2A6DF\u3000-\u303F\$]', '', cleaned_part)
- # Remove all newline characters (single or multiple)
- cleaned_part = re.sub(r"\n+", " ", cleaned_part)
- # Process the part and give it a non-character name if being used vai API or standalone.
- if "character_menu" in state:
- output_file = Path(
- f'{params["output_folder_wav"]}/{state["character_menu"]}_{int(time.time())}.wav'
- )
- else:
- output_file = Path(
- f'{params["output_folder_wav"]}/TTSOUT_{int(time.time())}.wav'
- )
- output_file_str = output_file.as_posix()
- output_file = get_output_filename(state)
- generate_response = send_generate_request(
- cleaned_part,
- params["voice"],
- language_code,
- temperature,
- repetition_penalty,
- output_file_str,
- )
- audio_path = generate_response.get("data", {}).get("audio_path")
- final_output_file = audio_path
- finally:
- # Always release the lock, whether an exception occurs or not
- process_lock.release()
- else:
- # The lock is already acquired
- print(
- f"[{params['branding']}Model] \033[91mWarning\033[0m Audio generation is already in progress. Please wait."
- )
- return
-
- if generate_response.get("status") == "generate-success":
- audio_path = generate_response.get("data", {}).get("audio_path")
- if audio_path:
- # Handle Gradio and playback
- autoplay = "autoplay" if params["autoplay"] else ""
- string = (f'')
- if params["show_text"]:
- string += reinsert_images(cleaned_string, img_info)
- shared.processing_message = "*Is typing...*"
- return string
- else:
- print(f"[{params['branding']}Server] \033[91mWarning\033[0m No audio path in the response.")
- else:
- print(f"[{params['branding']}Server] \033[91mWarning\033[0m Audio generation failed. Status:", generate_response.get("message"),)
-
-def get_output_filename(state):
- if "character_menu" in state:
- return Path(
- f'{params["output_folder_wav"]}/{state["character_menu"]}_{str(uuid.uuid4())[:8]}.wav'
- ).as_posix()
- else:
- return Path(
- f'{params["output_folder_wav"]}/TTSOUT_{str(uuid.uuid4())[:8]}.wav'
- ).as_posix()
-
-
-###############################################
-#### SEND GENERATION REQUEST TO TTS ENGINE ####
-###############################################
-def send_generate_request(
- text, voice, language, temperature, repetition_penalty, output_file
-):
- url = f"{base_url}/api/generate"
- payload = {
- "text": text,
- "voice": voice,
- "language": language,
- "temperature": temperature,
- "repetition_penalty": repetition_penalty,
- "output_file": output_file,
- }
- headers = {"Content-Type": "application/json"}
- response = requests.post(url, json=payload, headers=headers)
- return response.json()
-
-
-################################
-#### SUBPORCESS TERMINATION ####
-################################
-# Register the termination code to be executed at exit
-atexit.register(lambda: process.terminate() if process.poll() is None else None)
-
-
-######################
-#### GRADIO STUFF ####
-######################
-def state_modifier(state):
- if not params["activate"]:
- return state
-
- state["stream"] = False
- return state
-
-
-def update_narrator_enabled(value):
- if value == "Enabled":
- params["narrator_enabled"] = True
- elif value == "Disabled":
- params["narrator_enabled"] = False
-
-
-def update_non_quoted_text_is(value):
- global non_quoted_text_is
- if value == "Narrator":
- non_quoted_text_is = False
- elif value == "Char":
- non_quoted_text_is = True
-
-
-def input_modifier(string, state):
- if not params["activate"]:
- return string
-
- shared.processing_message = "*Is recording a voice message...*"
- return string
-
-
-def ui():
- with gr.Accordion(params["branding"] + " TTS (XTTSv2)"):
- # Activate alltalk_tts, Enable autoplay, Show text
- with gr.Row():
- activate = gr.Checkbox(value=params["activate"], label="Enable TTS")
- autoplay = gr.Checkbox(value=params["autoplay"], label="Autoplay TTS")
- show_text = gr.Checkbox(value=params["show_text"], label="Show Text")
-
- # Low vram enable, Deepspeed enable, Remove trailing dots
- with gr.Row():
- low_vram = gr.Checkbox(
- value=params["low_vram"], label="Enable Low VRAM Mode"
- )
- low_vram_play = gr.HTML(visible=False)
- deepspeed_checkbox = gr.Checkbox(
- value=params["deepspeed_activate"],
- label="Enable DeepSpeed",
- visible=deepspeed_installed,
- )
- deepspeed_checkbox_play = gr.HTML(visible=False)
- remove_trailing_dots = gr.Checkbox(
- value=params["remove_trailing_dots"], label='Remove trailing "."'
- )
-
- # TTS method, Character voice selection
- with gr.Row():
- model_loader_choices = ["API TTS", "API Local", "XTTSv2 Local"]
- if finetuned_model:
- model_loader_choices.append("XTTSv2 FT")
- tts_radio_buttons = gr.Radio(
- choices=model_loader_choices,
- label="TTS Method (Each method sounds slightly different)",
- value=gr_modelchoice, # Set the default value
- )
- tts_radio_buttons_play = gr.HTML(visible=False)
- with gr.Row():
- available_voices = get_available_voices()
- default_voice = params[
- "voice"
- ] # Check if the default voice is in the list of available voices
-
- if default_voice not in available_voices:
- default_voice = available_voices[
- 0
- ] # Choose the first available voice as the default
- # Add allow_custom_value=True to the Dropdown
- voice = gr.Dropdown(
- available_voices,
- label="Character Voice",
- value=default_voice,
- allow_custom_value=True,
- )
- create_refresh_button(
- voice,
- lambda: None,
- lambda: {
- "choices": get_available_voices(),
- "value": params["voice"],
- },
- "refresh-button",
- )
-
- # Language, Narrator voice
- with gr.Row():
- language = gr.Dropdown(
- languages.keys(), label="Language", value=params["language"]
- )
- with gr.Row():
- narrator_voice_gr = gr.Dropdown(
- get_available_voices(),
- label="Narrator Voice",
- allow_custom_value=True,
- value=params["narrator_voice"],
- )
- create_refresh_button(
- narrator_voice_gr,
- lambda: None,
- lambda: {
- "choices": get_available_voices(),
- "value": params["narrator_voice"],
- },
- "refresh-button",
- )
-
- # Temperature, Repetition Penalty
- with gr.Row():
- local_temperature_gr = gr.Slider(
- minimum=0.05,
- maximum=1,
- step=0.05,
- label="Temperature",
- value=params["local_temperature"],
- )
- local_repetition_penalty_gr = gr.Slider(
- minimum=0.5,
- maximum=20,
- step=0.5,
- label="Repetition Penalty",
- value=params["local_repetition_penalty"],
- )
-
- # Narrator enable, Non quoted text, Explanation text
- with gr.Row():
- with gr.Row():
- narrator_enabled_gr = gr.Radio(
- choices={"Enabled": "true", "Disabled": "false"},
- label="Narrator",
- value="Enabled" if params.get("narrator_enabled") else "Disabled",
- )
- non_quoted_text_is_gr = gr.Radio(
- choices={"Character": "true", "Narrator": "false"},
- label='Unmarked text NOT inside of * or " is',
- value="Character" if non_quoted_text_is else "Narrator",
- )
- explanation_text = gr.HTML(
- f"
⚙️ Settings and Documentation Page⚙️
- Low VRAM Mode and Deepspeed take 15 seconds to be enabled or disabled.
- The DeepSpeed checkbox is only visible if DeepSpeed is present.