New Narrator Version

The narrator and filtering has been re-written to vastly improve filtering of text, resulting in less unwanted sounds and better control over narrator/character.
This commit is contained in:
erew123 authored and GitHub committed 2023-12-13 04:51:41 +00:00
1 parent 53b314d127
commit 40b775459e
3 files changed
+123 -69

No files matched your search

+110 -64
View File
@@ -48,7 +48,6 @@ except ModuleNotFoundError:
# IMPORT - Attempt Importing DeepSpeed (required for displaying Deepspeed checkbox in gradio) # IMPORT - Attempt Importing DeepSpeed (required for displaying Deepspeed checkbox in gradio)
try: try:
import deepspeed import deepspeed
deepspeed_installed = True deepspeed_installed = True
except ImportError: except ImportError:
deepspeed_installed = False deepspeed_installed = False
@@ -155,8 +154,8 @@ elif params["tts_method_api_local"] == True:
elif params["tts_method_xtts_local"] == True: elif params["tts_method_xtts_local"] == True:
gr_modelchoice = "XTTSv2 Local" gr_modelchoice = "XTTSv2 Local"
gr_narrator_enabled = str(params["narrator_enabled"]).lower() # Set the default for Narrated text without asterisk or quotes to be Narrator
non_quoted_text_is = True
###################### ######################
#### GRADIO STUFF #### #### GRADIO STUFF ####
@@ -289,6 +288,21 @@ def send_reload_request(tts_method):
if json_response.get("status") == "model-success": if json_response.get("status") == "model-success":
# Update model_loaded to True if the reload was successful # Update model_loaded to True if the reload was successful
params["model_loaded"] = True params["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
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
elif tts_method == "XTTSv2 Local":
params["tts_method_api_tts"] = False
params["tts_method_api_local"] = False
params["tts_method_xtts_local"] = True
return json_response return json_response
except requests.exceptions.RequestException as e: except requests.exceptions.RequestException as e:
# Handle the HTTP request error # Handle the HTTP request error
@@ -402,11 +416,11 @@ def combine(audio_files, output_folder, state):
audio = np.concatenate((audio, audio_data)) audio = np.concatenate((audio, audio_data))
# Save the combined audio to a file with a specified sample rate # Save the combined audio to a file with a specified sample rate
output_file_path = os.path.join( if "character_menu" in state:
output_folder, f'{state["character_menu"]}_{int(time.time())}_combined.wav' 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) sf.write(output_file_path, audio, samplerate=sample_rate)
print(f"[{params['branding']}TTSGen] Narrated audio generated")
# Clean up unnecessary files # Clean up unnecessary files
for audio_file in audio_files: for audio_file in audio_files:
os.remove(audio_file) os.remove(audio_file)
@@ -463,7 +477,6 @@ def voice_preview(string):
################################# #################################
# STANDARD VOICE - Generate TTS Function # STANDARD VOICE - Generate TTS Function
def output_modifier(string, state): def output_modifier(string, state):
# DEBUG print("THE ORIGINAL STRING IS:", string,"\n")
if not params["activate"]: if not params["activate"]:
return string return string
original_string = string original_string = string
@@ -477,65 +490,88 @@ def output_modifier(string, state):
if process_lock.acquire(blocking=False): if process_lock.acquire(blocking=False):
try: try:
if params["narrator_enabled"]: if params["narrator_enabled"]:
processed_string = original_string # Do Some basic stripping and remove any double CR's
processed_string = processed_string.replace("***", "*") processed_string = (
processed_string = processed_string.replace("**", "*") original_string
processed_string = processed_string.replace(".*", "*") .replace("***", "*")
processed_string = processed_string.replace(".'", "'") .replace("**", "*")
processed_string = processed_string.replace(".'", "'") .replace("\n\n", "\n")
# Remove new lines )
processed_string = processed_string.replace("\n", " ") # Clean up two asterisks (narrators) being made between a newline, making it one sentence.
#DEBUG print("PROCESSED STRING IS NOW:", processed_string) processed_string = re.sub(r'\.\*\n\*', '. ', processed_string)
# Split the processed string into lines # Clean up a few other bits.
lines = processed_string.split("\n") processed_string = (
processed_string
.replace("'", "'")
.replace("\n", " ")
# Add special characters to the quote so that we can use it to identify things later after its been split
.replace('&quot;', '&quot;<')
# Capture new conversations which wont have things like &quote in them
.replace('"', '&quot;<')
)
# Set up a tracking of the individual wav files.
audio_files_all_paragraphs = [] audio_files_all_paragraphs = []
is_narrator = False # Split the line using &quot; and ".* " (so end of sentences, leaving special characters added to the start of all OTHER sentences, bar possibly the first one if its starting with a *
for line in lines: parts = re.split(r'&quot;|\.\*', processed_string)
# Decode HTML entities audio_files_paragraph = []
decoded_text = html.unescape(line) for i, part in enumerate(parts):
# Initialize variables to track narrator and voice parts # Skip parts that are too short
is_narrator = "*" in line or is_narrator if len(part.strip()) <= 1:
# Split the line using double quotes continue
parts = re.split(r'"', decoded_text) # Figure out which type of line it is, then replace characters as necessary to avoid TTS trying to pronunce them, htmlunescape after.
audio_files_paragraph = [] # Character will always be a < with a letter immediately after it
for i, part in enumerate(parts): if '<' in part and '< *' not in part and '<*' not in part and '< *' not in part and '< ' not in part and '< ' not in part:
# Skip parts that are too short cleaned_part = html.unescape(part.replace('<', ''))
if len(part.strip()) <= 1: voice_to_use = params["voice"]
continue #Narrator will always be am * or < with an * a position or two after it.
# Determine if it's a narrator or voice part within double quotes elif '<*' in part or '< *' in part or '< *' in part or '*' in part:
is_narrator_part = is_narrator if i % 2 == 0 else False cleaned_part = html.unescape(part.replace('<*', '').replace('< *', '').replace('< *', '').replace('*', ''))
voice_to_use = ( voice_to_use = params["narrator_voice"]
params["narrator_voice"] if is_narrator_part else params["voice"] #If the other two dont capture it, aka, the AI gave no * or &quot; on the line, use non_quoted_text_is aka user interface, user can choose Char or Narrator
) elif non_quoted_text_is:
# DEBUG print(f"THE STRING TO BE USED: {voice_to_use} {part.strip()}") cleaned_part = html.unescape(part.replace('< ', '').replace('< ', '').replace('< ', ''))
# Process the part voice_to_use = params["voice"]
output_file = Path( else:
f'{params["output_folder_wav"]}/{state["character_menu"]}_{int(time.time())}_{i}.wav' cleaned_part = html.unescape(part.replace('< ', '').replace('< ', '').replace('< ', ''))
) voice_to_use = params["narrator_voice"]
output_file_str = output_file.as_posix() # Check if character name exists and if not, just call it TTSOUT_
generate_response = send_generate_request( if "character_menu" in state:
part, voice_to_use, language_code, output_file_str output_file = Path(f'{params["output_folder_wav"]}/{state["character_menu"]}_{int(time.time())}_{i}.wav')
) else:
audio_path = generate_response.get("data", {}).get("audio_path") output_file = Path(f'{params["output_folder_wav"]}/TTSOUT_{int(time.time())}_{i}.wav')
audio_files_paragraph.append(audio_path) # Generate that TTS and output to a file
is_narrator_part = is_narrator if i % 2 == 0 else False output_file_str = output_file.as_posix()
voice_to_use = ( generate_response = send_generate_request(
params["narrator_voice"] if is_narrator_part else params["voice"] cleaned_part, voice_to_use, language_code, output_file_str
) )
# Accumulate audio files within the paragraph audio_path = generate_response.get("data", {}).get("audio_path")
audio_files_all_paragraphs.extend(audio_files_paragraph) audio_files_paragraph.append(audio_path)
# Accumulate audio files within the paragraph
audio_files_all_paragraphs.extend(audio_files_paragraph)
# Combine audio files across paragraphs # Combine audio files across paragraphs
final_output_file = combine( final_output_file = combine(
audio_files_all_paragraphs, params["output_folder_wav"], state audio_files_all_paragraphs, params["output_folder_wav"], state
) )
else: else:
output_file = Path( processed_string = (
f'{params["output_folder_wav"]}/{state["character_menu"]}_{int(time.time())}.wav' original_string
.replace("***", "")
.replace("**", "")
.replace("*", "")
.replace("\n\n", "\n")
.replace("&#x27;", "'")
) )
cleaned_part = html.unescape(processed_string)
# 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_str = output_file.as_posix()
output_file = get_output_filename(state) output_file = get_output_filename(state)
generate_response = send_generate_request( generate_response = send_generate_request(
string, params["voice"], language_code, output_file_str cleaned_part, params["voice"], language_code, output_file_str
) )
audio_path = generate_response.get("data", {}).get("audio_path") audio_path = generate_response.get("data", {}).get("audio_path")
final_output_file = audio_path final_output_file = audio_path
@@ -620,6 +656,12 @@ def update_narrator_enabled(value):
elif value == "Disabled": elif value == "Disabled":
params["narrator_enabled"] = False 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): def input_modifier(string, state):
if not params["activate"]: if not params["activate"]:
@@ -702,11 +744,16 @@ def ui():
}, },
"refresh-button", "refresh-button",
) )
narrator_enabled_gr = gr.Radio( narrator_enabled_gr = gr.Radio(
choices={"Enabled": "true", "Disabled": "false"}, choices={"Enabled": "true", "Disabled": "false"},
label="Narrator Activation", label="Narrator Activation",
value="Enabled" if gr_narrator_enabled == "true" else "Disabled", value="Enabled" if params.get("narrator_enabled") else "Disabled",
) )
non_quoted_text_is_gr = gr.Radio(
choices={"Char": "true", "Narrator": "false"},
label='Text NOT inside of * or " is',
value="Char" if non_quoted_text_is else "Narrator",
)
with gr.Row(): with gr.Row():
low_vram = gr.Checkbox( low_vram = gr.Checkbox(
@@ -807,9 +854,8 @@ def ui():
# Narrator selection actions # Narrator selection actions
narrator_enabled_gr.change(update_narrator_enabled, narrator_enabled_gr, None) narrator_enabled_gr.change(update_narrator_enabled, narrator_enabled_gr, None)
narrator_voice_gr.change( non_quoted_text_is_gr.change(update_non_quoted_text_is, non_quoted_text_is_gr, None)
lambda x: params.update({"narrator_voice": x}), narrator_voice_gr, None narrator_voice_gr.change(lambda x: params.update({"narrator_voice": x}), narrator_voice_gr, None)
)
# Play preview # Play preview
preview_text.submit(voice_preview, preview_text, preview_audio) preview_text.submit(voice_preview, preview_text, preview_audio)
Binary file not shown.

After

Width:  |  Height:  |  Size: 156 KiB

+13 -5
View File
@@ -1,5 +1,4 @@
import json import json
import random
import time import time
import os import os
from pathlib import Path from pathlib import Path
@@ -15,13 +14,12 @@ from fastapi import (
FastAPI, FastAPI,
Form, Form,
Request, Request,
HTTPException,
BackgroundTasks,
Response, Response,
Depends, Depends,
) )
from fastapi.responses import JSONResponse, HTMLResponse, RedirectResponse from fastapi.responses import JSONResponse, HTMLResponse, RedirectResponse
from fastapi.templating import Jinja2Templates from fastapi.templating import Jinja2Templates
from fastapi.staticfiles import StaticFiles
from contextlib import asynccontextmanager from contextlib import asynccontextmanager
########################### ###########################
@@ -511,7 +509,6 @@ def list_files(directory):
] ]
return files return files
############################# #############################
#### JSON CONFIG UPDATER #### #### JSON CONFIG UPDATER ####
############################# #############################
@@ -541,7 +538,8 @@ async def get_settings(request: Request):
"wav_files": wav_files, "wav_files": wav_files,
}, },
) )
# Define an endpoint to serve static files
app.mount("/static", StaticFiles(directory=str(this_dir / "templates")), name="static")
@app.post("/update-settings") @app.post("/update-settings")
async def update_settings( async def update_settings(
@@ -716,6 +714,7 @@ simple_webpage = """
<li><a href="#getting-started">Getting Started with AllTalk TTS</a></li> <li><a href="#getting-started">Getting Started with AllTalk TTS</a></li>
<li><a href="#server-information">Server Information</a></li> <li><a href="#server-information">Server Information</a></li>
<li><a href="#using-voice-samples">Using Voice Samples</a></li> <li><a href="#using-voice-samples">Using Voice Samples</a></li>
<li><a href="#text-not-inside">Text Not inside function</a></li>
<li><a href="#local-model-temperature-and-repetition-settings">Local Model temperature and repetition settings</a></li> <li><a href="#local-model-temperature-and-repetition-settings">Local Model temperature and repetition settings</a></li>
<li><a href="#where-are-the-outputs-stored">Automatic output wav file deletion</a></li> <li><a href="#where-are-the-outputs-stored">Automatic output wav file deletion</a></li>
<li><a href="#low-vram-option-overview">Low VRAM Overview</a></li> <li><a href="#low-vram-option-overview">Low VRAM Overview</a></li>
@@ -768,6 +767,15 @@ simple_webpage = """
</ul> </ul>
<p><a href="#toc">Back to top of page</a></p> <p><a href="#toc">Back to top of page</a></p>
<h3 id="text-not-inside"><strong>Text Not inside function</strong></h3>
<p>When using the Narrator function, most AI models should be using asterisks or double quotes to differentiate between the Narrator or the Character, however, many models sometimes switch between using asterisks and double quotes or nothing at all. This leaves a bit of a mess because sometimes that text is narration and sometimes its the character talking and there's no clear way to know where to split sentences. There is no 100&percnt; solution at the moment.</p>
<p>Most models usually lean more way than the other as to it is narration or the character talking when it does this. So you can use the "<b>Text NOT inside of * or &quot; is</b>" function to decide what the TTS engine should do in situations like this. It's an either/or situation. It will either use the Character voice or the narrator voice when text is not inside asterisks or double quotes.</p>
<div style="text-align: center;">
<img src="/static/textnotinside.jpg" alt="When the AI doesnt use an asterisk or a quote">
</div>
<p><a href="#toc">Back to top of page</a></p>
<h3 id="local-model-temperature-and-repetition-settings"><strong>Local Model Temperature and Repetition Settings</strong></h3> <h3 id="local-model-temperature-and-repetition-settings"><strong>Local Model Temperature and Repetition Settings</strong></h3>
<p><strong>Caution:</strong> It is recommended not to modify these settings unless you fully comprehend their effects. A general overview is provided below for reference.</p> <p><strong>Caution:</strong> It is recommended not to modify these settings unless you fully comprehend their effects. A general overview is provided below for reference.</p>