From 633e14d1531b5ee61e19f83c0c5a1e5406326cc9 Mon Sep 17 00:00:00 2001
From: erew123 <35898566+erew123@users.noreply.github.com>
Date: Tue, 13 Aug 2024 13:34:20 +0100
Subject: [PATCH 01/38] Add files via upload
---
system/gradio_pages/api_documentation.py | 1167 +++++++++++-----------
1 file changed, 588 insertions(+), 579 deletions(-)
diff --git a/system/gradio_pages/api_documentation.py b/system/gradio_pages/api_documentation.py
index ec42b32..49844ee 100644
--- a/system/gradio_pages/api_documentation.py
+++ b/system/gradio_pages/api_documentation.py
@@ -1,580 +1,589 @@
-import gradio as gr
-
-def api_documentation():
- best_practices = """
- ### ๐ Best Practices for writing an extension
-
- You will need to store the IP address and Port (minimum) that your extension will be connecting to. Potentially also http/https if you wish to use AllTalk over tunnels. Additionally, you may also wish to store a connection timeout value.
-
- ```
- {
- "api_alltalk_protocol": "http://",
- "api_alltalk_ip_port": "127.0.0.1:7851",
- "api_connection_timeout": 5
- }
- ```
- When your extension starts up you will need to connect to the IP/Port and check the **/api/ready** end point for a 'Ready' status. You may wish to do this on a loop for the period of the connection timeout.
-
- If the timeout is reached without a response, you can set voices, models available, models loaded, basically whatever values you are going to store/present to the user as "Server Offline" or whatever terminology you wish.
-
- If the connection is available, then you would:
-
- > 1) Pull the current settings and populate those into a paramaters variabele. **/api/currentsettings**
- 2) Pull the current voices and populate those into a paramaters variabele. **/api/voices**
- 3) Pull the current rvcvoices and populate those into a paramaters variabele. **/api/rvcvoices**
-
- You can now present those to the user interface, choosing if you are just going to present the voices (narrator or not), RVC voices, model loaded, models available etc.
-
- If you are going to present a multitude of options, please abide by the **xxxxxx_capable** settings provided by **/api/currentsettings**, disabling or enabling options in your user interface as the loaded in TTS engine does or doesnt allow for e.g. if the current TTS engine doesnt support changing your generation audio speed, it will send **"generationspeed_capable": false** over when you use **/api/currentsettings**. As such, you should disable the option to change the generation speed within your interface, however, ultimately, even if not disabled, if the current TTS engine loaded isnt capable of speed generation, changing the generation speed will have no effect on the resulting TTS generation, but you may end up with users asking why!
-
- It is also advisable to have a refresh button to pull make AllTalk update its settings **/api/reload_config** and then you can pull current settings & voices again. This ensures the use always has a way to reconnect to the AllTalk server if its unavailable when your extension starts, or in the scenario that something else was changed in the Gradio interface and now the TTS Engine loaded in is different from when your extension started up.
-
- You can obviously choose if you will give the user access to change the model loaded, TTS engine etc. Please read the documentation on the API endpoints for more information.
-
- If you are presenting all the variables/options to the user, then when you send off a generation request to **/api/tts-generate** send all of the variable/options that are stored within your interface.
-
- If you are NOT presenting all the variables/options to the user, then you have 2x options when sending to **/api/tts-generate**:
-
- > 1) Send all the values/options but hard code some settings in your outbound generation request. This will ensure you always get the TTS generation result you want. Please read the **/api/tts-generate** section for details.
-
- OR
-
- >2) Dont send all the values/options in the generation request. In this scenario, the AllTalk server will populate missing settings from the global API Default Settings and the current TTS Engine's settings that are loaded in. e.g. if you dont provide a character voice, the default set character voice of whatever TTS engine is loaded in will be used. The same goes for all the other settings, except the actual text of the generation request.
-
- Finally, depending on how you are storing your settings, for your extension, you may wish to save the settings each time there is a successful generation request. This ensures that the settings are valid and the connected IP address/port are correct.
- """
-
- settings_endpoints = """
- ### ๐ Ready Endpoint
- Check if the Text-to-Speech (TTS) engine has started and is ready to accept requests. This endpoint is useful when performing reload model/engine requests as it will return "Unloaded" while the engine is restarting. You can poll this endpoint every second or so to confirm a "Ready" status, indicating AllTalk is ready to process requests. Note that if there is an issue loading the selected model (e.g., missing from the file system), the endpoint will still return "Ready". This status only indicates that the engine has loaded.
-
- **URL**: `http://{ipaddress}:{port}/api/ready`
- **Method**: `GET`
-
- > `curl -X GET "http://127.0.0.1:7851/api/ready"`
-
- Response: `Ready` or `Unloaded`
-
- ### ๐ Standard Voices List Endpoint
- Retrieve a list of available voices for generating speech that the currently loaded TTS engine and model supports. If the currently loaded TTS engine does not load a model directly (e.g., Piper), the models themselves will be displayed as voices.
-
- **URL**: `http://{ipaddress}:{port}/api/voices`
- **Method**: `GET`
-
- > `curl -X GET "http://127.0.0.1:7851/api/voices"`
-
- ```
- {
- "status": "success",
- "voices": ["voice1", "voice2", "voice3"]
- }
-
- ```
-
- ### ๐ RVC Voices List Endpoint
- Retrieve a list of available RVC voices for further processing your TTS with the RVC pipeline. `Disabled` will always be included in the list. When `Disabled` is used, the RVC pipeline will not be utilized. If the RVC pipeline is globally disabled in AllTalk, `Disabled` will be the only item in the list. Index files matching their RVC voices will not be displayed; if an index file exists for an RVC voice, it will be automatically selected during generation. If multiple index files exist and it is impossible to automatically select one, a message will be output in the console.
-
- **URL**: `http://{ipaddress}:{port}/api/rvcvoices`
- **Method**: `GET`
-
- > `curl -X GET "http://127.0.0.1:7851/api/rvcvoices"`
-
- ```
- {
- "status": "success",
- "voices": ["Disabled", "folder1\\voice1.pth", "folder2\\voice2.pth", "folder3\\voice3.pth"]
- }
- ```
-
- ### ๐ Current Settings Endpoint
- Retrieve the current settings of the TTS engine.
-
- **URL**: `http://{ipaddress}:{port}/api/currentsettings`
- **Method**: `GET`
-
- > `curl -X GET "http://127.0.0.1:7851/api/currentsettings"`
-
-
- ```
- {
- "engines_available": ["parler", "piper", "vits", "xtts"],
- "current_engine_loaded": "xtts",
- "models_available": [
- {"name": "xtts - xttsv2_2.0.2"},
- {"name": "apitts - xttsv2_2.0.2"},
- {"name": "xtts - xttsv2_2.0.3"},
- {"name": "apitts - xttsv2_2.0.3"}
- ],
- "current_model_loaded": "xtts - xttsv2_2.0.3",
- "manufacturer_name": "Coqui",
- "audio_format": "wav",
- "deepspeed_capable": true,
- "deepspeed_available": true,
- "deepspeed_enabled": true,
- "generationspeed_capable": true,
- "generationspeed_set": 1,
- "lowvram_capable": true,
- "lowvram_enabled": false,
- "pitch_capable": false,
- "pitch_set": 0,
- "repetitionpenalty_capable": true,
- "repetitionpenalty_set": 10,
- "streaming_capable": true,
- "temperature_capable": true,
- "temperature_set": 0.75,
- "ttsengines_installed": true,
- "languages_capable": true,
- "multivoice_capable": true,
- "multimodel_capable": true
- }
- ```
-
- #### **engines_available**
- > Lists the currently available TTS engines that can be loaded.
-
- #### **current_engine_loaded**
- > Shows the currently loaded TTS engine.
- These can be used together with the **/api/enginereload** endpoint to determine and change the currently loaded engine. Note that swapping the engine using this method can cause the main script process of AllTalk to lose sync with the subprocess. This results in a message indicating that script.py lost control of the subprocess when exiting AllTalk.
-
- #### **models_available**
- > Lists all the models available for the currently loaded TTS engine. Displayed as {engine-name - model-name} from models_available.
-
- #### **current_model_loaded**
- > Shows the currently loaded model.
- You can swap models using the **/api/reload** endpoint by specifying the full {enginename - modelname}.
-
- #### **audio_format**
- > The primary format in which the currently loaded engine produces audio output. This does not account for any global transcoding settings in the AllTalk Settings page but is used by the transcoding process to determine if transcoding is necessary.
-
- #### **deepspeed_capable**
- > Indicates if the current TTS engine supports DeepSpeed and can handle DeepSpeed enable/disable requests.
-
- #### **deepspeed_available**
- > If the current TTS engine is DeepSpeed capable, shows whether DeepSpeed was detected and loaded for use.
-
- #### **deepspeed_enabled**
- > Indicates whether DeepSpeed is currently enabled or disabled for the current TTS engine, assuming the above two are true.
-
- #### **generationspeed_capable**
- > Indicates if the current TTS engine supports generating TTS at different speeds.
-
- #### **generationspeed_set**
- > Shows the current default speed set in the TTS engine's configuration settings.
-
- #### **lowvram_capable**
- > Indicates if the current TTS engine allows moving the TTS model between VRAM and System RAM, which is useful in low VRAM situations, such as when using AllTalk with an LLM that fills up VRAM. This helps keep models contiguous within VRAM and speeds up processing.
-
- #### **lowvram_enabled**
- > If the model is lowvram_capable, setting this to true allows the model to move between VRAM and System RAM as necessary.
-
- #### **pitch_capable**
- > Indicates if the current TTS engine supports generating TTS at different pitches.
-
- #### **pitch_set**
- > Shows the current default audio pitch set in the TTS engine's configuration settings.
-
- #### **repetitionpenalty_capable**
- > Indicates if the current TTS engine supports different repetition penalties for its TTS models.
-
- #### **repetitionpenalty_set**
- > Shows the current default repetition penalty set in the TTS engine's configuration settings.
-
- #### **temperature_capable**
- > Indicates if the current TTS engine supports different temperature settings for its TTS models.
-
- #### **temperature_set**
- > Shows the current default temperature set in the TTS engine's configuration settings.
-
- #### **languages_capable**
- > Indicates if the models within the current TTS engine support multiple languages. This can be used to disable language selection in your interface if necessary.
-
- #### **multivoice_capable**
- > Indicates if the current model loaded into the TTS engine supports multiple voices or just one voice. If a model is not multi-voice capable, you can display 'Default' as the only available voice.
-
- #### **multimodel_capable**
- > Some TTS engines, such as Piper, use models as voices. In such cases, you don't need to load different models but present the models as voices. If a model is not multimodel_capable, it indicates this status in your interface.
-
- """
-
- openapi_endpoint = """
- ### ๐ OpenAI Speech API Compatible Endpoint
- Validate and generate audio based on text input using an OpenAI Speech v1 API compatible endpoint, as per the documentation at [OpenAI Text-to-Speech Guide](https://platform.openai.com/docs/guides/text-to-speech) and [OpenAI API Reference](https://platform.openai.com/docs/api-reference/audio).
-
- Voices are mapped on a one-to-one basis within the AllTalk Gradio interface on a TTS engine by TTS engine basis. If RVC is globally enabled in AllTalk settings and a voice other than "Disabled" is selected for the character voice, the chosen RVC voice will be applied after the TTS is generated and before the audio is transcoded and sent back out.
-
- Note that there is no capability within the OpenAI API to specify a language, so the response will be in whatever language the currently loaded TTS engine and model support.
-
- **URL**: `http://{ipaddress}:{port}/v1/audio/speech`
- **Method**: `POST`
-
- `curl -X POST "http://127.0.0.1:7851/v1/audio/speech" -H "Content-Type: application/json" -d "{\"model\":\"any_model_name\",\"input\":\"Hello, this is a test.\",\"voice\":\"nova\",\"response_format\":\"wav\",\"speed\":1.0}"`
-
- The request body must be a JSON object with the following fields:
-
- **model** (string): The TTS model to use. Currently ignored, but is required in the request.
- **input** (string): The text to generate audio for. Maximum length is 4096 characters.
- **voice** (string): The voice to use when generating the audio. Supported voices are ["alloy", "echo", "fable", "nova", "onyx", "shimmer"].
- **response_format** (string, optional): The format of the audio. Audio will be transcoded to the requested format.
- **speed** (float, optional): The speed of the generated audio. Must be between 0.25 and 4.0. Default is 1.0. Id the TTS engine loaded doesnt support different speeds, then the audio generated will not abide by this speed request.
-
- ```
- {
- "model": "any_model_name",
- "input": "Hello, this is a test.",
- "voice": "nova",
- "response_format": "wav",
- "speed": 1.0
- }
- ```
-
- #### Python Example
- ```
- import requests
- import json
-
- # Define the endpoint URL
- url = "http://127.0.0.1:7851/v1/audio/speech"
-
- # Define the request payload
- payload = {
- "model": "any_model_name",
- "input": "Hello, this is a test.",
- "voice": "nova",
- "response_format": "wav",
- "speed": 1.0
- }
-
- # Set the headers
- headers = {
- "Content-Type": "application/json"
- }
-
- # Send the POST request
- response = requests.post(url, data=json.dumps(payload), headers=headers)
-
- # Check the response
- if response.status_code == 200:
- with open("output.wav", "wb") as f:
- f.write(response.content)
- print("Audio file saved as output.wav")
- else:
- print(f"Error: {response.status_code} - {response.text}")
- ```
- #### Javascript Example
- ```
- // Define the endpoint URL
- const url = "http://127.0.0.1:7851/v1/audio/speech";
-
- // Define the request payload
- const payload = {
- model: "any_model_name",
- input: "Hello, this is a test.",
- voice: "nova",
- response_format: "wav",
- speed: 1.0
- };
-
- // Set the headers
- const headers = {
- "Content-Type": "application/json"
- };
-
- // Send the POST request
- fetch(url, {
- method: "POST",
- headers: headers,
- body: JSON.stringify(payload)
- })
- .then(response => {
- if (response.ok) {
- return response.blob();
- } else {
- return response.text().then(text => { throw new Error(text); });
- }
- })
- .then(blob => {
- // Create a link element
- const url = window.URL.createObjectURL(blob);
- const a = document.createElement('a');
- a.style.display = 'none';
- a.href = url;
- a.download = 'output.wav';
-
- // Append the link to the body
- document.body.appendChild(a);
-
- // Programmatically click the link to trigger the download
- a.click();
-
- // Remove the link from the document
- window.URL.revokeObjectURL(url);
- document.body.removeChild(a);
-
- console.log("Audio file saved as output.wav");
- })
- .catch(error => {
- console.error("Error:", error);
- });
- ```
-
- """
-
- interaction_endpoints = """
- ### ๐ Stop Generation Endpoint
- Interrupt the current TTS generation process. When this endpoint is called, it sets tts_stop_generation in the model_engine to True, which can be used to interrupt the current TTS generation. Stop requests will only be honored if the current TTS engine is capable of handling and can honor a stop request partway through generation.
-
- **URL**: `http://{ipaddress}:{port}/api/stop-generation`
- **Method**: `PUT`
-
- > `curl -X PUT "http://127.0.0.1:7851/api/stop-generation"`
-
- ```
- {
- "message": "Cancelling current TTS generation"
- }
- ```
-
- ### ๐ Reload Configuration Endpoint
- Reload the TTS engine's configuration and scan for new voices and models. When this endpoint is called, it reloads all current settings from the configuration files and scans for new voices and models that are available. This ensures that subsequent calls to /api/currentsettings, /api/voices, and /api/rvcvoices return an up-to-date set of settings and information about the current state of the TTS engine.
-
- **URL**: `http://{ipaddress}:{port}/api/reload_config`
- **Method**: `GET`
-
- > `curl -X GET "http://127.0.0.1:7851/api/reload_config"`
-
- `Config file reloaded successfully`
-
- ### ๐ Reload/Swap model
- Can be used to load in/swap to one of the models presented by /api/currentsettings in the `models_available` list.
-
- **URL**: `http://{ipaddress}:{port}/api/reload?tts_method=x`
- **Method**: `POST`
-
- > `curl -X POST "http://127.0.0.1:7851/api/reload?tts_method=xtts%20-%20xttsv2_2.0.2"`
-
- {"status": "model-success"} or {"status": "model-failure"}
-
- ### ๐ Switch DeepSpeed Endpoint
-
- **URL**: `http://{ipaddress}:{port}/api/deepspeed`
- **Method**: `POST`
-
- > `curl -X POST "http://127.0.0.1:7851/api/deepspeed?new_deepspeed_value=True"`
-
- Replace True with False to disable DeepSpeed mode.
-
- ```
- {
- "status": "deepspeed-success"
- }
- ```
-
- ### ๐ Switching Low VRAM Endpoint
-
- **URL**: `http://{ipaddress}:{port}/api/lowvramsetting`
- **Method: `POST`
-
- > `curl -X POST "http://127.0.0.1:7851/api/lowvramsetting?new_low_vram_value=True"`
-
- Replace True with False to disable Low VRAM mode.
-
- ```
- {
- "status": "lowvram-success"
- }
- ```
- """
-
- standardtts_endpoint = """
- ### ๐ TTS Generation Endpoint (Standard Generation)
- Generate TTS audio based on text input. This endpoint supports both character and narrator speech generation. All global settings for the API endpoint can be configured within the AllTalk interface under Global Settings > AllTalk API Defaults. TTS engine-specific settings, such as voices to use or engine parameters, can be set on an engine-by-engine basis in TTS Engine Settings > TTS Engine of your choice.
-
- It's important to note that although you can send all these variables/settings, the loaded TTS engine will only support them if it is capable e.g. you can request a TTS generation in Russian, but if the TTS model that is loaded in only support English, it's only going to generate english sounding text to speech.
-
- **URL**: `http://{ipaddress}:{port}/api/tts-generate`
- **Method**: `POST`
- **Content-Type**: application/x-www-form-urlencoded
-
- ### ๐ Example Command Lines (Standard Generation)
- Standard TTS generation supports narration and generates a WAV file/blob.
-
- > **Standard TTS Speech Example** - Generate a time-stamped file for standard text:
- `curl -X POST "http://127.0.0.1:7851/api/tts-generate" -d "text_input=All of this is text spoken by the character. This is text not inside quotes, though that doesnt matter in the slightest" -d "text_filtering=standard" -d "character_voice_gen=female_01.wav" -d "narrator_enabled=false" -d "narrator_voice_gen=male_01.wav" -d "text_not_inside=character" -d "language=en" -d "output_file_name=myoutputfile" -d "output_file_timestamp=true" -d "autoplay=true" -d "autoplay_volume=0.8"`
-
- > **Narrator Example** - Generate a time-stamped file for text with narrator and character speech:
- `curl -X POST "http://127.0.0.1:7851/api/tts-generate" -d "text_input=*This is text spoken by the narrator* \\\"This is text spoken by the character\\\;". This is text not inside quotes." -d "text_filtering=standard" -d "character_voice_gen=female_01.wav" -d "narrator_enabled=true" -d "narrator_voice_gen=male_01.wav" -d "text_not_inside=character" -d "language=en" -d "output_file_name=myoutputfile" -d "output_file_timestamp=true" -d "autoplay=true" -d "autoplay_volume=0.8"`
-
- > If your text contains double quotes, escape them with \" (see the narrator example).
- Voices sent in the request have to match the voices available within the TTS engine loaded. Generation requests where the voices dont match, will result nothing being generated and possibly an error message.
-
- Because of the way AllTalk works you can send a request with any mix of settings you wish e.g.
-
- > `curl -X POST "http://127.0.0.1:7851/api/tts-generate" -d "text_input=All of this is text spoken by the character. This is text not inside quotes, though that doesnt matter in the slightest"`
-
- Would generate the TTS for whatver engine is curretly loaded in. It will use the default API settings and default TTS engine settings to populate any missing fields, which gives you flexibility on how you want to send API requests to AllTalk.
-
- ### ๐ Request Parameters
- #### **text_input**
- > The text you want the TTS engine to produce. Use escaped double quotes for character speech and asterisks for narrator speech if using the narrator function. Example:
- -d "text_input=*This is text spoken by the narrator* \"This is text spoken by the character\". This is text not inside quotes."
-
- #### **text_filtering**
- > Filter for text. Options:
- **none**: No filtering. Raw text is sent to the TTS engine.
- **standard**: Basic level of filtering to clean up special characters.
- **html**: For HTML content with entities like ".
- -d "text_filtering=none"
- -d "text_filtering=standard"
- -d "text_filtering=html"
-
- #### **character_voice_gen**
- > The name of the character's voice file (WAV format).
- -d "character_voice_gen=female_01.wav"
-
- #### **rvccharacter_voice_gen**
- > The name of the RVC voice file for the character. Should be in the format `folder\\file.pth` or the word `Disabled`. When Disabled is sent and RVC is globally enabled, the RVC pipeline will not be used for the character/main voice.
- -d "rvccharacter_voice_gen=folder\\voice1.pth"
-
- #### **narrator_enabled**
- > Enable or disable the narrator function. If true, minimum text filtering is set to standard.
- -d "narrator_enabled=true"
- -d "narrator_enabled=false"
-
- #### **narrator_voice_gen**
- > The name of the narrator's voice file (WAV format).
- -d "narrator_voice_gen=male_01.wav"
-
- #### **rvcnarrator_voice_gen**
- > The name of the RVC voice file for the narrator. Should be in the format folder\\file.pth or the word Disabled. When Disabled is sent and RVC is globally enabled, the RVC pipeline will not be used for the character/main voice.
- -d "rvcnarrator_voice_gen=folder\\voice2.pth"
-
- #### **text_not_inside**
- > Specify the handling of lines not inside double quotes or asterisks for the narrator feature. Options:
- **character**: Treat as character speech.
- **narrator**: Treat as narrator speech.
- **silent**: Ignore text not inside quotes or asterisks.
- -d "text_not_inside=character"
- -d "text_not_inside=narrator"
- -d "text_not_inside=silent"
-
- #### **language**
- > Choose the language for TTS. Options:
- **ar**: Arabic
- **zh-cn**: Chinese (Simplified)
- **cs**: Czech
- **nl**: Dutch
- **en**: English
- **fr**: French
- **de**: German
- **hi**: Hindi (limited support)
- **hu**: Hungarian
- **it**: Italian
- **ja**: Japanese
- **ko**: Korean
- **pl**: Polish
- **pt**: Portuguese
- **ru**: Russian
- **es**: Spanish
- **tr**: Turkish
- -d "language=en"
-
- #### **output_file_name***
- > The name of the output file (excluding the .wav extension).
- --d "output_file_name=myoutputfile"
-
- #### **output_file_timestamp**
- > Add a timestamp to the output file name. If true, each file will have a unique timestamp; otherwise, the same file name will be overwritten each time you generate TTS.
- --d "output_file_timestamp=true"
- --d "output_file_timestamp=false"
-
- #### **autoplay**
- > Enable or disable playing the generated TTS to your standard sound output device at the time of TTS generation.
- --d "autoplay=true"
- --d "autoplay=false"
-
- #### **autoplay_volume**
- > Set the autoplay volume. Should be between 0.1 and 1.0.
- --d "autoplay_volume=0.8"
-
- #### **speed**
- > Set the speed of the generated audio. Should be between 0.25 and 2.0.
- --d "speed=1.0"
-
- #### **pitch**
- > Set the pitch of the generated audio. Should be between -10 and 10.
- --d "pitch=0"
-
- #### **temperature**
- > Set the temperature for the TTS engine. Should be between 0.1 and 1.0.
- --d "temperature=0.75"
-
- #### **repetition_penalty**
- > Set the repetition penalty for the TTS engine. Should be between 1.0 and 20.0.
- --d "repetition_penalty=10"
-
- ### ๐ TTS Generation Response
- > The API returns a JSON object with the following properties:
- **status**: Indicates whether the generation was successful (generate-success) or failed (generate-failure).
- **output_file_path**: The on-disk location of the generated WAV file.
- **output_file_url**: The HTTP location for accessing the generated WAV file for browser playback.
- **output_cache_url**: The HTTP location for accessing the generated WAV file as a pushed download.
-
- ```
- {
- "status": "generate-success",
- "output_file_path": "C:\\text-generation-webui\\extensions\\alltalk_tts\\outputs\\myoutputfile_1704141936.wav",
- "output_file_url": "/audio/myoutputfile_1704141936.wav",
- "output_cache_url": "/audiocache/myoutputfile_1704141936.wav"
- }
- ```
-
- Note: The response no longer includes the IP address and port number. You will need to add these in your own software/extension.
- """
-
- streamingtts_endpoint = """
- ### ๐ TTS Generation Endpoint (Streaming Generation)
- Generate TTS audio as a stream. This endpoint does not support narration and will generate an audio stream, not a file. It also does not support the RVC pipeline. Generate and stream TTS audio directly for real-time playback.
-
- **URL**: `http://{ipaddress}:{port}/api/tts-generate-streaming`
- **Method**: POST
- **Content-Type**: application/x-www-form-urlencoded
-
- > `http://127.0.0.1:7851/api/tts-generate-streaming?text=Here%20is%20some%20text&voice=female_01.wav&language=en&output_file=stream_output.wav`
-
- Example JavaScript for Streaming Playback:
-
- ```
- const text = "Here is some text";
- const voice = "female_01.wav";
- const language = "en";
- const outputFile = "stream_output.wav";
- const encodedText = encodeURIComponent(text);
- const streamingUrl = `http://localhost:7851/api/tts-generate-streaming?text=${encodedText}&voice=${voice}&language=${language}&output_file=${outputFile}`;
- const audioElement = new Audio(streamingUrl);
- audioElement.play();
- ```
-
- Response: A StreamingResponse for the audio stream.
-
- Request Parameters:
-
- **text**: The text to convert to speech.
- **voice**: The voice type to use.
- **language**: The language for the TTS.
- **output_file**: The name of the output file.
- **Response**: A JSON object indicating the status of the request.
-
- ```
- {
- "output_file_path": "stream_output.wav"
- }
- ```
- """
-
- with gr.Tab("API Endpoints & Dev"):
- with gr.Tab("Best Development Practices"): gr.Markdown(best_practices)
- with gr.Tab("Standard TTS Generation"): gr.Markdown(standardtts_endpoint)
- with gr.Tab("Streaming TTS Generation"): gr.Markdown(streamingtts_endpoint)
- with gr.Tab("Settings API's"): gr.Markdown(settings_endpoints)
- with gr.Tab("Interaction API's"): gr.Markdown(interaction_endpoints)
+import gradio as gr
+
+def api_documentation():
+ best_practices = """
+ ### ๐ Best Practices for writing an extension
+
+ You will need to store the IP address and Port (minimum) that your extension will be connecting to. Potentially also http/https if you wish to use AllTalk over tunnels. Additionally, you may also wish to store a connection timeout value.
+
+ ```
+ {
+ "api_alltalk_protocol": "http://",
+ "api_alltalk_ip_port": "127.0.0.1:7851",
+ "api_connection_timeout": 5
+ }
+ ```
+ When your extension starts up you will need to connect to the IP/Port and check the **/api/ready** end point for a 'Ready' status. You may wish to do this on a loop for the period of the connection timeout.
+
+ If the timeout is reached without a response, you can set voices, models available, models loaded, basically whatever values you are going to store/present to the user as "Server Offline" or whatever terminology you wish.
+
+ If the connection is available, then you would:
+
+ > 1) Pull the current settings and populate those into a paramaters variabele. **/api/currentsettings**
+ 2) Pull the current voices and populate those into a paramaters variabele. **/api/voices**
+ 3) Pull the current rvcvoices and populate those into a paramaters variabele. **/api/rvcvoices**
+
+ You can now present those to the user interface, choosing if you are just going to present the voices (narrator or not), RVC voices, model loaded, models available etc.
+
+ If you are going to present a multitude of options, please abide by the **xxxxxx_capable** settings provided by **/api/currentsettings**, disabling or enabling options in your user interface as the loaded in TTS engine does or doesnt allow for e.g. if the current TTS engine doesnt support changing your generation audio speed, it will send **"generationspeed_capable": false** over when you use **/api/currentsettings**. As such, you should disable the option to change the generation speed within your interface, however, ultimately, even if not disabled, if the current TTS engine loaded isnt capable of speed generation, changing the generation speed will have no effect on the resulting TTS generation, but you may end up with users asking why!
+
+ It is also advisable to have a refresh button to pull make AllTalk update its settings **/api/reload_config** and then you can pull current settings & voices again. This ensures the use always has a way to reconnect to the AllTalk server if its unavailable when your extension starts, or in the scenario that something else was changed in the Gradio interface and now the TTS Engine loaded in is different from when your extension started up.
+
+ You can obviously choose if you will give the user access to change the model loaded, TTS engine etc. Please read the documentation on the API endpoints for more information.
+
+ If you are presenting all the variables/options to the user, then when you send off a generation request to **/api/tts-generate** send all of the variable/options that are stored within your interface.
+
+ If you are NOT presenting all the variables/options to the user, then you have 2x options when sending to **/api/tts-generate**:
+
+ > 1) Send all the values/options but hard code some settings in your outbound generation request. This will ensure you always get the TTS generation result you want. Please read the **/api/tts-generate** section for details.
+
+ OR
+
+ >2) Dont send all the values/options in the generation request. In this scenario, the AllTalk server will populate missing settings from the global API Default Settings and the current TTS Engine's settings that are loaded in. e.g. if you dont provide a character voice, the default set character voice of whatever TTS engine is loaded in will be used. The same goes for all the other settings, except the actual text of the generation request.
+
+ Finally, depending on how you are storing your settings, for your extension, you may wish to save the settings each time there is a successful generation request. This ensures that the settings are valid and the connected IP address/port are correct.
+ """
+
+ settings_endpoints = """
+ ### ๐ Ready Endpoint
+ Check if the Text-to-Speech (TTS) engine has started and is ready to accept requests. This endpoint is useful when performing reload model/engine requests as it will return "Unloaded" while the engine is restarting. You can poll this endpoint every second or so to confirm a "Ready" status, indicating AllTalk is ready to process requests. Note that if there is an issue loading the selected model (e.g., missing from the file system), the endpoint will still return "Ready". This status only indicates that the engine has loaded.
+
+ **URL**: `http://{ipaddress}:{port}/api/ready`
+ **Method**: `GET`
+
+ > `curl -X GET "http://127.0.0.1:7851/api/ready"`
+
+ Response: `Ready` or `Unloaded`
+
+ ### ๐ Standard Voices List Endpoint
+ Retrieve a list of available voices for generating speech that the currently loaded TTS engine and model supports. If the currently loaded TTS engine does not load a model directly (e.g., Piper), the models themselves will be displayed as voices.
+
+ **URL**: `http://{ipaddress}:{port}/api/voices`
+ **Method**: `GET`
+
+ > `curl -X GET "http://127.0.0.1:7851/api/voices"`
+
+ ```
+ {
+ "status": "success",
+ "voices": ["voice1", "voice2", "voice3"]
+ }
+
+ ```
+
+ ### ๐ RVC Voices List Endpoint
+ Retrieve a list of available RVC voices for further processing your TTS with the RVC pipeline. `Disabled` will always be included in the list. When `Disabled` is used, the RVC pipeline will not be utilized. If the RVC pipeline is globally disabled in AllTalk, `Disabled` will be the only item in the list. Index files matching their RVC voices will not be displayed; if an index file exists for an RVC voice, it will be automatically selected during generation. If multiple index files exist and it is impossible to automatically select one, a message will be output in the console.
+
+ **URL**: `http://{ipaddress}:{port}/api/rvcvoices`
+ **Method**: `GET`
+
+ > `curl -X GET "http://127.0.0.1:7851/api/rvcvoices"`
+
+ ```
+ {
+ "status": "success",
+ "voices": ["Disabled", "folder1\\voice1.pth", "folder2\\voice2.pth", "folder3\\voice3.pth"]
+ }
+ ```
+
+ ### ๐ Current Settings Endpoint
+ Retrieve the current settings of the TTS engine.
+
+ **URL**: `http://{ipaddress}:{port}/api/currentsettings`
+ **Method**: `GET`
+
+ > `curl -X GET "http://127.0.0.1:7851/api/currentsettings"`
+
+
+ ```
+ {
+ "engines_available": ["parler", "piper", "vits", "xtts"],
+ "current_engine_loaded": "xtts",
+ "models_available": [
+ {"name": "xtts - xttsv2_2.0.2"},
+ {"name": "apitts - xttsv2_2.0.2"},
+ {"name": "xtts - xttsv2_2.0.3"},
+ {"name": "apitts - xttsv2_2.0.3"}
+ ],
+ "current_model_loaded": "xtts - xttsv2_2.0.3",
+ "manufacturer_name": "Coqui",
+ "audio_format": "wav",
+ "deepspeed_capable": true,
+ "deepspeed_available": true,
+ "deepspeed_enabled": true,
+ "generationspeed_capable": true,
+ "generationspeed_set": 1,
+ "lowvram_capable": true,
+ "lowvram_enabled": false,
+ "pitch_capable": false,
+ "pitch_set": 0,
+ "repetitionpenalty_capable": true,
+ "repetitionpenalty_set": 10,
+ "streaming_capable": true,
+ "temperature_capable": true,
+ "temperature_set": 0.75,
+ "ttsengines_installed": true,
+ "languages_capable": true,
+ "multivoice_capable": true,
+ "multimodel_capable": true
+ }
+ ```
+
+ #### **engines_available**
+ > Lists the currently available TTS engines that can be loaded.
+
+ #### **current_engine_loaded**
+ > Shows the currently loaded TTS engine.
+ These can be used together with the **/api/enginereload** endpoint to determine and change the currently loaded engine. Note that swapping the engine using this method can cause the main script process of AllTalk to lose sync with the subprocess. This results in a message indicating that script.py lost control of the subprocess when exiting AllTalk.
+
+ #### **models_available**
+ > Lists all the models available for the currently loaded TTS engine. Displayed as {engine-name - model-name} from models_available.
+
+ #### **current_model_loaded**
+ > Shows the currently loaded model.
+ You can swap models using the **/api/reload** endpoint by specifying the full {enginename - modelname}.
+
+ #### **audio_format**
+ > The primary format in which the currently loaded engine produces audio output. This does not account for any global transcoding settings in the AllTalk Settings page but is used by the transcoding process to determine if transcoding is necessary.
+
+ #### **deepspeed_capable**
+ > Indicates if the current TTS engine supports DeepSpeed and can handle DeepSpeed enable/disable requests.
+
+ #### **deepspeed_available**
+ > If the current TTS engine is DeepSpeed capable, shows whether DeepSpeed was detected and loaded for use.
+
+ #### **deepspeed_enabled**
+ > Indicates whether DeepSpeed is currently enabled or disabled for the current TTS engine, assuming the above two are true.
+
+ #### **generationspeed_capable**
+ > Indicates if the current TTS engine supports generating TTS at different speeds.
+
+ #### **generationspeed_set**
+ > Shows the current default speed set in the TTS engine's configuration settings.
+
+ #### **lowvram_capable**
+ > Indicates if the current TTS engine allows moving the TTS model between VRAM and System RAM, which is useful in low VRAM situations, such as when using AllTalk with an LLM that fills up VRAM. This helps keep models contiguous within VRAM and speeds up processing.
+
+ #### **lowvram_enabled**
+ > If the model is lowvram_capable, setting this to true allows the model to move between VRAM and System RAM as necessary.
+
+ #### **pitch_capable**
+ > Indicates if the current TTS engine supports generating TTS at different pitches.
+
+ #### **pitch_set**
+ > Shows the current default audio pitch set in the TTS engine's configuration settings.
+
+ #### **repetitionpenalty_capable**
+ > Indicates if the current TTS engine supports different repetition penalties for its TTS models.
+
+ #### **repetitionpenalty_set**
+ > Shows the current default repetition penalty set in the TTS engine's configuration settings.
+
+ #### **temperature_capable**
+ > Indicates if the current TTS engine supports different temperature settings for its TTS models.
+
+ #### **temperature_set**
+ > Shows the current default temperature set in the TTS engine's configuration settings.
+
+ #### **languages_capable**
+ > Indicates if the models within the current TTS engine support multiple languages. This can be used to disable language selection in your interface if necessary.
+
+ #### **multivoice_capable**
+ > Indicates if the current model loaded into the TTS engine supports multiple voices or just one voice. If a model is not multi-voice capable, you can display 'Default' as the only available voice.
+
+ #### **multimodel_capable**
+ > Some TTS engines, such as Piper, use models as voices. In such cases, you don't need to load different models but present the models as voices. If a model is not multimodel_capable, it indicates this status in your interface.
+
+ """
+
+ openapi_endpoint = """
+ ### ๐ OpenAI Speech API Compatible Endpoint
+ Validate and generate audio based on text input using an OpenAI Speech v1 API compatible endpoint, as per the documentation at [OpenAI Text-to-Speech Guide](https://platform.openai.com/docs/guides/text-to-speech) and [OpenAI API Reference](https://platform.openai.com/docs/api-reference/audio).
+
+ Voices are mapped on a one-to-one basis within the AllTalk Gradio interface on a TTS engine by TTS engine basis. If RVC is globally enabled in AllTalk settings and a voice other than "Disabled" is selected for the character voice, the chosen RVC voice will be applied after the TTS is generated and before the audio is transcoded and sent back out.
+
+ Note that there is no capability within the OpenAI API to specify a language, so the response will be in whatever language the currently loaded TTS engine and model support.
+
+ **URL**: `http://{ipaddress}:{port}/v1/audio/speech`
+ **Method**: `POST`
+
+ `curl -X POST "http://127.0.0.1:7851/v1/audio/speech" -H "Content-Type: application/json" -d "{\"model\":\"any_model_name\",\"input\":\"Hello, this is a test.\",\"voice\":\"nova\",\"response_format\":\"wav\",\"speed\":1.0}"`
+
+ The request body must be a JSON object with the following fields:
+
+ **model** (string): The TTS model to use. Currently ignored, but is required in the request.
+ **input** (string): The text to generate audio for. Maximum length is 4096 characters.
+ **voice** (string): The voice to use when generating the audio. Supported voices are ["alloy", "echo", "fable", "nova", "onyx", "shimmer"].
+ **response_format** (string, optional): The format of the audio. Audio will be transcoded to the requested format.
+ **speed** (float, optional): The speed of the generated audio. Must be between 0.25 and 4.0. Default is 1.0. Id the TTS engine loaded doesnt support different speeds, then the audio generated will not abide by this speed request.
+
+ ```
+ {
+ "model": "any_model_name",
+ "input": "Hello, this is a test.",
+ "voice": "nova",
+ "response_format": "wav",
+ "speed": 1.0
+ }
+ ```
+
+ Remapping the voices through an API call:
+
+ **URL**: `http://{ipaddress}:{port}/api/openai-voicemap`
+ **Method**: `POST`
+
+ `curl -X PUT "http://localhost:7851/api/openai-voicemap" -H "Content-Type: application/json" -d "{\"alloy\":\"female_01.wav\",\"echo\":\"female_01.wav\",\"fable\":\"female_01.wav\",\"nova\":\"female_01.wav\",\"onyx\":\"male_01.wav\",\"shimmer\":\"male_02.wav\"}"`
+
+ You can re-map the 6x voices on the fly, to any voices supported/available for the currently loaded TTS engine by using the above style post. You can use the `curl -X GET "http://127.0.0.1:7851/api/voices"` endpoint to get a list of all currently available voices. Please note that the Gradio interface will not reflect these changes until AllTalk is reloaded as gradio caches the list.
+
+ #### Python Example
+ ```
+ import requests
+ import json
+
+ # Define the endpoint URL
+ url = "http://127.0.0.1:7851/v1/audio/speech"
+
+ # Define the request payload
+ payload = {
+ "model": "any_model_name",
+ "input": "Hello, this is a test.",
+ "voice": "nova",
+ "response_format": "wav",
+ "speed": 1.0
+ }
+
+ # Set the headers
+ headers = {
+ "Content-Type": "application/json"
+ }
+
+ # Send the POST request
+ response = requests.post(url, data=json.dumps(payload), headers=headers)
+
+ # Check the response
+ if response.status_code == 200:
+ with open("output.wav", "wb") as f:
+ f.write(response.content)
+ print("Audio file saved as output.wav")
+ else:
+ print(f"Error: {response.status_code} - {response.text}")
+ ```
+ #### Javascript Example
+ ```
+ // Define the endpoint URL
+ const url = "http://127.0.0.1:7851/v1/audio/speech";
+
+ // Define the request payload
+ const payload = {
+ model: "any_model_name",
+ input: "Hello, this is a test.",
+ voice: "nova",
+ response_format: "wav",
+ speed: 1.0
+ };
+
+ // Set the headers
+ const headers = {
+ "Content-Type": "application/json"
+ };
+
+ // Send the POST request
+ fetch(url, {
+ method: "POST",
+ headers: headers,
+ body: JSON.stringify(payload)
+ })
+ .then(response => {
+ if (response.ok) {
+ return response.blob();
+ } else {
+ return response.text().then(text => { throw new Error(text); });
+ }
+ })
+ .then(blob => {
+ // Create a link element
+ const url = window.URL.createObjectURL(blob);
+ const a = document.createElement('a');
+ a.style.display = 'none';
+ a.href = url;
+ a.download = 'output.wav';
+
+ // Append the link to the body
+ document.body.appendChild(a);
+
+ // Programmatically click the link to trigger the download
+ a.click();
+
+ // Remove the link from the document
+ window.URL.revokeObjectURL(url);
+ document.body.removeChild(a);
+
+ console.log("Audio file saved as output.wav");
+ })
+ .catch(error => {
+ console.error("Error:", error);
+ });
+ ```
+
+ """
+
+ interaction_endpoints = """
+ ### ๐ Stop Generation Endpoint
+ Interrupt the current TTS generation process. When this endpoint is called, it sets tts_stop_generation in the model_engine to True, which can be used to interrupt the current TTS generation. Stop requests will only be honored if the current TTS engine is capable of handling and can honor a stop request partway through generation.
+
+ **URL**: `http://{ipaddress}:{port}/api/stop-generation`
+ **Method**: `PUT`
+
+ > `curl -X PUT "http://127.0.0.1:7851/api/stop-generation"`
+
+ ```
+ {
+ "message": "Cancelling current TTS generation"
+ }
+ ```
+
+ ### ๐ Reload Configuration Endpoint
+ Reload the TTS engine's configuration and scan for new voices and models. When this endpoint is called, it reloads all current settings from the configuration files and scans for new voices and models that are available. This ensures that subsequent calls to /api/currentsettings, /api/voices, and /api/rvcvoices return an up-to-date set of settings and information about the current state of the TTS engine.
+
+ **URL**: `http://{ipaddress}:{port}/api/reload_config`
+ **Method**: `GET`
+
+ > `curl -X GET "http://127.0.0.1:7851/api/reload_config"`
+
+ `Config file reloaded successfully`
+
+ ### ๐ Reload/Swap model
+ Can be used to load in/swap to one of the models presented by /api/currentsettings in the `models_available` list.
+
+ **URL**: `http://{ipaddress}:{port}/api/reload?tts_method=x`
+ **Method**: `POST`
+
+ > `curl -X POST "http://127.0.0.1:7851/api/reload?tts_method=xtts%20-%20xttsv2_2.0.2"`
+
+ {"status": "model-success"} or {"status": "model-failure"}
+
+ ### ๐ Switch DeepSpeed Endpoint
+
+ **URL**: `http://{ipaddress}:{port}/api/deepspeed`
+ **Method**: `POST`
+
+ > `curl -X POST "http://127.0.0.1:7851/api/deepspeed?new_deepspeed_value=True"`
+
+ Replace True with False to disable DeepSpeed mode.
+
+ ```
+ {
+ "status": "deepspeed-success"
+ }
+ ```
+
+ ### ๐ Switching Low VRAM Endpoint
+
+ **URL**: `http://{ipaddress}:{port}/api/lowvramsetting`
+ **Method: `POST`
+
+ > `curl -X POST "http://127.0.0.1:7851/api/lowvramsetting?new_low_vram_value=True"`
+
+ Replace True with False to disable Low VRAM mode.
+
+ ```
+ {
+ "status": "lowvram-success"
+ }
+ ```
+ """
+
+ standardtts_endpoint = """
+ ### ๐ TTS Generation Endpoint (Standard Generation)
+ Generate TTS audio based on text input. This endpoint supports both character and narrator speech generation. All global settings for the API endpoint can be configured within the AllTalk interface under Global Settings > AllTalk API Defaults. TTS engine-specific settings, such as voices to use or engine parameters, can be set on an engine-by-engine basis in TTS Engine Settings > TTS Engine of your choice.
+
+ It's important to note that although you can send all these variables/settings, the loaded TTS engine will only support them if it is capable e.g. you can request a TTS generation in Russian, but if the TTS model that is loaded in only support English, it's only going to generate english sounding text to speech.
+
+ **URL**: `http://{ipaddress}:{port}/api/tts-generate`
+ **Method**: `POST`
+ **Content-Type**: application/x-www-form-urlencoded
+
+ ### ๐ Example Command Lines (Standard Generation)
+ Standard TTS generation supports narration and generates a WAV file/blob.
+
+ > **Standard TTS Speech Example** - Generate a time-stamped file for standard text:
+ `curl -X POST "http://127.0.0.1:7851/api/tts-generate" -d "text_input=All of this is text spoken by the character. This is text not inside quotes, though that doesnt matter in the slightest" -d "text_filtering=standard" -d "character_voice_gen=female_01.wav" -d "narrator_enabled=false" -d "narrator_voice_gen=male_01.wav" -d "text_not_inside=character" -d "language=en" -d "output_file_name=myoutputfile" -d "output_file_timestamp=true" -d "autoplay=true" -d "autoplay_volume=0.8"`
+
+ > **Narrator Example** - Generate a time-stamped file for text with narrator and character speech:
+ `curl -X POST "http://127.0.0.1:7851/api/tts-generate" -d "text_input=*This is text spoken by the narrator* \\\"This is text spoken by the character\\\;". This is text not inside quotes." -d "text_filtering=standard" -d "character_voice_gen=female_01.wav" -d "narrator_enabled=true" -d "narrator_voice_gen=male_01.wav" -d "text_not_inside=character" -d "language=en" -d "output_file_name=myoutputfile" -d "output_file_timestamp=true" -d "autoplay=true" -d "autoplay_volume=0.8"`
+
+ > If your text contains double quotes, escape them with \" (see the narrator example).
+ Voices sent in the request have to match the voices available within the TTS engine loaded. Generation requests where the voices dont match, will result nothing being generated and possibly an error message.
+
+ Because of the way AllTalk works you can send a request with any mix of settings you wish e.g.
+
+ > `curl -X POST "http://127.0.0.1:7851/api/tts-generate" -d "text_input=All of this is text spoken by the character. This is text not inside quotes, though that doesnt matter in the slightest"`
+
+ Would generate the TTS for whatver engine is curretly loaded in. It will use the default API settings and default TTS engine settings to populate any missing fields, which gives you flexibility on how you want to send API requests to AllTalk.
+
+ ### ๐ Request Parameters
+ #### **text_input**
+ > The text you want the TTS engine to produce. Use escaped double quotes for character speech and asterisks for narrator speech if using the narrator function. Example:
+ -d "text_input=*This is text spoken by the narrator* \"This is text spoken by the character\". This is text not inside quotes."
+
+ #### **text_filtering**
+ > Filter for text. Options:
+ **none**: No filtering. Raw text is sent to the TTS engine.
+ **standard**: Basic level of filtering to clean up special characters.
+ **html**: For HTML content with entities like ".
+ -d "text_filtering=none"
+ -d "text_filtering=standard"
+ -d "text_filtering=html"
+
+ #### **character_voice_gen**
+ > The name of the character's voice file (WAV format).
+ -d "character_voice_gen=female_01.wav"
+
+ #### **rvccharacter_voice_gen**
+ > The name of the RVC voice file for the character. Should be in the format `folder\\file.pth` or the word `Disabled`. When Disabled is sent and RVC is globally enabled, the RVC pipeline will not be used for the character/main voice.
+ -d "rvccharacter_voice_gen=folder\\voice1.pth"
+
+ #### **narrator_enabled**
+ > Enable or disable the narrator function. If true, minimum text filtering is set to standard.
+ -d "narrator_enabled=true"
+ -d "narrator_enabled=false"
+
+ #### **narrator_voice_gen**
+ > The name of the narrator's voice file (WAV format).
+ -d "narrator_voice_gen=male_01.wav"
+
+ #### **rvcnarrator_voice_gen**
+ > The name of the RVC voice file for the narrator. Should be in the format folder\\file.pth or the word Disabled. When Disabled is sent and RVC is globally enabled, the RVC pipeline will not be used for the character/main voice.
+ -d "rvcnarrator_voice_gen=folder\\voice2.pth"
+
+ #### **text_not_inside**
+ > Specify the handling of lines not inside double quotes or asterisks for the narrator feature. Options:
+ **character**: Treat as character speech.
+ **narrator**: Treat as narrator speech.
+ **silent**: Ignore text not inside quotes or asterisks.
+ -d "text_not_inside=character"
+ -d "text_not_inside=narrator"
+ -d "text_not_inside=silent"
+
+ #### **language**
+ > Choose the language for TTS. Options:
+ **ar**: Arabic
+ **zh-cn**: Chinese (Simplified)
+ **cs**: Czech
+ **nl**: Dutch
+ **en**: English
+ **fr**: French
+ **de**: German
+ **hi**: Hindi (limited support)
+ **hu**: Hungarian
+ **it**: Italian
+ **ja**: Japanese
+ **ko**: Korean
+ **pl**: Polish
+ **pt**: Portuguese
+ **ru**: Russian
+ **es**: Spanish
+ **tr**: Turkish
+ -d "language=en"
+
+ #### **output_file_name***
+ > The name of the output file (excluding the .wav extension).
+ -d "output_file_name=myoutputfile"
+
+ #### **output_file_timestamp**
+ > Add a timestamp to the output file name. If true, each file will have a unique timestamp; otherwise, the same file name will be overwritten each time you generate TTS.
+ -d "output_file_timestamp=true"
+ -d "output_file_timestamp=false"
+
+ #### **autoplay**
+ > Enable or disable playing the generated TTS to your standard sound output device at the time of TTS generation.
+ -d "autoplay=true"
+ -d "autoplay=false"
+
+ #### **autoplay_volume**
+ > Set the autoplay volume. Should be between 0.1 and 1.0.
+ -d "autoplay_volume=0.8"
+
+ #### **speed**
+ > Set the speed of the generated audio. Should be between 0.25 and 2.0.
+ -d "speed=1.0"
+
+ #### **pitch**
+ > Set the pitch of the generated audio. Should be between -10 and 10.
+ -d "pitch=0"
+
+ #### **temperature**
+ > Set the temperature for the TTS engine. Should be between 0.1 and 1.0.
+ -d "temperature=0.75"
+
+ #### **repetition_penalty**
+ > Set the repetition penalty for the TTS engine. Should be between 1.0 and 20.0.
+ -d "repetition_penalty=10"
+
+ ### ๐ TTS Generation Response
+ > The API returns a JSON object with the following properties:
+ **status**: Indicates whether the generation was successful (generate-success) or failed (generate-failure).
+ **output_file_path**: The on-disk location of the generated WAV file.
+ **output_file_url**: The HTTP location for accessing the generated WAV file for browser playback.
+ **output_cache_url**: The HTTP location for accessing the generated WAV file as a pushed download.
+
+ ```
+ {
+ "status": "generate-success",
+ "output_file_path": "C:\\text-generation-webui\\extensions\\alltalk_tts\\outputs\\myoutputfile_1704141936.wav",
+ "output_file_url": "/audio/myoutputfile_1704141936.wav",
+ "output_cache_url": "/audiocache/myoutputfile_1704141936.wav"
+ }
+ ```
+
+ Note: The response no longer includes the IP address and port number. You will need to add these in your own software/extension.
+ """
+
+ streamingtts_endpoint = """
+ ### ๐ TTS Generation Endpoint (Streaming Generation)
+ Generate TTS audio as a stream. This endpoint does not support narration and will generate an audio stream, not a file. It also does not support the RVC pipeline. Generate and stream TTS audio directly for real-time playback.
+
+ **URL**: `http://{ipaddress}:{port}/api/tts-generate-streaming`
+ **Method**: POST
+ **Content-Type**: application/x-www-form-urlencoded
+
+ > `http://127.0.0.1:7851/api/tts-generate-streaming?text=Here%20is%20some%20text&voice=female_01.wav&language=en&output_file=stream_output.wav`
+
+ Example JavaScript for Streaming Playback:
+
+ ```
+ const text = "Here is some text";
+ const voice = "female_01.wav";
+ const language = "en";
+ const outputFile = "stream_output.wav";
+ const encodedText = encodeURIComponent(text);
+ const streamingUrl = `http://localhost:7851/api/tts-generate-streaming?text=${encodedText}&voice=${voice}&language=${language}&output_file=${outputFile}`;
+ const audioElement = new Audio(streamingUrl);
+ audioElement.play();
+ ```
+
+ Response: A StreamingResponse for the audio stream.
+
+ Request Parameters:
+
+ **text**: The text to convert to speech.
+ **voice**: The voice type to use.
+ **language**: The language for the TTS.
+ **output_file**: The name of the output file.
+ **Response**: A JSON object indicating the status of the request.
+
+ ```
+ {
+ "output_file_path": "stream_output.wav"
+ }
+ ```
+ """
+
+ with gr.Tab("API Endpoints & Dev"):
+ with gr.Tab("Best Development Practices"): gr.Markdown(best_practices)
+ with gr.Tab("Standard TTS Generation"): gr.Markdown(standardtts_endpoint)
+ with gr.Tab("Streaming TTS Generation"): gr.Markdown(streamingtts_endpoint)
+ with gr.Tab("Settings API's"): gr.Markdown(settings_endpoints)
+ with gr.Tab("Interaction API's"): gr.Markdown(interaction_endpoints)
with gr.Tab("OpenAPI Compatible Endpoint"): gr.Markdown(openapi_endpoint)
\ No newline at end of file
From cc5a665ad657338b25a53beb1936f190e8501be6 Mon Sep 17 00:00:00 2001
From: erew123 <35898566+erew123@users.noreply.github.com>
Date: Thu, 15 Aug 2024 18:27:40 +0100
Subject: [PATCH 02/38] Update requirements_standalone.txt
---
.../requirements/requirements_standalone.txt | 22 +++++++++----------
1 file changed, 11 insertions(+), 11 deletions(-)
diff --git a/system/requirements/requirements_standalone.txt b/system/requirements/requirements_standalone.txt
index b131f19..8115912 100644
--- a/system/requirements/requirements_standalone.txt
+++ b/system/requirements/requirements_standalone.txt
@@ -1,25 +1,25 @@
-TTS
-faster-whisper>=1.0.0
+coqui-tts>=0.24.1
+faster-whisper>=1.0.3
fuzzywuzzy
gradio>=4.26.0
-importlib_metadata>=7.1.0
+importlib_metadata>=7.2.1
inputimeout>=1.0.4
-Jinja2>=3.1.0
+Jinja2>=3.1.4
librosa>=0.10.2.post1
local_attention>=1.9.0
nvidia-cublas-cu11>=11.11.3.6
nvidia-cudnn-cu11>=9.1.1.17
-onnxruntime-gpu>=1.18.0
-pydantic>=2.7.0
-python-ffmpeg>=2.0.0
+onnxruntime-gpu>=1.18.1
+pydantic>=2.8.2
+python-ffmpeg>=2.0.12
python-Levenshtein>=0.25.1
-praat-parselmouth>=0.4.0
-pyworld>=0.3.0
+praat-parselmouth>=0.4.4
+pyworld>=0.3.4
sounddevice>=0.4.7
-soundfile>=0.12.0
+soundfile>=0.12.1
spacy>=3.7.1
torchcrepe>=0.0.2
-tqdm>=4.66.0
+tqdm>=4.66.5
unidic-lite>=1.0.8
uvicorn>=0.29.0
pillow==10.3.0
From 61f36d5b0de8c8999eb5795f1d8b99bad9288eea Mon Sep 17 00:00:00 2001
From: erew123 <35898566+erew123@users.noreply.github.com>
Date: Thu, 15 Aug 2024 19:28:02 +0100
Subject: [PATCH 03/38] Update requirements_textgen.txt
---
system/requirements/requirements_textgen.txt | 1 -
1 file changed, 1 deletion(-)
diff --git a/system/requirements/requirements_textgen.txt b/system/requirements/requirements_textgen.txt
index 69402dc..575c4cd 100644
--- a/system/requirements/requirements_textgen.txt
+++ b/system/requirements/requirements_textgen.txt
@@ -25,7 +25,6 @@ uvicorn>=0.29.0
pypinyin
hangul_romanize
word2number
-git+https://github.com/huggingface/parler-tts.git
./system/config/fairseq-0.12.4-cp311-cp311-win_amd64.whl; sys_platform == "win32"
./system/config/fairseq-0.12.3.1-cp311-cp311-linux_x86_64.whl; sys_platform == "linux"
piper-tts; sys_platform == "linux"
From 2a40143eca6a02dab53a24aea1d81add0fde660d Mon Sep 17 00:00:00 2001
From: erew123 <35898566+erew123@users.noreply.github.com>
Date: Thu, 15 Aug 2024 19:28:17 +0100
Subject: [PATCH 04/38] Update requirements_colab.txt
---
system/requirements/requirements_colab.txt | 1 -
1 file changed, 1 deletion(-)
diff --git a/system/requirements/requirements_colab.txt b/system/requirements/requirements_colab.txt
index 57cb7c7..4af1cee 100644
--- a/system/requirements/requirements_colab.txt
+++ b/system/requirements/requirements_colab.txt
@@ -41,6 +41,5 @@ tomlkit
python-multipart
hangul_romanize
word2number
-git+https://github.com/huggingface/parler-tts.git
piper-tts; sys_platform == "linux"
trainer
From 458b0c38f2affc0ed1675cfad04f69f2d4c03969 Mon Sep 17 00:00:00 2001
From: erew123 <35898566+erew123@users.noreply.github.com>
Date: Thu, 15 Aug 2024 19:28:29 +0100
Subject: [PATCH 05/38] Update requirements_standalone.txt
---
system/requirements/requirements_standalone.txt | 1 -
1 file changed, 1 deletion(-)
diff --git a/system/requirements/requirements_standalone.txt b/system/requirements/requirements_standalone.txt
index 8115912..64844af 100644
--- a/system/requirements/requirements_standalone.txt
+++ b/system/requirements/requirements_standalone.txt
@@ -26,7 +26,6 @@ pillow==10.3.0
pypinyin
hangul_romanize
word2number
-git+https://github.com/huggingface/parler-tts.git
./system/config/fairseq-0.12.4-cp311-cp311-win_amd64.whl; sys_platform == "win32"
./system/config/fairseq-0.12.3.1-cp311-cp311-linux_x86_64.whl; sys_platform == "linux"
piper-tts; sys_platform == "linux"
From a1452bf0b747a0a02d14c6273e54dc850df05ed5 Mon Sep 17 00:00:00 2001
From: erew123 <35898566+erew123@users.noreply.github.com>
Date: Thu, 15 Aug 2024 19:34:23 +0100
Subject: [PATCH 06/38] Upload Parler requirements as a seperate
Parler have updated parts of their script and the requirements cause issues if used with other requirements files as they are too restrictive.
---
system/requirements/requirements_parler.txt | 1 +
1 file changed, 1 insertion(+)
create mode 100644 system/requirements/requirements_parler.txt
diff --git a/system/requirements/requirements_parler.txt b/system/requirements/requirements_parler.txt
new file mode 100644
index 0000000..b289387
--- /dev/null
+++ b/system/requirements/requirements_parler.txt
@@ -0,0 +1 @@
+git+https://github.com/huggingface/parler-tts.git
\ No newline at end of file
From cf625469f56a6825362d81238733f0bc4bc67c67 Mon Sep 17 00:00:00 2001
From: erew123 <35898566+erew123@users.noreply.github.com>
Date: Thu, 15 Aug 2024 19:37:12 +0100
Subject: [PATCH 07/38] Update atsetup.bat
---
atsetup.bat | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/atsetup.bat b/atsetup.bat
index 16b9351..8f0ddd2 100644
--- a/atsetup.bat
+++ b/atsetup.bat
@@ -139,6 +139,7 @@ goto StandaloneMenu
:InstallNvidiaTextGen
pip install -r system\requirements\requirements_textgen.txt
pip install -r system\requirements\requirements_textgen2.txt
+pip install -r system\requirements\requirements_parler.txt
echo ** Faiss **
call "%CONDA_ROOT_PREFIX%\Scripts\conda.exe" install -y pytorch::faiss-cpu
echo ** FFmpeg **
@@ -413,6 +414,8 @@ curl -LO https://github.com/erew123/alltalk_tts/releases/download/DeepSpeed-14.0
echo Installing DeepSpeed...
pip install deepspeed-0.14.0+ce78a63-cp311-cp311-win_amd64.whl
del deepspeed-0.14.0+ce78a63-cp311-cp311-win_amd64.whl
+echo Installing Parler...
+pip install -r system\requirements\requirements_parler.txt
echo ** Clean Environment **
call "%CONDA_ROOT_PREFIX%\Scripts\conda.exe" clean --all --force-pkgs-dirs -y
@@ -558,6 +561,7 @@ echo Installing requirements. If you have problems after this, you should
echo delete the alltalk_environment and run the setup routine again.
echo.
pip install -r system\requirements\requirements_standalone.txt
+pip install -r system\requirements\requirements_parler.txt
echo Installing DeepSpeed...
pip install deepspeed-0.14.0+ce78a63-cp311-cp311-win_amd64.whl
del deepspeed-0.14.0+ce78a63-cp311-cp311-win_amd64.whl
From 4c016a49a3f98d69db9786882337d7d845bfa91e Mon Sep 17 00:00:00 2001
From: erew123 <35898566+erew123@users.noreply.github.com>
Date: Thu, 15 Aug 2024 19:54:15 +0100
Subject: [PATCH 08/38] Update atsetup.sh
---
atsetup.sh | 2 ++
1 file changed, 2 insertions(+)
diff --git a/atsetup.sh b/atsetup.sh
index 1167901..c8dc1db 100644
--- a/atsetup.sh
+++ b/atsetup.sh
@@ -396,6 +396,7 @@ install_custom_standalone() {
echo Installing DeepSpeed...
pip install deepspeed-0.14.2+cu121torch2.2-cp311-cp311-manylinux_2_24_x86_64.whl
rm deepspeed-0.14.2+cu121torch2.2-cp311-cp311-manylinux_2_24_x86_64.whl
+ pip install -r system\requirements\requirements_parler.txt
conda clean --all --force-pkgs-dirs -y
Create start_environment.sh to run AllTalk
cat << EOF > start_environment.sh
@@ -578,6 +579,7 @@ reapply_standalone() {
echo " Installing additional requirements."
echo
pip install -r system/requirements/requirements_standalone.txt
+ pip install -r system\requirements\requirements_parler.txt
echo
echo " Requirements have been re-applied/updated."
echo
From 2bc06607708f3b4503e92ab43b992531fb07c2d9 Mon Sep 17 00:00:00 2001
From: erew123 <35898566+erew123@users.noreply.github.com>
Date: Thu, 15 Aug 2024 19:55:04 +0100
Subject: [PATCH 09/38] Update atsetup.bat
---
atsetup.bat | 1 +
1 file changed, 1 insertion(+)
diff --git a/atsetup.bat b/atsetup.bat
index 8f0ddd2..2a303e3 100644
--- a/atsetup.bat
+++ b/atsetup.bat
@@ -673,6 +673,7 @@ Echo Remember, after installation you can....
Echo Run %L_YELLOW%start_alltalk.bat%RESET% to start AllTalk.
Echo Run %L_YELLOW%start_finetune.bat%RESET% to start Finetuning.
Echo Run %L_YELLOW%start_environment.bat%RESET% to start the AllTalk Python environment.
+Echo Run %L_YELLOW%start_diagnostics.bat%RESET% to start generate a diagnostics file.
Echo.
exit /b
From 7e6a972198405c5d05cd634031d167bf2b632144 Mon Sep 17 00:00:00 2001
From: erew123 <35898566+erew123@users.noreply.github.com>
Date: Thu, 15 Aug 2024 20:06:08 +0100
Subject: [PATCH 10/38] Update README.md
---
README.md | 8 ++++++--
1 file changed, 6 insertions(+), 2 deletions(-)
diff --git a/README.md b/README.md
index 1dfeb26..47978fd 100644
--- a/README.md
+++ b/README.md
@@ -72,8 +72,12 @@ Quick setup scripts are available for users on Windows 10/11 and Linux. Instruct
- Ensure that **Git** is installed on your system as it is required for cloning the repository. If you do not have Git installed, visit [**Git's official website**](https://git-scm.com/downloads) to download and install it.
- **Important**: Do not use dashes or spaces in your folder path (e.g. avoid `/my folder-is-this/alltalk_tts-main`) as this causes issues with Python.
-### **NOTE. IF YOU HAVE AN EXISTING ALLTALK INSTALL** this will want to create a folder called `alltalk_tts` so you will need to re-name your old `alltalk_tts` to something else OR put this in a different folder/location.
-### **NOTE**. I am not saying this is a over-the-top of an old alltalk upgrade ATM. It needs the Python environment rebuilding, hence a fresh install is needed for now.
+#### **NOTE. IF YOU HAVE AN EXISTING ALLTALK INSTALL** this will want to create a folder called `alltalk_tts` so you will need to re-name your old `alltalk_tts` to something else OR put this in a different folder/location.
+#### **NOTE**. I am not saying this is a over-the-top of an old alltalk upgrade ATM. It needs the Python environment rebuilding, hence a fresh install is needed for now.
+
+---
+#### Python/PIP has recently made changes to the package installer and I have/am doing what I can to keep the requirements files working, however, this is a complicated time to say the least as some external requirements change and this can cause installation issues. Please raise a ticket if you have an error that a module is missing.
+---
QUICK SETUP - Text-Generation-webui
From 0a1a81a962813282f5482ebeea965bf1be1df7f6 Mon Sep 17 00:00:00 2001
From: erew123 <35898566+erew123@users.noreply.github.com>
Date: Wed, 21 Aug 2024 09:14:53 +0100
Subject: [PATCH 11/38] Add files via upload
---
system/gradio_pages/alltalk_documentation.py | 2417 +++++++++---------
system/gradio_pages/api_documentation.py | 6 +-
2 files changed, 1214 insertions(+), 1209 deletions(-)
diff --git a/system/gradio_pages/alltalk_documentation.py b/system/gradio_pages/alltalk_documentation.py
index fa62b8d..61db3b0 100644
--- a/system/gradio_pages/alltalk_documentation.py
+++ b/system/gradio_pages/alltalk_documentation.py
@@ -1,1207 +1,1212 @@
-import gradio as gr
-
-def alltalk_documentation():
- low_vram = """
- The Low VRAM option is a crucial feature designed to enhance performance under constrained (VRAM) conditions, as TTS models **can** require 2GB-3GB of VRAM to run effectively. This feature strategically manages the relocation of the Text-to-Speech (TTS) model between your system's Random Access Memory (RAM) and VRAM, moving it between the two on the fly. Obviously, this is very useful for people who have smaller graphics cards or people who's LLM has filled their VRAM, but you want to keep up performance.
-
- When you don't have enough VRAM free after loading your LLM model into your VRAM (Normal Mode example below), you can see that with so little working space, your GPU will have to swap in and out bits of the TTS model, which causes horrible slowdown.
-
- Note An Nvidia Graphics card is required for the LowVRAM option to work, as you will just be using system RAM otherwise.
-
- ### How It Works
- The Low VRAM mode intelligently relocates of the entire TTS model and stores the TTS model in your system RAM when not being used. When the TTS engine requires VRAM for processing, the entire model seamlessly moves into VRAM, causing your LLM to unload/displace some layers, ensuring optimal performance of the TTS engine.
-
- Post-TTS processing, the model moves back to system RAM, freeing up VRAM space for your Language Model (LLM) to load back in the missing layers. This adds about 1-2 seconds to both text generation by the LLM and the TTS engine.
-
- By transferring the entire model between RAM and VRAM, the Low VRAM option avoids model fragmentation, ensuring the TTS model remains cohesive and has all the working space it needs in your GPU, without having to just work on small bits of the TTS model at a time (which causes terrible slow down).
-
- This creates a TTS generation performance Boost for Low VRAM Users and is particularly beneficial for users with less than 2GB of free VRAM after loading their LLM, delivering a substantial 5-10x improvement in TTS generation speed.
- """
-
- silly_tavern_support = """
- #### Important note for Text-generation-webui users
- You **HAVE** to disable **Enable TTS** within the Text-generation-webui AllTalk interface, otherwise Text-generation-webui will also generate TTS due to the way it sends out text. You can do this each time you start up Text-generation-webui or set it in the start-up settings at the top of this page.
-
- ### ๐ฐ Quick Tips
- > Only change DeepSpeed, Low VRAM or Model one at a time. Wait for it to say Ready before changing something else.
- > You can permanently change the AllTalk startup settings or DeepSpeed, Low VRAM and Model at the top of this page.
- > Different AI models use quotes and asterisks differently, so you may need to change "Text not inside" depending on model.
- > Add new voice samples to the voices folder. You can Finetune a model to make it sound even closer to the original sample.
- > DeepSpeed will improve processing time to TTS to be 2-3x faster.
- > Low VRAM can be very beneficial if you don't have much memory left after loading your LLM.
-
- #### ๐ฐ TTS Generation Methods in SilllyTavern
- You have 2 types of audio generation options, Streaming and Standard.
-
- The Streaming Audio Generation method is designed for speed and is best suited for situations where you just want quick audio playback. This method, however, is limited to using just one voice per TTS generation request. This means a limitation of the Streaming method is the inability to utilize the AllTalk narrator function, making it a straightforward but less nuanced option.
-
- On the other hand, the Standard Audio Generation method provides a richer auditory experience. It's slightly slower than the Streaming method but compensates for this with its ability to split text into multiple voices. This functionality is particularly useful in scenarios where differentiating between character dialogues and narration can enhance the storytelling and delivery. The inclusion of the AllTalk narrator functionality in the Standard method allows for a more layered and immersive experience, making it ideal for content where depth and variety in voice narration add significant value.
-
- In summary, the choice between Streaming and Standard methods in AllTalk TTS depends on what you want. Streaming is great for quick and simple audio generation, while Standard is preferable for a more dynamic and engaging audio experience.
-
- **AllTalk TTS Generation Method:**
- > Select between Standard and Streaming Audio Generation methods.
- > This setting impacts the AllTalk narrator functionality.
-
- **Language Selection:**
- > Select your preferred TTS generation language from the "Language" dropdown.
-
- **Model Switching:**
- > Switch between different TTS models like API TTS, API Local, XTTSv2 Local, and optionally XTTSv2 FT if you have a finetuned model available.
- > Fine-tuned model availability (XTTSv2 FT) will only show when a finetuned model is detected by AllTalk.
- > See TTS Models/Methods for more information (though most people will want to stick with XTTSv2 Local).
-
- **DeepSpeed and Low VRAM Options:**
- > Optimize performance with DeepSpeed and Low VRAM settings.
- > DeepSpeed can offer a 2-3x performance boost on TTS generation. (Requires installation)
- > See the relevant sections in this documentation for details.
-
- Changing model or DeepSpeed or Low VRAM **each** take about 15 seconds so you should only change one at a time and wait for `Ready` before changing the next setting. To set these options long term you can apply the settings at the top of this page.
-
- #### ๐ฐ AllTalk Narrator
- Only available on the Standard Audo Generation method.
-
- **Narrator Voice Selection:**
- > Allows users to choose different narrator voices.
- > Access via the "Narrator Voice" dropdown.
-
- **AllTalk Narrator:**
- > Toggle the AllTalk narrator feature.
- > Access via "AT Narrator" dropdown with Enabled/Disabled options.
-
- **Text Outside Asterisks Handling:**
- > Choose how text outside asterisks is interpreted (as Narrator or Character voice).
- > Managed via the "Text Not Inside * or "" dropdown.
- > Note, only available when the AllTalk Narrator is enabled.
-
- #### ๐ฐ Usage Notes:
- > On startup of SillyTavern, it will pull your current settings from AllTalk (Current model, DeepSpeed status, Low VRAM status and Finetuned model availability).
- > Enabling the narrator automatically unchecks certain checkboxes related to text handling.
- > Changes in model or settings might trigger multiple requests to the server; patience is advised.
-
- #### ๐ฐ Troubleshooting:
- > If experiencing issues, use the Reload button in SillyTavern's TTS extension to reinitialize the connection to AllTalk and check if AllTalk is started correctly.
- """
-
- updating_alltalk = """
- ### ๐ช Updating
-
- Maintaining the latest version of your setup ensures access to new features and improvements. Below are the steps to update your installation, whether you're using Text-Generation-webui or running as a Standalone Application.
-
-
- UPDATING - Text-Generation-webui
-
- The update process closely mirrors the installation steps. Follow these to ensure your setup remains current:
-
- **Open a Command Prompt/Terminal**
- > Navigate to your Text-Generation-webui folder with:
- > `cd text-generation-webui`
-
- **Start the Python Environment**
- > Activate the Python environment tailored for your operating system. Use the appropriate command from below based on your OS:
- > **Windows**: `cmd_windows.bat`
- > **Linux**: `./cmd_linux.sh`
- > **macOS**: `cmd_macos.sh`
- > **WSL (Windows Subsystem for Linux)**: `cmd_wsl.bat`
-
- > If you're unfamiliar with Python environments and wish to learn more, consider reviewing **Understanding Python Environments Simplified** in the Help section.
-
- **Navigate to the AllTalk TTS Folder**
- > Move into your extensions and then the alltalk_tts directory:
- > `cd extensions/alltalk_tts`
-
- **Update the Repository**
- > Fetch the latest updates from the repository with:
- > `git pull`
-
- **Install Updated Requirements**
- > Depending on your machine's OS, install the required dependencies using pip:
- > **Windows Machines**:
- > `pip install -r system\\requirements\\requirements_textgen.txt`
- > **Linux/Mac**:
- > `pip install -r system/requirements/requirements_textgen.txt`
-
- **DeepSpeed Requirements**
- > If Text-gen-webui is using a new version of PyTorch, you **may** need to uninstall and update your DeepSpeed version.
- > Use AllTalks diagnostics or start-up menu to identify your version of PyTorch.
-
-
-
-
- UPDATING - Standalone Application
-
-
- If you installed from a ZIP file, you cannot use a `git pull` to update, as noted in the Quick Setup instructions.
-
- For Standalone Application users, here's how to update your setup:
-
- 1. **Open a Command Prompt/Terminal**:
- > Navigate to your AllTalk folder with
- > `cd alltalk_tts`
-
- 2. **Access the Python Environment**:
- > In a command prompt or terminal window, navigate to your `alltalk_tts` directory and start the Python environment:
- > **Windows**
- > `start_environment.bat`
- > **Linux/macOS**
- > `./start_environment.sh`
-
- > If you're unfamiliar with Python environments and wish to learn more, consider reviewing **Understanding Python Environments Simplified** in the Help section.
-
- 2. **Pull the Latest Updates**:
- > Retrieve the latest changes from the repository with:
- > `git pull`
-
- 3. **Install Updated Requirements**:
- > Depending on your machine's OS, install the required dependencies using pip:
- > **Windows Machines**
- > `pip install -r system\\requirements\\requirements_standalone.txt`
- > **Linux/Mac**
- > `pip install -r system/requirements/requirements_standalone.txt`
-
-
-
- ### ๐ช Resolving Update Issues
-
- If you encounter problems during or after an update, following these steps can help resolve the issue by refreshing your installation while preserving your data:
-
-
- RESOLVING - Updates
-
- The process involves renaming your existing `alltalk_tts` directory, setting up a fresh instance, and then migrating your data:
-
- 1. **Rename Existing Directory**:
- > First, rename your current `alltalk_tts` folder to keep it safe e.g. `alltalk_tts.old`. This preserves any existing data.
-
- 2. **Follow the Quick Setup instructions**:
- > You will now follow the **Quick Setup** instructions, performing the `git clone https://github.com/erew123/alltalk_tts` to pull down a new copy of AllTalk and install the requirements.
-
- > If you're not familiar with Python environments, see **Understanding Python Environments Simplified** in the Help section for more info.
-
- 3. **Migrate Your Data**:
- > **Before** starting the AllTalk, transfer the `models`, `voices`, `outputs` folders and also `confignew.json` from `alltalk_tts.old` to the new `alltalk_tts` directory. This action preserves your voice history and prevents the need to re-download the model.
-
- 4) **Launch AllTalk**
- > You're now ready to launch AllTalk and check it works correctly.
-
- 6. **Final Step**:
- > Once you've verified that everything is working as expected and you're satisfied with the setup, feel free to delete the `alltalk_tts.old` directory to free up space.
-
-
- """
-
- narrator_guides = """
- Messages intended for the Narrator should be enclosed in asterisks `*` and those for the character inside quotation marks `"`. However, AI systems often deviate from these rules, resulting in text that is neither in quotes nor asterisks. Sometimes, text may appear with only a single asterisk, and AI models may vary their formatting mid-conversation. For example, they might use asterisks initially and then switch to unmarked text. A properly formatted line should look like this:
-
- `"`Hey! I'm so excited to finally meet you. I've heard so many great things about you and I'm eager to pick your brain about computers.`"` `*`She walked across the room and picked up her cup of coffee`*`
-
- Most narrator/character systems switch voices upon encountering an asterisk or quotation marks, which is somewhat effective. AllTalk has undergone several revisions in its sentence splitting and identification methods. While some irregularities and AI deviations in message formatting are inevitable, any line beginning or ending with an asterisk should now be recognized as Narrator dialogue. Lines enclosed in double quotes are identified as Character dialogue. For any other text, you can choose how AllTalk handles it: whether it should be interpreted as Character or Narrator dialogue (most AI systems tend to lean more towards one format when generating text not enclosed in quotes or asterisks).
-
- ### ๐ฆ Narrator Types of Text
-
- The TTS engine identifies and processes three types of text when the Narrator function is `Enabled` or `Enabled (Silent)`:
-
- > **Narrated Text**: Text enclosed in asterisks `*Narrated text*`.
- > **Character Text**: Text enclosed in double quotes `"Character text"`.
- > **Text-Not-Inside**: Any text not enclosed in asterisks or double quotes. The handling of this text is customizable.
-
- ### ๐ฆ Settings for Narrated Text
-
- You can control how narrated text is handled with the following settings:
-
- > **Enabled**: Narrated text will be spoken by the TTS engine using the narrator voice.
- > **Enabled (Silent)**: Narrated text is recognized but not spoken. This is useful for adding silent annotations or actions within the text.
- > **Disabled**: Narrated text will be spoken using the character voice instead of the narrator voice. The narrator engine is turned off, and the Text-Not-Inside setting does not apply.
-
- Please note, if you set `Enabled` or `Enabled (silent)` as the APi defaults, then all text will go into the narrator function unless `disabled` is sent as part of the TTS generation request.
-
- ### ๐ฆ Settings for Text-Not-Inside
-
- You can customize how text-not-inside is handled with these options:
-
- > **"Character"**: Text-not-inside is treated as character dialogue and will be spoken.
- > ***Narrator***: Text-not-inside is treated as narration and will be spoken.
- > **Silent**: Text-not-inside is recognized but not spoken.
-
- ### ๐ฆ Examples and Variations
-
- Here are some examples to illustrate how different settings affect the text processing:
-
- > **Example 1**:
- **Narrator Enabled, Text-Not-Inside as Character**
- > ***Narrated text*** will be spoken as narration.
- > **"Character text"** will be spoken as character dialogue.
- > **Any other** text will be spoken as character dialogue.
-
- > **Example 2**:
- **Narrator Disabled**
- > ***Narrated text*** will be spoken using the character voice.
- > **"Character text"** will be spoken as character dialogue.
- > **Any other** text will be spoken as character dialogue.
-
- > **Example 3**:
- **Narrator Enabled (Silent), Text-Not-Inside as Silent**
- > ***Narrated text*** will be recognized but not spoken.
- > **"Character text"** will be spoken as character dialogue.
- > **Any other** text will be recognized but not spoken.
-
- ### ๐ฆ Potential Confusions and Clarifications
-
- **Silent Options Impact**
-
- > **Narrator Enabled (Silent)**: This setting allows for silent annotations within your text. Users can add notes or actions in the narrative without them being spoken.
- > **Text-Not-Inside as Silent**: This setting enables users to include text that is recognized by the TTS engine but remains unspoken, useful for adding context or instructions within the script that are not meant to be vocalized.
-
- **Combining Settings**
-
- > **Full Silence**: If both Narrator is set to `Enabled (Silent)` and Text-Not-Inside is set to `Silent`, **only character text** in quotes will be spoken.
- > **Partial Silence**: You can configure the settings to have only certain parts of the text spoken while others are silent. For example, you might want all narration silent and only character dialogue spoken, or vice versa.
-
- ### ๐ฆ Short Story Example
-
- >*Once upon a time in a small village, there was a young girl named Lily.*
- Lily woke up early every morning.
- "I love exploring the forest near my home."
- * Lily said. One day, she found a mysterious key lying on the ground.*
- "This key looks old and rusty, but it seems to sparkle in the sunlight,"
- She thought. Lily decided she would keep the key safe until she could discover its purpose.
- *Curious and excited, she decided to find out what the key opened.*
-
- ### Short Story Explanation
-
- **Narrated Text** (Asterisks surrounding the text):
- > *Once upon a time in a small village, there was a young girl named Lily.*
- > * Lily said. One day, she found a mysterious key lying on the ground.*
- > *Curious and excited, she decided to find out what the key opened.*
- > These lines are enclosed in asterisks and represent the narration. If Narrator was set to `Enabled (Silent)` these lines would not be spoken.
-
- **Character Text** (Double Quotes surrounding the text):
- > "I love exploring the forest near my home."
- > "This key looks old and rusty, but it seems to sparkle in the sunlight."
- > These lines are enclosed in double quotes and represent the characterโs spoken dialogue or thoughts.
-
- **Text-Not-Inside** (Neither Asterisks nor Quotes):
- > Lily woke up early every morning.
- > She thought. Lily decided she would keep the key safe until she could discover its purpose.
- > These lines are not enclosed in asterisks or quotes and will be handled according to the `Text-Not-Inside` settings. If set to `Text-not-inside` > `Silent` these lines would not be spoken.
-
- """
-
- rvc_guides = """
- RVC (Retrieval-based Voice Conversion) enhances TTS by replicating voice characteristics for characters or narrators, adding depth to synthesized speech. It functions as a TTS-to-TTS pipeline and can be used with any TTS engine/model. However, the closer the original TTS generation is to the voice you want to use with RVC, the better the result will be. Therefore, it is recommended to use a voice cloning TTS engine like Coqui XTTS with voice samples for optimal performance.
-
- ### ๐ฉ Voice Model Files
-
- Voice models should be stored in the `/models/rvc_voices/{subfolder}` directory in their own subfolder. Typically, a voice model includes a PTH file and potentially an index file. If an index file is present, AllTalk will automatically select and use it. If multiple index files are found, none will be used, and a message will be output to the console. You can find places on the internet to download pre-generated RVC voice models e.g. https://voice-models.com/ and huggingface etc.
-
- ### ๐ฉ Purpose of the Index File
-
- The index file helps improve the quality of the generated audio by providing a reference during the conversion process. The FAISS index enables faster and more accurate retrieval of voice characteristics, leading to more natural and high-quality voice synthesis. The training data size setting determines how much of the index is used, impacting both quality and computation time. By indexing a larger number of data points, the conversion process can produce audio that closely matches the desired voice characteristics.
-
- ### ๐ฉ RVC Settings Page
- """
- rvc_guides2 = """
- ### Default Character Voice Model
- > This setting allows you to select the voice model used for character conversion. If "Disabled" is selected, RVC will not be applied to character voices. This option is used only if RVC is enabled and no other voice is specified in the API request. Selecting a specific model will apply RVC to all character dialogues, potentially making them sound more natural and lifelike.
-
- ### Default Narrator Voice Model
- > This setting allows you to select the voice model used for narrator conversion. If "Disabled" is selected, RVC will not be applied to the narrator voice. This option is used only if RVC is enabled and no other voice is specified in the API request. Applying RVC to the narrator can enhance the overall narrative experience by providing a consistent and engaging voice.
-
- ### Index Influence Ratio
- > Sets the influence exerted by the index file on the final output. A higher value increases the impact of the index, potentially enhancing detail but also increasing the risk of artifacts. Fine-tuning this setting helps achieve the desired balance between audio detail and artifact prevention.
-
- ### Pitch
- > This slider sets the pitch of the audio output. Increasing the value raises the pitch, while decreasing the value lowers it. Adjusting the pitch can help match the voice to specific character traits or emotional states, enhancing the overall storytelling experience.
-
- ### Volume Envelope
- > This setting substitutes or blends with the volume envelope of the output. A ratio closer to 1 means the output envelope is more heavily employed. This can help maintain the natural dynamics of the voice, making it sound more realistic and less robotic.
-
- ### Protect Voiceless Consonants/Breath Sounds
- > Prevents artifacts in voiceless consonants and breath sounds. Higher values (up to 0.5) provide stronger protection but might affect indexing. This setting helps maintain the clarity and intelligibility of speech, especially in parts of the audio where voiceless consonants and breath sounds are present.
-
- ### AutoTune
- > Enables or disables auto-tune for the generated audio. Recommended for singing conversions to ensure the output remains in tune. Auto-tune can correct pitch discrepancies and produce a more polished and harmonious sound.
- """
- rvc_guides3 = """
- ### Filter Radius
- > If the number is greater than or equal to three, employing median filtering on the collected tone results has the potential to decrease respiration. This setting helps reduce noise and unwanted sounds in the audio, leading to cleaner and more professional-sounding output.
-
- ### Training Data Size (AllTalk Specific)
- > Determines the number of training data points used to train the FAISS index. Increasing the size may improve the quality of the output but can also increase computation time. Different index files have different sizes. This setting limits the maximum amount of the index used. For example, if an index file has 70,000 points and you set the limit to 50,000, only 50,000 points will be used. A higher training data size generally results in better audio quality because more data points contribute to a more accurate voice conversion.
-
- ### Embedder Model
- > Select between different models for learning speaker embedding. "hubert" and "contentvec" are the available options.
- **hubert**: A model that focuses on capturing the phonetic and linguistic content of the voice, which is useful for a wide range of voice conversion tasks.
- **contentvec**: A model that captures more detailed voice characteristics and nuances, potentially offering higher fidelity in voice conversion but may require more computational resources.
-
- ### Split Audio
- > Splits the audio into chunks for inference to obtain better results in some cases. This can improve the quality of conversion, especially for longer audio inputs, by ensuring more consistent and accurate processing across the entire length of the audio.
-
- ### Pitch Extraction Algorithm
- > Choose the algorithm used for extracting the pitch (F0) during audio conversion. The available options include:
- **crepe**: Provides high accuracy in pitch detection and is robust against various types of noise.
- **crepe-tiny**: A smaller, faster version of crepe with slightly reduced accuracy.
- **dio**: A fast pitch extraction algorithm that is less accurate than crepe but suitable for real-time applications.
- **fcpe**: Focuses on precise pitch extraction, often used for high-fidelity voice conversion.
- **harvest**: Known for producing smooth and natural pitch contours, suitable for music and singing voice conversion.
- **hybrid[rmvpe+fcpe]**: Combines the strengths of rmvpe and fcpe to achieve a balance of accuracy and performance.
- **pm**: A robust algorithm that works well in various conditions, offering a balance of speed and accuracy.
- **rmvpe**: Recommended for most cases due to its balance of accuracy and performance, especially in TTS applications.
- """
-
-
-
- deepspeed_guides = """
- ### ๐ต๐ข DeepSpeed Installation Options
- **DeepSpeed requires an Nvidia Graphics card**
-
- ---
- ## Documentation yet to be updated. In a Nutshell though....
- > AllTalk **Standalone** setups **(Windows & Linux)** will install DeepSpeed **automatically**. You can stop reading here and you dont have to do anything.
-
- > **Windows** users, please see instructions in `atsetup.bat`
-
- > **Linux users**, please look here https://github.com/erew123/alltalk_tts/releases/tag/DeepSpeed-14.2-Linux
- ---
- ### Legacy manual build of DeepSpeed Instructions
- #### ๐ต Linux Installation
- DeepSpeed requires access to the **Nvidia CUDA Development Toolkit** to compile on a Linux system. It's important to note that this toolkit is distinct and unrealted to your graphics card driver or the CUDA version the Python environment uses.
-
-
- Linux DeepSpeed - Text-generation-webui
-
- ### DeepSpeed Installation for Text generation webUI
-
- 1. **Nvidia CUDA Development Toolkit Installation**:
- - The toolkit is crucial for DeepSpeed to compile/build for your version of Linux and requires around 3GB's of disk space.
- - Install using your package manager **(Recommended)** e.g. **CUDA Toolkit 11.8** or download directly from [Nvidia CUDA Toolkit Archive](https://developer.nvidia.com/cuda-toolkit-archive) (choose 11.8 or 12.1 for Linux).
-
- 2. **Open a Terminal Console**:
- - After Nvidia CUDA Development Toolkit installation, access your terminal console.
-
- 3. **Install libaio-dev**:
- - Use your Linux distribution's package manager.
-
- - - `sudo apt install libaio-dev` for Debian-based systems
- - `sudo yum install libaio-devel` for RPM-based systems.
-
- 4. **Navigate to Text generation webUI Folder**:
- - Change directory to your Text generation webUI folder with `cd text-generation-webui`.
-
- 5. **Activate Text generation webUI Custom Conda Environment**:
- - Run `./cmd_linux.sh` to start the environment.
-
- > If you're unfamiliar with Python environments and wish to learn more, consider reviewing **Understanding Python Environments Simplified** in the Help section.
-
- 6. **Set `CUDA_HOME` Environment Variable**:
- - DeepSpeed locates the Nvidia toolkit using the `CUDA_HOME` environment variable.
- - You will only set this temporarily as Text generation webUI sets up its own CUDA_HOME environment each time you use `./cmd_linux.sh` or `./start_linux.sh`
-
- 7. **Temporarily Configuring `CUDA_HOME`**:
- - When the Text generation webUI Python environment is active **(step 5)**, set `CUDA_HOME`.
-
- - - `export CUDA_HOME=/usr/local/cuda`
- - `export PATH=${CUDA_HOME}/bin:${PATH}`
- - `export LD_LIBRARY_PATH=${CUDA_HOME}/lib64:$LD_LIBRARY_PATH`
-
- - You can confirm the path is set correctly and working by running the command `nvcc --version` should confirm `Cuda compilation tools, release 11.8.`.
- - Incorrect path settings may lead to errors. If you encounter path issues or receive errors like `[Errno 2] No such file or directory` when you run the next step, confirm the path correctness or adjust as necessary.
-
- 8. **DeepSpeed Installation**:
- - Install DeepSpeed using `pip install deepspeed`.
-
- 9. **Troubleshooting**:
- - Troubleshooting steps for DeepSpeed installation can be located down below.
- - **NOTE**: You **DO NOT** need to set Text-generation-webUI's **--deepspeed** setting for AllTalk to be able to use DeepSpeed. These are two completely separate things and incorrectly setting that on Text-generation-webUI may cause other complications.
-
-
- Linux DeepSpeed - Standalone Installation
-
- ### DeepSpeed Installation for Standalone AllTalk
-
- 1. **Nvidia CUDA Development Toolkit Installation**:
- - The toolkit is crucial for DeepSpeed to compile/build for your version of Linux and requires around 3GB's of disk space.
- - Install using your package manager **(Recommended)** e.g. **CUDA Toolkit 11.8** or download directly from [Nvidia CUDA Toolkit Archive](https://developer.nvidia.com/cuda-toolkit-archive) (choose 11.8 or 12.1 for Linux).
-
- 2. **Open a Terminal Console**:
- - After Nvidia CUDA Development Toolkit installation, access your terminal console.
-
- 3. **Install libaio-dev**:
- - Use your Linux distribution's package manager.
-
- - - `sudo apt install libaio-dev` for Debian-based systems
- - `sudo yum install libaio-devel` for RPM-based systems.
-
- 4. **Navigate to AllTalk TTS Folder**:
- - Change directory to your AllTalk TTS folder with `cd alltalk_tts`.
-
- 5. **Activate AllTalk Custom Conda Environment**:
- - Run `./start_environment.sh` to start the AllTalk Python environment.
- - This command will start the custom Python environment that was installed with `./atsetup.sh`.
-
- > If you're unfamiliar with Python environments and wish to learn more, consider reviewing **Understanding Python Environments Simplified** in the Help section.
-
- 6. **Set `CUDA_HOME` Environment Variable**:
- - The DeepSpeed installation routine locates the Nvidia toolkit using the `CUDA_HOME` environment variable. This can be set temporarily for a session or permanently, depending on other requirements you may have for other Python/System environments.
- - For temporary use, proceed to **step 8**. For a permanent solution, see [Conda's manual on setting environment variables](https://conda.io/projects/conda/en/latest/user-guide/tasks/manage-environments.html#set-env-vars).
-
- 7. **(Optional) Permanent `CUDA_HOME` Setup**:
- - If you choose to set `CUDA_HOME` permanently, follow the instructions in the provided Conda manual link above.
-
- 8. **Configuring `CUDA_HOME`**:
- - When your Python environment is active **(step 5)**, set `CUDA_HOME`.
-
- - - `export CUDA_HOME=/usr/local/cuda`
- - `export PATH=${CUDA_HOME}/bin:${PATH}`
- - `export LD_LIBRARY_PATH=${CUDA_HOME}/lib64:$LD_LIBRARY_PATH`
-
- - You can confirm the path is set correctly and working by running the command `nvcc --version` should confirm `Cuda compilation tools, release 11.8.`.
- - Incorrect path settings may lead to errors. If you encounter path issues or receive errors like `[Errno 2] No such file or directory` when you run the next step, confirm the path correctness or adjust as necessary.
-
- 9. **DeepSpeed Installation**:
- - Install DeepSpeed using `pip install deepspeed`.
-
- 10. **Starting AllTalk TTS WebUI**:
- - Launch the AllTalk TTS interface with `./start_alltalk.sh` and enable DeepSpeed.
-
- ### Troubleshooting
-
- - If setting `CUDA_HOME` results in path duplication errors (e.g., `.../bin/bin/nvcc`), you can correct this by unsetting `CUDA_HOME` with `unset CUDA_HOME` and then adding the correct path to your system's PATH variable.
- - Always verify paths and compatibility with other CUDA-dependent applications to avoid conflicts.
- - If you have multiple versions of the Nvidia CUDA Development Toolkit installed, you will have to specify the version number in step 8 for the CUDA_HOME path.
- - If it became necessary to uninstall DeepSpeed, you can do so by start the Python enviroment and then running `pip uninstall deepspeed`
-
-
-
- #### ๐ข Windows Installation
- You have 2x options for how to setup DeepSpeed on Windows. Pre-compiled wheel files for specific Python, CUDA and Pytorch builds, or manually compiling DeepSpeed.
-
-
- Windows DeepSpeed - Pre-Compiled Wheels (Quick and Easy)
-
- ### DeepSpeed Installation with Pre-compiled Wheels
-
- 1. **Introduction to Pre-compiled Wheels**:
- - The `atsetup.bat` utility simplifies the installation of DeepSpeed by automatically downloading and installing pre-compiled wheel files. These files are tailored for **specific** versions of Python, CUDA, and PyTorch, ensuring compatibility with both the **Standalone Installation** and a standard build of **Text-generation-webui**.
-
- 2. **Manual Installation of Pre-compiled Wheels**:
- - If needed, pre-compiled DeepSpeed wheel files that I have built are available on the [Releases Page](https://github.com/erew123/alltalk_tts/releases). You can manually install or uninstall these wheels using the following commands:
- - Installation: `pip install {deep-speed-wheel-file-name-here}`
- - Uninstallation: `pip uninstall deepspeed`
-
- 3. **Using `atsetup.bat` for Simplified Management**:
- - For those running the Standalone Installation or a standard build of Text-generation-webui, the `atsetup.bat` utility offers the simplest and most efficient way to manage DeepSpeed installations on Windows.
-
-
-
-
- Windows DeepSpeed - Manual Compilation
-
- ### Manual DeepSpeed Wheel Compilation
-
- 1. **Preparation for Manual Compilation**:
- - Manual compilation of DeepSpeed wheels is an advanced process that requires:
- - **1-2 hours** of your time for initial setup and compilation.
- - **6-10GB** of disk space on your computer.
- - A solid technical understanding of Windows environments and Python.
-
- 2. **Understanding Wheel Compatibility**:
- - A compiled DeepSpeed wheel is uniquely tied to the specific versions of Python, PyTorch, and CUDA used during its compilation. If any of these versions are changed, you will need to compile a new DeepSpeed wheel to ensure compatibility.
-
- 3. **Compiling DeepSpee Resources**:
- - Myself and [@S95Sedan](https://github.com/S95Sedan) have worked to simplify the compilation process. [@S95Sedan](https://github.com/S95Sedan) has notably improved the process for later versions of DeepSpeed, ensuring ease of build on Windows.
- - Because [@S95Sedan](https://github.com/S95Sedan) is now maintaining the instructions for compiling DeepSpeed on Windows, please visit [@S95Sedan](https://github.com/S95Sedan)'s [DeepSpeed GitHub page](https://github.com/S95Sedan/Deepspeed-Windows).
-
-
- """
-
- help_with_problems = """
- ### ๐ Support Requests, Troubleshooting & Feature requests
- I'm thrilled to see the enthusiasm and engagement with AllTalk! Your feedback and questions are invaluable, helping to make this project even better. To ensure everyone gets the help they need efficiently, please consider the following before submitting a support request:
-
- **Consult the Documentation:** A comprehensive guide and FAQ sections (below) are available to help you navigate AllTalk. Many common questions and troubleshooting steps are covered here.
-
- **Search Past Discussions:** Your issue or question might already have been addressed in the discussions area or [closed issues](https://github.com/erew123/alltalk_tts/issues?q=is%3Aissue+is%3Aclosed). Please use the search function to see if there's an existing solution or advice that applies to your situation.
-
- **Bug Reports:** If you've encountered what you believe is a bug, please first check the [Updates & Bug Fixes List](https://github.com/erew123/alltalk_tts/issues/25) to see if it's a known issue or one that's already been resolved. If not, I encourage you to report it by raising a bug report in the [Issues section](https://github.com/erew123/alltalk_tts/issues), providing as much detail as possible to help identify and fix the issue.
-
- **Feature Requests:** The current Feature request list can be [found here](https://github.com/erew123/alltalk_tts/discussions/74). I love hearing your ideas for new features! While I can't promise to implement every suggestion, I do consider all feedback carefully. Please share your thoughts in the [Discussions area](https://github.com/erew123/alltalk_tts/discussions) or via a Feature Request in the [Issues section](https://github.com/erew123/alltalk_tts/issues).
-
-
- ### ๐จ Help with problems
-
- #### ๐ **Minor updates/bug fixes list** can be found [here](https://github.com/erew123/alltalk_tts/issues/25)
-
-
- ๐จ How to make a diagnostics report file
-
- If you are on a Windows machine or a Linux machine, you should be able to use the `atsetup.bat` or `./atsetup.sh` utility to create a diagnositcs file. If you are unable to use the `atsetup` utility, please follow the instructions below.
-
-
- Manually making a diagnostics report file
-
- 1) Open a command prompt window and start the Python environment. Depending on your setup (Text-generation-webui or Standalone AllTalk), the steps to start the Python environment vary:
-
- - **For Text-generation-webui Users**:
- - Navigate to the Text-generation-webui directory:
- - `cd text-generation-webui`
- - Start the Python environment suitable for your OS:
- - Windows: `cmd_windows.bat`
- - Linux: `./cmd_linux.sh`
- - macOS: `cmd_macos.sh`
- - WSL (Windows Subsystem for Linux): `cmd_wsl.bat`
- - Move into the AllTalk directory:
- - `cd extensions/alltalk_tts`
-
- - **For Standalone AllTalk Users**:
- - Navigate to the `alltalk_tts` folder:
- - `cd alltalk_tts`
- - Start the Python environment:
- - Windows: `start_environment.bat`
- - Linux: `./start_environment.sh`
-
- > If you're unfamiliar with Python environments and wish to learn more, consider reviewing **Understanding Python Environments Simplified** in the Help section.
-
- 2) Run the diagnostics and select the requirements file name you installed AllTalk with:
- - `python diagnostics.py`
-
- 3) You will have an on screen output showing your environment setttings, file versions request vs whats installed and details of your graphics card (if Nvidia). This will also create a file called `diagnostics.log` in the `alltalk_tts` folder, that you can upload if you need to create a support ticket on here.
-
- ### Installation and Setup Issues
-
-
- ๐จ Understanding Python Environments Simplified
-
- Think of Python environments like different rooms in your house, each designed for a specific purpose. Just as you wouldn't cook in the bathroom or sleep in the kitchen, different Python applications need their own "spaces" or environments because they have unique requirements. Sometimes, these requirements can clash with those of other applications (imagine trying to cook a meal in a bathroom!). To avoid this, you can create separate Python environments.
-
- #### Why Separate Environments?
- Separate environments, like separate rooms, keep everything organized and prevent conflicts. For instance, one Python application might need a specific version of a library or dependency, while another requires a different version. Just as you wouldn't store kitchen utensils in the bathroom, you wouldn't want these conflicting requirements to interfere with each other. Each environment is tailored and customized for its application, ensuring it has everything it needs without disrupting others.
-
- #### How It Works in Practice:
-
- **Standalone AllTalk Installation:** When you install AllTalk standalone, it's akin to adding a new room to your house specifically designed for your AllTalk activities. The setup process, using the atsetup utility, constructs this custom "room" (Python environment `alltalk_environment`) with all the necessary tools and furnishings (libraries and dependencies) that AllTalk needs to function smoothly, without meddling with the rest of your "house" (computer system). The AllTalk environment is started each time you run `start_alltalk` or `start_environment` within the AllTalk folder.
-
- **Text-generation-webui Installation:** Similarly, installing Text-generation-webui is like setting up another specialized room. Upon installation, it automatically creates its own tailored environment, equipped with everything required for text generation, ensuring a seamless and conflict-free operation. The Text-generation-webui environment is started each time you run `start_*your-os-version*` or `cmd_*your-os-version*` within the Text-generation-webui folder.
-
- #### Managing Environments:
- Just as you might renovate a room or bring in new furniture, you can also update or modify Python environments as needed. Tools like Conda or venv make it easy to manage these environments, allowing you to create, duplicate, activate, or delete them much like how you might manage different rooms in your house for comfort and functionality.
-
- Once you're in the right environment, by activating it, installing or updating dependencies (the tools and furniture of your Python application) is straightforward. Using pip, a package installer for Python, you can easily add what you need. For example, to install all required dependencies listed in a requirements.txt file, you'd use:
-
- `pip install -r requirements.txt`
-
- This command tells pip to read the list of required packages and versions from the requirements.txt file and install them in the current environment, ensuring your application has everything it needs to operate. It's like having a shopping list for outfitting a room and ensuring you have all the right items delivered and set up.
-
- Remember, just as it's important to use the right tools for tasks in different rooms of your house, it's crucial to manage your Python environments and dependencies properly to ensure your applications run as intended.
-
- #### How do I know if I am in a Python environment?:
- When a Python environment starts up, it changes the command prompt to show the Python environment that it currently running within that terminal/console.
-
- 
-
-
-
- ๐จ Windows & Python requirements for compiling packages (ERROR: Could not build wheels for TTS)
-
- `ERROR: Microsoft Visual C++ 14.0 or greater is required` or `ERROR: Could not build wheels for TTS.` or `ModuleNotFoundError: No module named 'TTS`
-
- Python requires that you install C++ development tools on Windows. This is detailed on the [Python site here](https://wiki.python.org/moin/WindowsCompilers). You would need to install `MSVCv142 - VS 2019 C++ x64/x86 build tools` and `Windows 10/11 SDK` from the C++ Build tools section.
-
- You can get hold of the **Community** edition [here](https://visualstudio.microsoft.com/downloads/) the during installation, selecting `C++ Build tools` and then `MSVCv142 - VS 2019 C++ x64/x86 build tools` and `Windows 10/11 SDK`.
-
- 
-
-
-
- ๐จ Standalone Install - start_{youros}.xx opens and closes instantly and AllTalk doesnt start
-
- This is more than likely caused by having a space ` ` in your folder path e.g. `c:\program files\alltalk_tts`. In this circumstance you would be best moving the folder to a path without a space e.g. `c:\myfiles\alltalk_tts`. You would have to delete the `alltalk_environment` folder and `start_alltalk.bat` or `start_alltalk.sh` and then re-run `atsetup` to re-create the environment and startup files.
-
-
-
- ๐จ Text-geneneration-webui & Stable-Diffusion Plugin - Load Order & stripped text
-
- The Stable Diffusion plugin for Text-generation-webui **strips out** some of the text, which is passed to Stable Diffusion for image/scene generation. Because this text is stripped, its important to consider the load order of the plugins to get the desired result you want. Lets assume the AI has just generated the following message `*He walks into the room with a smile on his face and says* Hello how are you?`. Depending on the load order will change what text reaches AllTalk for generation e.g.
-
- **SD Plugin loaded before AllTalk** - Only `Hi how are you?` is sent to AllTalk, with the `*He walks into the room with a smile on his face and says*` being sent over to SD for image generation. Narration of the scene is not possible.
- **AllTalk loaded before SD Plugin** - `*He walks into the room with a smile on his face and says* Hello how are you?` is sent to AllTalk with the `*He walks into the room with a smile on his face and says*` being sent over to SD for image generation.
- The load order can be changed within Text-generation-webui's `settings.yaml` file or `cmd_flags.txt` (depending on how you are managing your extensions).
-
-
-
- ๐จ I think AllTalks requirements file has installed something another extension doesn't like
-
- Ive paid very close attention to **not** impact what Text-generation-webui is requesting on a factory install. This is one of the requirements of submitting an extension to Text-generation-webui. If you want to look at a comparison of a factory fresh text-generation-webui installed packages (with cuda 12.1, though AllTalk's requirements were set on cuda 11.8) you can find that comparison [here](https://github.com/erew123/alltalk_tts/issues/23). This comparison shows that AllTalk is requesting the same package version numbers as Text-generation-webui or even lower version numbers (meaning AllTalk will not update them to a later version). What other extensions do, I cant really account for that.
-
- I will note that the TTS engine downgrades Pandas data validator to 1.5.3 though its unlikely to cause any issues. You can upgrade it back to text-generation-webui default (december 2023) with `pip install pandas==2.1.4` when inside of the python environment. I have noticed no ill effects from it being a lower or higher version, as far as AllTalk goes. This is also the same behaviour as the Coqui_tts extension that comes with Text-generation-webui.
-
- Other people are reporting issues with extensions not starting with errors about Pydantic e.g. ```pydantic.errors.PydanticImportError: BaseSettings` has been moved to the pydantic-settings package. See https://docs.pydantic.dev/2.5/migration/#basesettings-has-moved-to-pydantic-settings for more details.```
-
- Im not sure if the Pydantic version has been recently updated by the Text-generation-webui installer, but this is nothing to do with AllTalk. The other extension you are having an issue with, need to be updated to work with Pydantic 2.5.x. AllTalk was updated in mid december to work with 2.5.x. I am not specifically condoning doing this, as it may have other knock on effects, but within the text-gen Python environment, you can use `pip install pydantic==2.5.0` or `pip install pydantic==1.10.13` to change the version of Pydantic installed.
-
-
- ๐จ I am having problems getting AllTalk to start after changing settings or making a custom setup/model setup.
-
- I would suggest following [Problems Updating](https://github.com/erew123/alltalk_tts#-problems-updating) and if you still have issues after that, you can raise an issue [here](https://github.com/erew123/alltalk_tts/issues)
-
-
- ### Networking and Access Issues
-
-
- ๐จ I cannot access AllTalk from another machine on my Network
-
- You will need to change the IP address within AllTalk's settings from being 127.0.0.1, which only allows access from the local machine its installed on. To do this, please see [Changing AllTalks IP address & Accessing AllTalk over your Network](https://github.com/erew123/alltalk_tts/tree/main?tab=readme-ov-file#-changing-alltalks-ip-address--accessing-alltalk-over-your-network) at the top of this page.
-
- You may also need to allow access through your firewall or Antivirus package to AllTalk.
-
-
-
- ๐จ I am running a Headless system and need to change the IP Address manually as I cannot reach the config page
-
- To do this you can edit the `confignew.json` file within the `alltalk_tts` folder. You would look for `"ip_address": "127.0.0.1",` and change the `127.0.0.1` to your chosen IP address,then save the file and start AllTalk.
-
- When doing this, be careful not to impact the formatting of the JSON file. Worst case, you can re-download a fresh copy of `confignew.json` from this website and that will put you back to a factory setting.
-
-
- ### Configuration and Usage Issues
-
- ๐จ I activated DeepSpeed in the settings page, but I didnt install DeepSpeed yet and now I have issues starting up
-
- You can either follow the [Problems Updating](https://github.com/erew123/alltalk_tts#-problems-updating) and fresh install your config. Or you can edit the `confignew.json` file within the `alltalk_tts` folder. You would look for '"deepspeed_activate": true,' and change the word true to false `"deepspeed_activate": false,' ,then save the file and try starting again.
-
- If you want to use DeepSpeed, you need an Nvidia Graphics card and to install DeepSpeed on your system. Instructions are [here](https://github.com/erew123/alltalk_tts#-deepspeed-installation-options)
-
-
-
- ๐จ I am having problems updating/some other issue where it wont start up/Im sure this is a bug
-
- Please see [Problems Updating](https://github.com/erew123/alltalk_tts#-problems-updating). If that doesnt help you can raise an ticket [here](https://github.com/erew123/alltalk_tts/issues). It would be handy to have any log files from the console where your error is being shown. I can only losely support custom built Python environments and give general pointers. Please create a `diagnostics.log` report file to submit with a support request.
-
- Also, is your text-generation-webui up to date? [instructions here](https://github.com/oobabooga/text-generation-webui?tab=readme-ov-file#how-to-install)
-
-
-
- ๐จ Error Details: HTTPConnectionPool(host='null', port=80): Max retries exceeded with url: /api/tts-generate............ etc
-
- If you get this error, the gradio window you have open has lost connection to the AllTalk backend. This is most likely caused by AllTalk being restarted in the background and you not opening the gradio window again. Simply refresh the gradio web page and it will be connected back in and working.
-
-
-
-
- ๐จ I see some red "asyncio" messages
-
- As far as I am aware, these are to do with the chrome browser the gradio text-generation-webui in some way. I raised an issue about this on the text-generation-webui [here](https://github.com/oobabooga/text-generation-webui/issues/4788) where you can see that AllTalk is not loaded and the messages persist. Either way, this is more a warning than an actual issue, so shouldnt affect any functionality of either AllTalk or text-generation-webui, they are more just an annoyance.
-
-
- ### Startup, Performance and Compatibility Issues
-
-
- ๐จ Understanding the AllTalk start-up screen
-
- The AllTalk start-up screen provides various bits of information about the detected Python environment and errors.
-
- 
-
- **Config file check**
- - Sometimes I need to add/remove something to your existing configuration file settings. Obviously, I donโt want to impact your existing settings, however any new features may need these settings to be created before AllTalk starts up. Ive added extra code that checks `alltalk_tts/system/config/at_configupdate.json` and `alltalk_tts/system/config/at_configdowngrade.json`, either adding or removing items to your configuration as necessary. If a change is made, you will be notified and a backup of the previous configuration file will be created in the `alltalk_tts` folder.
-
- **AllTalk startup Mode**
- - informational. This will state if AllTalk has detected it is running as part of Text-generation-webui or as a Standalone Application.
-
- **WAV file deletion**
- - If you have set deletion of old generated WAV files, this will state the time frame after which they are purged.
-
- **DeepSpeed version**
- - What version of DeepSpeed is installed/detected. This will **not** tell you if the version of DeepSpeed is compiled for your Python, PyTorch or CUDA version. Its important to remember that DeepSpeed has to be compiled for the exact version of Python, PyTorch and CUDA that you are using, so please ensure you have the correct DeepSpeed version installed if necessary.
-
- **Model is available**
- - AllTalk is checking if your model files exist. This is not a validity check of the actual model files, they can still be corrupted. If files are missing, AllTalk will attempt to download them from Huggingface, however, if Huggingface has an outage/issue or your internet connection has issues, its possible corrupted or incomplete files will be downloaded. Please read `RuntimeError: PytorchStreamReader failed reading zip archive: failed finding central directory` if you need to confirm your model files are ok.
-
- **Current Python Version**
- - Informational. Literally tells you the version of Python running in your Python environment.
-
- **Current PyTorch Version**
- - Informational. Tell tells you the version of **PyTorch** running in your Python environment, however if you have an Nvidia card, you should be running a CUDA based version of Pytorch. This is indicated with a `+cXXX` after the PyTorch version e.g. `2.2.2+cu121` would be PyTorch version 2.2.2 with CUDA 12.1 extensions. If you donโt have the PyTorch CUDA extensions installed, but you do have an Nvidia card, you may need to re-install PyTorch.
-
- **Current CUDA Version**
- - Informational. This is linked to the Current PyTorch Version, as detailed above.
-
- **Current TTS Version**
- - Informational. The current version of the TTS engine that is running.
-
- **AllTalk Github updated**
- - As long as you have an internet connection, this will tell you last time AllTalk was updated on Github. It is checking the [commit list](https://github.com/erew123/alltalk_tts/commits/main/) to see when the last commit was made. As such, this could be simply a documentation update, a bug fix or new features. Its simply there as a guide to let you know the last time something was changed on AllTalk's Github.
-
- **TTS Subprocess**
- - When AllTalk reaches this stage, the subprocess that loads in the AI model is starting. This is most likely where an error could occur with loading the TTS model, just after the documentation message.
-
- **AllTalk Settings & Documentation: http ://x.x.x.x**
- - The link where you can reach AllTalks built in settings and documentation page. The TTS model will be loading immediately after this is displayed.
-
-
-
-
- ๐จ AllTalk is only loading into CPU, but I have an Nvidia GPU so it should be loading into CUDA
-
- This is caused by Pytorch (Torch) not having the CUDA extensions installed (You can check by running the diagnostics). Typically this happens (on Standalone installations) because when the setup routine goes to install Pytorch with CUDA, it looks in the PIP cache and if a previous application has downloaded a version of Pytorch that **doesn't** have CUDA extensions, the PIP installer doesnt recognise this fact and just uses the cached version for installation. To resolve this:
-
- 1) On the `atsetup` utility, on the `Standalone menu` select to `Purge the PIP cache`. This will remove cached packages from the PIP cache, meaning it will have to download fresh copies.
- 2) As we need to force the upgrade to the Python environment, the easiest way to do this will be to use `atsetup` to `Delete AllTalk's custom Python environment`. This means it will have to rebuild the Python environment. **Note**, you may have to run this step twice, as it has to exit the current Python environment, then you have to re-load `atsetup` and select `Delete AllTalk's custom Python environment` again.
- 3) You can now use `atsetup` to `Install AllTalk as a Standalone Application` which will download fresh copies of everything and re-install the Python environment.
- 4) Once this is done you can check if CUDA is now working with the diagnostics or starting AllTalk and checking the model loads into CUDA.
-
-
-
-
-
- ๐จ RuntimeError: PytorchStreamReader failed reading zip archive: failed finding central directory
-
- This error message is caused by the model being corrupted or damaged in some way. This error can occur if Huggingface, where the model is downloaded from, have an error (when the model is downloaded) or potentailly internet issues occuring while the model is downloaded on first start-up.
-
- ```
- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
- RuntimeError: PytorchStreamReader failed reading zip archive: failed finding central directory
-
- ERROR: Application startup failed. Exiting.
- [AllTalk Startup] Warning TTS Subprocess has NOT started up yet, Will keep trying for 120 seconds maximum. Please wait.
- ```
-
- To resolve this, first look in your `alltalk_tts/models/xttsv2_2.0.2` (or whichever) model folder and confirm that the file sizes are correct.
-
- 
-
- You can delete one or more suspect files and a factory fresh copy of that file or files will be downloaded on next start-up of AllTalk.
-
-
-
-
- ๐จ RuntimeError: Found no NVIDIA driver on your system.
-
- This error message is caused by DeepSpeed being enabled when you do not have a Nvidia GPU. To resolve this, edit `confignew.json` and change `"deepspeed_activate": true,` to `"deepspeed_activate": false,` then restart AllTalk.
-
- ```
- File "C:\alltalk_tts\alltalk_environment\env\Lib\site-packages\torch\cuda\__init__.py", line 302, in _lazy_init
- torch._C._cuda_init()
- RuntimeError: Found no NVIDIA driver on your system. Please check that you have an NVIDIA GPU and installed a driver from http://www.nvidia.com/Download/index.aspx
-
- ERROR: Application startup failed. Exiting.
- [AllTalk Startup] Warning TTS Subprocess has NOT started up yet, Will keep trying for 120 seconds maximum. Please wait.
- ```
-
-
-
-
- ๐จ raise RuntimeError("PyTorch version mismatch! DeepSpeed ops were compiled and installed.
-
- This error message is caused by having DeepSpeed enabled, but you have a version of DeepSpeed installed that was compiled for a different version of Python, PyTorch or CUDA (or any mix of those). You will need to start your Python environment and run `pip uninstall deepspeed` to remove DeepSpeed from your Python environment and then install the correct version of DeepSpeed.
-
- ```
- raise RuntimeError("PyTorch version mismatch! DeepSpeed ops were compiled and installed
- RuntimeError: PyTorch version mismatch! DeepSpeed ops were compiled and installed with a different version than what is being used at runtime. Please re-install DeepSpeed or switch torch versions. Install torch version=2.1, Runtime torch version=2.2
- ```
-
-
-
-
- ๐จ Warning TTS Subprocess has NOT started up yet, Will keep trying for 120 seconds maximum. Please wait. It times out after 120 seconds.
- When the subprocess is starting 2x things are occurring:
-
- **A)** Its trying to load the voice model into your graphics card VRAM (assuming you have a Nvidia Graphics card, otherwise its your system RAM)
- **B)** Its trying to start up the mini-webserver and send the "ready" signal back to the main process.
-
- Before giving other possibilities a go, some people with **old machines** are finding their startup times are **very** slow 2-3 minutes. Ive extended the allowed time within the script from 1 minute to 2 minutes. **If you have an older machine** and wish to try extending this further, you can do so by editing `script.py` and changing `startup_wait_time = 120` (120 seconds, aka 2 minutes) at the top of the script.py file, to a larger value e.g `startup_wait_time = 240` (240 seconds aka 4 minutes).
-
- **Note:** If you need to create a support ticket, please create a `diagnostics.log` report file to submit with a support request. Details on doing this are above.
-
- Other possibilities for this issue are:
-
- 1) You are starting AllTalk in both your `CMD FLAG.txt` and `settings.yaml` file. The `CMD FLAG.txt` you would have manually edited and the `settings.yaml` is the one you change and save in the `session` tab of text-generation-webui and you can `Save UI defaults to settings.yaml`. Please only have one of those two starting up AllTalk.
-
- 2) You are not starting text-generation-webui with its normal Python environment. Please start it with start_{your OS version} as detailed [here](https://github.com/oobabooga/text-generation-webui#how-to-install) (`start_windows.bat`,`./start_linux.sh`, `start_macos.sh` or `start_wsl.bat`) OR (`cmd_windows.bat`, `./cmd_linux.sh`, `cmd_macos.sh` or `cmd_wsl.bat` and then `python server.py`).
-
- 3) You have installed the wrong version of DeepSpeed on your system, for the wrong version of Python/Text-generation-webui. You can go to your text-generation-webui folder in a terminal/command prompt and run the correct cmd version for your OS e.g. (`cmd_windows.bat`, `./cmd_linux.sh`, `cmd_macos.sh` or `cmd_wsl.bat`) and then you can type `pip uninstall deepspeed` then try loading it again. If that works, please see here for the correct instructions for installing DeepSpeed [here](https://github.com/erew123/alltalk_tts#-deepspeed-installation-options).
-
- 4) You have an old version of text-generation-webui (pre Dec 2023) I have not tested on older versions of text-generation-webui, so cannot confirm viability on older versions. For instructions on updating the text-generation-webui, please look [here](https://github.com/oobabooga/text-generation-webui#how-to-install) (`update_linux.sh`, `update_windows.bat`, `update_macos.sh`, or `update_wsl.bat`).
-
- 5) You already have something running on port 7851 on your computer, so the mini-webserver cant start on that port. You can change this port number by editing the `confignew.json` file and changing `"port_number": "7851"` to `"port_number": "7602"` or any port number you wish that isnโt reserved. Only change the number and save the file, do not change the formatting of the document. This will at least discount that you have something else clashing on the same port number.
-
- 6) You have antivirus/firewalling that is blocking that port from being accessed. If you had to do something to allow text-generation-webui through your antivirus/firewall, you will have to do that for this too.
-
- 7) You have quite old graphics drivers and may need to update them.
-
- 8) Something within text-generation-webui is not playing nicely for some reason. You can go to your text-generation-webui folder in a terminal/command prompt and run the correct cmd version for your OS e.g. (`cmd_windows.bat`, `./cmd_linux.sh`, `cmd_macos.sh` or `cmd_wsl.bat`) and then you can type `python extensions\alltalk_tts\script.py` and see if AllTalk starts up correctly. If it does then something else is interfering.
-
- 9) Something else is already loaded into your VRAM or there is a crashed python process. Either check your task manager for erroneous Python processes or restart your machine and try again.
-
- 10) You are running DeepSpeed on a Linux machine and although you are starting with `./start_linux.sh` AllTalk is failing there on starting. This is because text-generation-webui will overwrite some environment variables when it loads its python environment. To see if this is the problem, from a terminal go into your text-generation-webui folder and `./cmd_linux.sh` then set your environment variable again e.g. `export CUDA_HOME=/usr/local/cuda` (this may vary depending on your OS, but this is the standard one for Linux, and assuming you have installed the CUDA toolkit), then `python server.py` and see if it starts up. If you want to edit the environment permanently you can do so, I have not managed to write full instructions yet, but here is the conda guide [here](https://conda.io/projects/conda/en/latest/user-guide/tasks/manage-environments.html#set-env-vars).
-
- 11) You have built yourself a custom Python environment and something is funky with it. This is very hard to diagnose as its not a standard environment. You may want to updating text-generation-webui and re installing its requirements file (whichever one you use that comes down with text-generation-webui).
-
-
-
- ๐จ I have multiple GPU's and I have problems running Finetuning
-
- Finetuning pulls in various other scripts and some of those scripts can have issues with multiple Nvidia GPU's being present. Until the people that created those other scripts fix up their code, there is a workaround to temporarily tell your system to only use the 1x of your Nvidia GPU's. To do this:
-
- - **Windows** - You will start the script with `set CUDA_VISIBLE_DEVICES=0 && python finetune.py`
- After you have completed training, you can reset back with `set CUDA_VISIBLE_DEVICES=`
-
- - **Linux** - You will start the script with `CUDA_VISIBLE_DEVICES=0 python finetune.py`
- After you have completed training, you can reset back with `unset CUDA_VISIBLE_DEVICES`
-
- Rebooting your system will also unset this. The setting is only applied temporarily.
-
- Depending on which of your Nvidia GPU's is the more powerful one, you can change the `0` to `1` or whichever of your GPU's is the most powerful.
-
-
-
-
- ๐จ Firefox - Streaming Audio doesnt work on Firefox
-
- This is a long standing issue with Mozilla & Firefox and one I am unable to resolve as Mozilla have not resolved the issue with Firefox. The solution is to use another web browser if you want to use Streaming audio. For details of my prior invesitigation please look at this [ticket](https://github.com/erew123/alltalk_tts/issues/143)
-
-
- ### Application Specific Issues
-
-
- ๐จ SillyTavern - I changed my IP address and now SillyTavern wont connect with AllTalk
- SillyTavern checks the IP address when loading extensions, saving the IP to its configuration only if the check succeeds. For whatever reason, SillyTavern's checks dont always allow changing its IP address a second time.
-
- To manually change the IP address:
-
- 1) Navigate to the SillyTavern Public folder located at `/sillytavern/public/`.
- 2) Open the `settings.json` file.
- 3) Look for the AllTalk section and find the `provider_endpoint` entry.
- 3) Replace `localhost` with your desired IP address, for example, `192.168.1.64`.
-
- 
-
-
- ### TTS Generation Issues & Questions
-
-
- ๐จ XTTS - Does the XTTS AI Model Support Emotion Control or Singing?
-
- No, the XTTS AI model does not currently support direct control over emotions or singing capabilities. While XTTS infuses generated speech with a degree of emotional intonation based on the context of the text, users cannot explicitly control this aspect. It's worth noting that regenerating the same line of TTS may yield slightly different emotional inflections, but there is no way to directly control it with XTTS.
-
-
- ๐จ XTTS - Skips, repeats or pronunciation Issues
-
- Firstly, it's important to clarify that the development and maintenance of the XTTS AI models and core scripts are handled by [Coqui](https://docs.coqui.ai/en/latest/index.html), with additional scripts and libraries from entities like [huggingface](https://huggingface.co/docs/transformers/en/index) among many other Python scripts and libraries used by AllTalk.
-
- AllTalk is designed to be a straightforward interface that simplifies setup and interaction with AI TTS models like XTTS. Currently, AllTalk supports the XTTS model, with plans to include more models in the future. Please understand that the deep inner workings of XTTS, including reasons why it may skip, repeat, or mispronounce, along with 3rd party scripts and libraries utilized, are ultimately outside my control.
-
- Although I ensure the text processed through AllTalk is accurately relayed to the XTTS model speech generation process, and I have aimed to mitigate as many issues as much as possible; skips, repeats and bad pronounciation can still occur.
-
- Certain aspects I have not been able to investigate due to my own time limitations, are:
-
- - The impact of DeepSpeed on TTS quality. Is this more likely to cause skips or repetition?
- - Comparative performance between different XTTS model versions (e.g., 2.0.3 vs. 2.0.2) regarding audio quality and consistency.
-
- **From my experience and anecdotally gained knowledge:**
-
- - Lower quality voice samples tend to produce more anomalies in generated speech.
- - Model finetuning with high-quality voice samples significantly reduces such issues, enhancing overall speech quality.
- - Unused/Excessive punctuation causes issues e.g. asterisks `*`, hashes `#`, brackets `(` `)` etc. Many of these AllTalk will filter out.
-
- So for example, the `female_01.wav` file that is provided with AllTalk is a studio quality voice sample, which the XTTS model was trained on. Typically you will find it unlikely that anomolies occur with TTS generation when using this voice sample. Hence good quality samples and finetuning, generally improve results with XTTS.
-
- If you wish to try out the XTTS version 2.0.3 model and see if it works better, you can download it from [here](https://huggingface.co/coqui/XTTS-v2/tree/v2.0.3), replacing all the files within your `/alltalk_tts/models/xttsv2_2.0.2` folder. This is on my list to both test version 2.0.3 more, but also build a more flexible TTS models downloader, that will not only accomdating other XTTS models, but also other TTS engines. If you try the XTTS version 2.0.3 model and gleen any insights, please let me know.
-
- """
-
- finetuning_a_model = """
- ### โซ Finetuning a model
- If you have a voice that the model doesnt quite reproduce correctly, or indeed you just want to improve the reproduced voice, then finetuning is a way to train your "XTTSv2 local" model **(stored in `/alltalk_tts/models/xxxxx/`)** on a specific voice. For this you will need:
-
- - An Nvidia graphics card. (Please see the help section [note](https://github.com/erew123/alltalk_tts/edit/main/README.md#performance-and-compatibility-issues) if you have multiple Nvidia GPU's).
- - 18GB of disk space free (most of this is used temporarily)
- - At least 2 minutes of good quality speech from your chosen speaker in mp3, wav or flacc format, in one or more files (have tested as far as 20 minutes worth of audio).
- - As a side note, many people seem to think that the Whisper v2 model (used on Step 1) is giving better results at generating training datasets, so you may prefer to try that, as opposed to the Whisper 3 model.
-
- #### โซ How will this work/How complicated is it?
- Everything has been done to make this as simple as possible. At its simplest, you can literally just download a large chunk of audio from an interview, and tell the finetuning to strip through it, find spoken parts and build your dataset. You can literally click 4 buttons, then copy a few files and you are done. At it's more complicated end you will clean up the audio a little beforehand, but its still only 4x buttons and copying a few files.
-
- #### โซ The audio you will use
- I would suggest that if its in an interview format, you cut out the interviewer speaking in audacity or your chosen audio editing package. You dont have to worry about being perfect with your cuts, the finetuning Step 1 will go and find spoken audio and cut it out for you. Is there is music over the spoken parts, for best quality you would cut out those parts, though its not 100% necessary. As always, try to avoid bad quality audio with noises in it (humming sounds, hiss etc). You can try something like [Audioenhancer](https://audioenhancer.ai/) to try clean up noisier audio. There is no need to down-sample any of the audio, all of that is handled for you. Just give the finetuning some good quality audio to work with.
-
- #### โซ Can I Finetune a model more than once on more than one voice
- Yes you can. You would do these as multiple finetuning's, but its absolutely possible and fine to do. Finetuning the XTTS model does not restrict it to only being able to reproduce that 1x voice you trained it on. Finetuning is generally nudging the model in a direction to learn the ability to sound a bit more like a voice its not heard before.
-
- #### โซ A note about anonymous training Telemetry information & disabling it
- Portions of Coqui's TTS trainer scripts gather anonymous training information which you can disable. Their statement on this is listed [here](https://github.com/coqui-ai/Trainer?tab=readme-ov-file#anonymized-telemetry). If you start AllTalk Finetuning with `start_finetuning.bat` or `./start_finetuning.sh` telemetry will be disabled. If you manually want to disable it, please expand the below:
-
-
- Manually disable telemetry
-
- Before starting finetuning, run the following in your terminal/command prompt:
-
- - On Windows by typing `set TRAINER_TELEMETRY=0`
- - On Linux & Mac by typing `export TRAINER_TELEMETRY=0`
-
- Before you start `finetune.py`. You will now be able to finetune offline and no anonymous training data will be sent.
-
- """
-
- finetuning_setup = """
- #### โซ Prerequisites for Fine-tuning with Nvidia CUDA Development Toolkit 11.8
-
- All the requirements for Finetuning will be installed by using the atsetup utility and installing your correct requirements (Standalone or for Text-generation-webui). The legacy manual instructions are stored below, however these shouldnt be required.
-
-
- Legacy manual instructions for installing Nvidia CUDA Development Toolkit 11.8
- - To perform fine-tuning, a specific portion of the **Nvidia CUDA Development Toolkit v11.8** must be installed. This is crucial for step 1 of fine-tuning. The objective is to minimize the installation footprint by installing only the essential components.
- - The **Nvidia CUDA Development Toolkit v11.8** operates independently from your graphics card drivers and the CUDA version utilized by your Python environment.
- - This installation process aims to keep the download and install size as minimal as possible, however a full install of the tookit requires 3GB's of disk space.
- - When running Finetuning it will require upto 20GB's of temporary disk space, so please ensure you have this space available and preferably use a SSD or NVME drive.
-
- 1. **Download the Toolkit**:
- - Obtain the **network install** version of the Nvidia CUDA Development Toolkit 11.8 from [Nvidia's Archive](https://developer.nvidia.com/cuda-11-8-0-download-archive).
-
- 2. **Run the Installer**:
- - Choose **Custom (Advanced)** installation.
- - Deselect all options initially.
- - Select the following components:
- - `CUDA` > `Development` > `Compiler` > `nvcc`
- - `CUDA` > `Development` > `Libraries` > `CUBLAS` (**both** development and runtime)
-
- 3. **Configure Environment Search Path**:
- - It's essential that `nvcc` and CUDA 11.8 library files are discoverable in your environment's search path. Adjustments can be reverted post-fine-tuning if desired.
-
- **For Windows**:
- - Edit the `Path` environment variable to include `C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v11.8\bin`.
- - Add `CUDA_HOME` and set its path to `C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v11.8.`
-
- **For Linux**:
- - The path may vary by Linux distribution. Here's a generic setup:
- - `export CUDA_HOME=/usr/local/cuda`
- - `export PATH=${CUDA_HOME}/bin:${PATH}`
- - `export LD_LIBRARY_PATH=${CUDA_HOME}/lib64:$LD_LIBRARY_PATH`
-
- - Consider adding these to your `~/.bashrc` for permanence, or apply temporarily for the current session by running the above commands each time you start your Python environment.
-
- **Note**: If using Text-generation-webui, its best to set these temporarily.
-
- 4. **Verify Installation**:
- - Open a **new** terminal/command prompt to refresh the search paths.
- - In a terminal or command prompt, execute `nvcc --version`.
- - Success is indicated by a response of `Cuda compilation tools, release 11.8.` Specifically, ensure it is version 11.8.
-
- 5. **Troubleshooting**:
- - If the correct version isn't reported, recheck your environment path settings for accuracy and potential conflicts with other CUDA versions.
-
-
- #### Additional Note on Torch and Torchaudio:
- - Ensure Torch and Torchaudio are CUDA-enabled (any version), which is separate from the CUDA Toolkit installation. CUDA 11.8 corresponds to `cu118` and CUDA 12.1 to `cu121` in AllTalk diagnostics.
- - Failure to install CUDA for Torch and Torchaudio will result in Step 2 of fine-tuning failing. These requirements are distinct from the CUDA Toolkit installation, so avoid conflating the two.
-
-
- #### โซ Starting Fine-tuning
-
- **NOTE:** Ensure AllTalk has been launched at least once after any updates to download necessary files for fine-tuning.
-
- 1. **Close Resource-Intensive Applications**:
- - Terminate any applications that are using your GPU/VRAM to ensure enough resources for fine-tuning.
-
- 2. **Organize Voice Samples**:
- - Place your audio samples into the following directory:
- `/alltalk_tts/finetune/put-voice-samples-in-here/`
-
- Depending on your setup (Text-generation-webui or Standalone AllTalk), the steps to start the Python environment vary:
-
- ### **For Standalone AllTalk Users**:
- Navigate to the `alltalk_tts` folder:
- > `cd alltalk_tts`
-
- Start the Python environment:
- > **Windows**: `start_finetune.bat`
- **Linux**: `./start_finetune.sh`
-
- ### **For Text-generation-webui Users**:
- Navigate to the Text-generation-webui directory:
- >`cd text-generation-webui`
-
- Start the Python environment suitable for your OS:
- **Windows**: `cmd_windows.bat`
- **Linux**: `./cmd_linux.sh`
- **macOS**: `cmd_macos.sh`
- **WSL (Windows Subsystem for Linux)**: `cmd_wsl.bat`
-
- Move into the AllTalk directory:
- >`cd extensions/alltalk_tts`
-
- **Linux** users only need to run this command:
- `
- export LD_LIBRARY_PATH=`python3 -c 'import os; import nvidia.cublas.lib; import nvidia.cudnn.lib; print(os.path.dirname(nvidia.cublas.lib.__file__) + ":" + os.path.dirname(nvidia.cudnn.lib.__file__))'`
- `
- Start the fine-tuning process with the command:
- > `python finetune.py`
-
- > If you're unfamiliar with Python environments and wish to learn more, consider reviewing **Understanding Python Environments Simplified** in the Help section.
-
- ### Both Standalone & Text-generation-webui users
- 3. **Pre-Flight Checklist**:
- > Go through the pre-flight checklist to ensure readiness. Address any issues flagged as "Fail".
-
- 4. **Post Fine-tuning Actions**:
- > Upon completing fine-tuning, the final tab will guide you on managing your files and relocating your newly trained model to the appropriate directory.
- """
-
- finetuning_details = """
- #### โซ How many Epochs etc is the right amount?
-
- In finetuning the suggested/recommended amount of epochs, batch size, evaluation percent etc is already set. However, there is no absolutely correct answer to what the settings should be, it all depends on what you are doing.
-
- - If you just want to train a normal human voice that is in an existing language, for most peopleโs needs, the base settings would work fine. You may choose to increase the epochs up to maybe 20, or run a second round of training if needed.
- - If you were training an entirely new language, you would need a huge amount of training data and it requires around 1000 epochs (based on things I can find around the internet of people who tried this).
- - If you are training a cartoon style voice in an existing language, it may need well upwards of 40 epochs until it can reproduce that voice with some success.
-
- There are no absolute correct settings, as there are too many variables, ranging from the amount of samples you are using (5 minutes worth? 4 hours worth? etc), if they are similar samples to what the AI model already understands, so on and so forth. Coqui whom originally trained the model usually say something along the lines of, once youโve trained it X amount, if it sounds good then you are done and if it doesnโt, train it more.
-
- #### โซ Evaluation Data Percentage
- In the process of finetuning, it's crucial to balance the data used for training the model against the data reserved for evaluating its performance. Typically, a portion of the dataset is set aside as an 'evaluation set' to assess the model's capabilities in dealing with unseen data. On Step 1 of finetuning you have the option to adjust this evaluation data percentage, offering more control over your model training process.
- **Why Adjust the Evaluation Percentage?**
- Adjusting the evaluation percentage **can** be beneficial in scenarios with limited voice samples. When dealing with a smaller dataset, allocating a slightly larger portion to training could enhance the model's ability to learn from these scarce samples. Conversely, with abundant data, a higher evaluation percentage might be more appropriate to rigorously test the model's performance. There are currently no absolutely optimal split percentages as it varies by dataset.
- - **Default Setting:** The default evaluation percentage is set at 15%, which is a balanced choice for most datasets.
- - **Adjustable Range:** Users can now adjust this percentage, but itโs generally recommend keeping it between 5% and 30%.
- - **Lower Bound:** A minimum of 5% ensures that there's enough data to evaluate model performance.
- - **Upper Bound:** Its suggested not exceeding 30% for evaluation to avoid limiting the amount of data available for training.
-
- - **Understanding the Impact:** Before adjusting this setting, it's important to understand its impact on model training and evaluation. Incorrect adjustments can lead to suboptimal model performance.
- - **Gradual Adjustments:** For those unfamiliar with the process, we recommend reading up on training data and training sets, then making small, incremental changes and observing their effects.
- - **Data Quality:** Regardless of the split, the quality of the audio data is paramount. Ensure that your datasets are built from good quality audio with enough data within them.
-
- #### โซ Using a Finetuned model in Text-generation-webui
-
- At the end of the finetune process, you will have an option to `Compact and move model to /trainedmodel/` this will compact the raw training file and move it to `/model/trainedmodel/`. When AllTalk starts up within Text-generation-webui, if it finds a model in this location a new loader will appear in the interface for `XTTSv2 FT` and you can use this to load your finetuned model.
**Be careful** not to train a new model from the base model, then overwrite your current `/model/trainedmodel/` **if** you want a seperately trained model. This is why there is an `OPTION B` to move your just trained model to `/models/lastfinetuned/`.
-
- #### โซ Training one model with multiple voices
-
- At the end of the finetune process, you will have an option to `Compact and move model to /trainedmodel/` this will compact the raw training file and move it to `/model/trainedmodel/`. This model will become available when you start up finetuning. You will have a choice to train the Base Model or the `Existing finetuned model` (which is the one in `/model/trainedmodel/`). So you can use this to keep further training this model with additional voices, then copying it back to `/model/trainedmodel/` at the end of training.
-
- #### โซ Do I need to keep the raw training data/model?
-
- If you've compacted and moved your model, its highly unlikely you would want to keep that data, however the choice is there to keep it if you wish. It will be between 5-10GB in size, so most people will want to delete it.
-
- #### โซ I have deeper questions about training the XTTS model, where can I find more information?
-
- If you have deeper questions about the XTTS model, its capabilites, the training process etc, anything thats not covered within the above text or the interface of `finetune.py`, please use the following links to research Coqui's documentation on the XTTS model.
-
- - https://docs.coqui.ai/en/latest/models/xtts.html
- - https://github.com/coqui-ai/TTS
- - https://github.com/coqui-ai/TTS/discussions
-
- """
-
- tts_generator = """
- ### โฌ AllTalk TTS Generator
- AllTalk TTS Generator is the solution for converting large volumes of text into speech using the voice of your choice. Whether you're creating audio content or just want to hear text read aloud, the TTS Generator is equipped to handle it all efficiently. Please see here for a quick [demo](https://www.youtube.com/watch?v=hunvXn0mLzc)
The link to open the TTS generator can be found on the built-in Settings and Documentation page.
**DeepSpeed** is **highly** recommended to speed up generation. **Low VRAM** would be best turned off and your LLM model unloaded from your GPU VRAM (unload your model). **No Playback** will reduce memory overhead on very large generations (15,000 words or more). Splitting **Export to Wav** into smaller groups will also reduce memory overhead at the point of exporting your wav files (so good for low memory systems).
-
- #### โฌ Estimated Throughput
- This will vary by system for a multitude of reasons, however, while generating a 58,000 word document to TTS, with DeepSpeed enabled, LowVram disabled, splitting size 2 and on an Nvidia RTX 4070, throughput was around 1,000 words per minute. Meaning, this took 1 hour to generate the TTS. Exporting to combined wavs took about 2-3 minutes total.
-
- #### โฌ Quick Start
- > **Text Input:** Enter the text you wish to convert into speech in the 'Text Input' box.
- > **Generate TTS:** Hit this to start the text-to-speech conversion.
- > **Pause/Resume:** Used to pause and resume the playback of the initial generation of wavs or the stream.
- > **Stop Playback:** This will stop the current audio playing back. It does not stop the text from being generated however.
-
- Once you have sent text off to be generated, either as a stream or wav file generation, the TTS server will remain busy until this process has competed. As such, think carefully as to how much you want to send to the server.
- If you are generating wav files and populating the queue, you can generate one lot of text to speech, then input your next lot of text and it will continue adding to the list.
-
- #### โฌ Customization and Preferences
- > **Character Voice:** Choose the voice that will read your text.
- > **Language:** Select the language of your text.
- > **Chunk Sizes:** Decide the size of text chunks for generation. Smaller sizes are recommended for better TTS quality.
-
- #### โฌ Interface and Accessibility
- > **Dark/Light Mode:** Switch between themes for your visual comfort.
- > **Word Count and Generation Queue:** Keep track of the word count and the generation progress.
-
- #### โฌ TTS Generation Modes
- > **Wav Chunks:** Perfect for creating audio books, or anything you want to keep long term. Breaks down your text into manageable wav files and queues them up. Generation begins automatically, and playback will start after a few chunks have been prepared ahead. You can set the volume to 0 if you donโt want to hear playback. With Wav chunks, you can edit and/or regenerate portions of the TTS as needed.
-
- > **Streaming:** For immediate playback without the ability to save. Ideal for on-the-fly speech generation and listening. This will not generate wav files and it will play back through your browser. You cannot stop the server generating the TTS once it has been sent.
-
- With wav chunks you can either playback โIn Browserโ which is the web page you are on, or โOn Serverโ which is through the console/terminal where AllTalk is running from, or "No Playback". Only generation โIn Browserโ can play back smoothly and populate the Generated TTS List. Setting the Volume will affect the volume level played back both โIn Browserโ and โOn Serverโ.
-
- For generating **large amounts of TTS**, it's recommended to select the **No Playback** option. This setting minimizes the memory usage in your web browser by avoiding the loading and playing of audio files directly within the browser, which is particularly beneficial for handling extensive audio generations. The definition of large will vary depending on your system RAM availability (will update when I have more information as to guidelines). Once the audio is generated, you can export your list to JSON (for safety) and use the **Play List** option to play back your audio.
-
- #### โฌ Playback and List Management
- > **Playback Controls:** Utilize 'Play List' to start from the beginning or 'Stop Playback' to halt at any time.
- > **Custom Start:** Jump into your list at a specific ID to hear a particular section.
- > **Regeneration and Editing:** If a chunk isn't quite right, you can opt to regenerate it or edit the text directly. Click off the text to save changes and hit regenerate for the specific line.
- > **Export/Import List:** Save your TTS list as a JSON file or import one. Note: Existing wav files are needed for playback. Exporting is handy if you want to take your files away into another program and have a list of which wav is which, or if you keep your audio files, but want to come back at a later date, edit one or two lines, regenerate the speech and re-combine the wavโs into one new long wav.
-
- #### โฌ Exporting Your Audio
- > **Export to WAV:** Combine all generated TTS from the list, into one single WAV file for easy download and distribution. Its always recommended to export your list to a JSON before exporting, so that you have a backup, should something go wrong. You can simply re-import the list and try exporting again.
-
- When exporting, there is a file size limit of 1GB and as such you have the option to choose how many files to include in each block of audio exported. 600 is just on the limit of 1GB, depending on the average file size, so 500 or less is a good amount to work with. You can combine the generated files after if you wish, in Audacity or similar.
-
- Additionally, lower export batches will lower the memory requirements, so if your system is low on memory (maybe 8 or 16GB system), you can use smaller export batches to keep the memory requirement down.
-
- #### โฌ Exporting Subtitles (SRT file)
- > **Export SRT:** This will scan through all wav files in your list and generate a subtitles file that will match your exported wav file.
-
- #### โฌ Analyzing generated TTS for errors
- > **Analyze TTS:** This will scan through all wav files comparing each ID's orignal text with the TTS generated for that ID and then flag up inconsistences. Its important to understand this is a **best effort** process and **not 100% perfect**, for example:
-
- > Your text may have the word `their` and the automated routine that listens to your generated TTS interprets the word as `there`, aka a spelling difference.
- > Your text may have `Examples are:` (note the colon) and the automated routine that listens to your generated TTS interprets the word as `Examples are` (note NO colon as you cannot sound out a colon in TTS), aka a punctuation difference.
- > Your text may have `There are 100 items` and the automated routine that listens to your generated TTS interprets the word as `There are one hundred items`, aka numbers vs the number written out in words.
-
- There will be other examples such as double quotes. As I say, please remember this is a **best effort** to help you identify issues.
-
- As such, there is a `% Accuracy` setting. This uses a couple of methods to try find things that are similar e.g. taking the `their` and `there` example from above, it would identify that they both sound the same, so even if the text says `their` and the AI listening to the generated TTS interprets the word as `there`, it will realise that both sound the same/are similar so there is no need to flag that as an error. However, there are limits to this and some things may slip through or get picked up when you would prefer them not to be flagged.
-
- The higher the accuracy you choose, the more things it will flag up, however you may get more unwanted detections. The lower the less detections. Based on my few tests, accuracy settings between 96 to 98 seem to generally give the best results. However, I would highly recommend you test out a small 10-20 line text and test out the **Analyze TTS** button to get a feel for how it responds to different settings, as well as things it flags up.
-
- You will be able to see the ID's and Text (orignal and as interpreted) by looking at the terminal/command prompt window.
-
- The Analyze TTS feature uses the Whisper Larger-v2 AI engine, which will download on first use if necessary. This will require about 2.5GB's of disk space and could take a few minutes to download, depending on your internet connection.
-
- You can use this feature on systems that do not have an Nvidia GPU, however, unless you have a very powerful CPU, expect it to be slow.
-
- #### โฌ Tricks to get the model to say things correctly
- Sometimes the AI model wonโt say something the way that you want it to. It could be because itโs a new word, an acronym or just something itโs not good at for whatever reason. There are some tricks you can use to improve the chances of it saying something correctly.
-
- **Adding pauses**
- You can use semi-colons ";" and colons ":" to create a pause, similar to a period "." which can be helpful with some splitting issues.
-
- **Acronyms**
- Not all acronyms are going to be pronounced correctly. Letโs work with the word `ChatGPT`. We know it is pronounced `"Chat G P T"` but when presented to the model, it doesnโt know how to break it down correctly. So, there are a few ways we could get it to break out "Chat" and the G P and T. e.g.
-
- > `Chat G P T.`
- > `Chat G,P,T.`
- > `Chat G.P.T.`
- > `Chat G-P-T.`
- > `Chat gee pee tea`
-
- All bar the last one are using ways within the English language to split out "Chat" into one word being pronounced and then split the G, P and T into individual letters. The final example, which is to use Phonetics will sound perfectly fine, but clearly would look wrong as far as human readable text goes. The phonetics method is very useful in edge cases where pronunciation difficult.
-
- #### โฌ Notes on Usage
- > For seamless TTS generation, it's advised to keep text chunks under 250 characters, which you can control with the Chunk sizes.
- > Generated audio can be played back from the list, which also highlights the currently playing chunk.
- > The TTS Generator remembers your settings, so you can pick up where you left off even after refreshing the page.
- """
-
- with gr.Tab("Documentation and Help"):
- with gr.Tab("Narrator Function"): gr.Markdown(narrator_guides)
- with gr.Tab("RVC"):
- gr.Markdown(rvc_guides)
- with gr.Row():
- with gr.Column(): gr.Markdown(rvc_guides2)
- with gr.Column(): gr.Markdown(rvc_guides3)
- with gr.Tab("Low VRAM"): gr.Markdown(low_vram)
- with gr.Tab("SillyTavern"): gr.Markdown(silly_tavern_support)
- with gr.Tab("Updating"): gr.Markdown(updating_alltalk)
- with gr.Tab("DeepSpeed"): gr.Markdown(deepspeed_guides)
- with gr.Tab("Help with Problems"): gr.Markdown(help_with_problems)
- with gr.Tab("XTTS Finetuning"):
- with gr.Tab("XTTS Finetuning basics"): gr.Markdown(finetuning_a_model)
- with gr.Tab("XTTS Finetuning Setup"): gr.Markdown(finetuning_setup)
- with gr.Tab("XTTS Finetuning Detailed"): gr.Markdown(finetuning_details)
+import gradio as gr
+
+def alltalk_documentation():
+ low_vram = """
+ The Low VRAM option is a crucial feature designed to enhance performance under constrained (VRAM) conditions, as TTS models **can** require 2GB-3GB of VRAM to run effectively. This feature strategically manages the relocation of the Text-to-Speech (TTS) model between your system's Random Access Memory (RAM) and VRAM, moving it between the two on the fly. Obviously, this is very useful for people who have smaller graphics cards or people who's LLM has filled their VRAM, but you want to keep up performance.
+
+ When you don't have enough VRAM free after loading your LLM model into your VRAM (Normal Mode example below), you can see that with so little working space, your GPU will have to swap in and out bits of the TTS model, which causes horrible slowdown.
+
+ Note An Nvidia Graphics card is required for the LowVRAM option to work, as you will just be using system RAM otherwise.
+
+ ### How It Works
+ The Low VRAM mode intelligently relocates of the entire TTS model and stores the TTS model in your system RAM when not being used. When the TTS engine requires VRAM for processing, the entire model seamlessly moves into VRAM, causing your LLM to unload/displace some layers, ensuring optimal performance of the TTS engine.
+
+ Post-TTS processing, the model moves back to system RAM, freeing up VRAM space for your Language Model (LLM) to load back in the missing layers. This adds about 1-2 seconds to both text generation by the LLM and the TTS engine.
+
+ By transferring the entire model between RAM and VRAM, the Low VRAM option avoids model fragmentation, ensuring the TTS model remains cohesive and has all the working space it needs in your GPU, without having to just work on small bits of the TTS model at a time (which causes terrible slow down).
+
+ This creates a TTS generation performance Boost for Low VRAM Users and is particularly beneficial for users with less than 2GB of free VRAM after loading their LLM, delivering a substantial 5-10x improvement in TTS generation speed.
+ """
+
+ silly_tavern_support = """
+ #### SillyTavern/AllTalk extension version
+ If you intend to use AllTalk V2 with SillyTavern, you will need to update the SillyTavern Extension from the `\alltalk_tts\system\SillyTavern Extension\` folder. Instructions are included within the folder.
+
+ #### Important note for Text-generation-webui users
+ You **HAVE** to disable **Enable TTS** within the Text-generation-webui AllTalk interface, otherwise Text-generation-webui will also generate TTS due to the way it sends out text. You can do this each time you start up Text-generation-webui or set it in the start-up settings at the top of this page.
+
+ ### ๐ฐ Quick Tips
+ > Only change DeepSpeed, Low VRAM or Model one at a time. Wait for it to say Ready before changing something else.
+ > You can permanently change the AllTalk startup settings or DeepSpeed, Low VRAM and Model at the top of this page.
+ > Different AI models use quotes and asterisks differently, so you may need to change "Text not inside" depending on model.
+ > Add new voice samples to the voices folder. You can Finetune a model to make it sound even closer to the original sample.
+ > DeepSpeed will improve processing time to TTS to be 2-3x faster.
+ > Low VRAM can be very beneficial if you don't have much memory left after loading your LLM.
+
+ #### ๐ฐ TTS Generation Methods in SilllyTavern
+ You have 2 types of audio generation options, Streaming and Standard.
+
+ The Streaming Audio Generation method is designed for speed and is best suited for situations where you just want quick audio playback. This method, however, is limited to using just one voice per TTS generation request. This means a limitation of the Streaming method is the inability to utilize the AllTalk narrator function, making it a straightforward but less nuanced option.
+
+ On the other hand, the Standard Audio Generation method provides a richer auditory experience. It's slightly slower than the Streaming method but compensates for this with its ability to split text into multiple voices. This functionality is particularly useful in scenarios where differentiating between character dialogues and narration can enhance the storytelling and delivery. The inclusion of the AllTalk narrator functionality in the Standard method allows for a more layered and immersive experience, making it ideal for content where depth and variety in voice narration add significant value.
+
+ In summary, the choice between Streaming and Standard methods in AllTalk TTS depends on what you want. Streaming is great for quick and simple audio generation, while Standard is preferable for a more dynamic and engaging audio experience.
+
+ **AllTalk TTS Generation Method:**
+ > Select between Standard and Streaming Audio Generation methods.
+ > This setting impacts the AllTalk narrator functionality.
+
+ **Language Selection:**
+ > Select your preferred TTS generation language from the "Language" dropdown.
+
+ **Model Switching:**
+ > Switch between different TTS models like API TTS, API Local, XTTSv2 Local, and optionally XTTSv2 FT if you have a finetuned model available.
+ > Fine-tuned model availability (XTTSv2 FT) will only show when a finetuned model is detected by AllTalk.
+ > See TTS Models/Methods for more information (though most people will want to stick with XTTSv2 Local).
+
+ **DeepSpeed and Low VRAM Options:**
+ > Optimize performance with DeepSpeed and Low VRAM settings.
+ > DeepSpeed can offer a 2-3x performance boost on TTS generation. (Requires installation)
+ > See the relevant sections in this documentation for details.
+
+ Changing model or DeepSpeed or Low VRAM **each** take about 15 seconds so you should only change one at a time and wait for `Ready` before changing the next setting. To set these options long term you can apply the settings at the top of this page.
+
+ #### ๐ฐ AllTalk Narrator
+ Only available on the Standard Audo Generation method.
+
+ **Narrator Voice Selection:**
+ > Allows users to choose different narrator voices.
+ > Access via the "Narrator Voice" dropdown.
+
+ **AllTalk Narrator:**
+ > Toggle the AllTalk narrator feature.
+ > Access via "AT Narrator" dropdown with Enabled/Disabled options.
+
+ **Text Outside Asterisks Handling:**
+ > Choose how text outside asterisks is interpreted (as Narrator or Character voice).
+ > Managed via the "Text Not Inside * or "" dropdown.
+ > Note, only available when the AllTalk Narrator is enabled.
+
+ #### ๐ฐ Usage Notes:
+ > On startup of SillyTavern, it will pull your current settings from AllTalk (Current model, DeepSpeed status, Low VRAM status and Finetuned model availability).
+ > Enabling the narrator automatically unchecks certain checkboxes related to text handling.
+ > Changes in model or settings might trigger multiple requests to the server; patience is advised.
+
+ #### ๐ฐ Troubleshooting:
+ > If experiencing issues, use the Reload button in SillyTavern's TTS extension to reinitialize the connection to AllTalk and check if AllTalk is started correctly.
+ """
+
+ updating_alltalk = """
+ ### ๐ช Updating
+
+ Maintaining the latest version of your setup ensures access to new features and improvements. Below are the steps to update your installation, whether you're using Text-Generation-webui or running as a Standalone Application.
+
+
+ UPDATING - Text-Generation-webui
+
+ The update process closely mirrors the installation steps. Follow these to ensure your setup remains current:
+
+ **Open a Command Prompt/Terminal**
+ > Navigate to your Text-Generation-webui folder with:
+ > `cd text-generation-webui`
+
+ **Start the Python Environment**
+ > Activate the Python environment tailored for your operating system. Use the appropriate command from below based on your OS:
+ > **Windows**: `cmd_windows.bat`
+ > **Linux**: `./cmd_linux.sh`
+ > **macOS**: `cmd_macos.sh`
+ > **WSL (Windows Subsystem for Linux)**: `cmd_wsl.bat`
+
+ > If you're unfamiliar with Python environments and wish to learn more, consider reviewing **Understanding Python Environments Simplified** in the Help section.
+
+ **Navigate to the AllTalk TTS Folder**
+ > Move into your extensions and then the alltalk_tts directory:
+ > `cd extensions/alltalk_tts`
+
+ **Update the Repository**
+ > Fetch the latest updates from the repository with:
+ > `git pull`
+
+ **Install Updated Requirements**
+ > Depending on your machine's OS, install the required dependencies using pip:
+ > **Windows Machines**:
+ > `pip install -r system\\requirements\\requirements_textgen.txt`
+ > **Linux/Mac**:
+ > `pip install -r system/requirements/requirements_textgen.txt`
+
+ **DeepSpeed Requirements**
+ > If Text-gen-webui is using a new version of PyTorch, you **may** need to uninstall and update your DeepSpeed version.
+ > Use AllTalks diagnostics or start-up menu to identify your version of PyTorch.
+
+
+
+
+ UPDATING - Standalone Application
+
+
+ If you installed from a ZIP file, you cannot use a `git pull` to update, as noted in the Quick Setup instructions.
+
+ For Standalone Application users, here's how to update your setup:
+
+ 1. **Open a Command Prompt/Terminal**:
+ > Navigate to your AllTalk folder with
+ > `cd alltalk_tts`
+
+ 2. **Access the Python Environment**:
+ > In a command prompt or terminal window, navigate to your `alltalk_tts` directory and start the Python environment:
+ > **Windows**
+ > `start_environment.bat`
+ > **Linux/macOS**
+ > `./start_environment.sh`
+
+ > If you're unfamiliar with Python environments and wish to learn more, consider reviewing **Understanding Python Environments Simplified** in the Help section.
+
+ 2. **Pull the Latest Updates**:
+ > Retrieve the latest changes from the repository with:
+ > `git pull`
+
+ 3. **Install Updated Requirements**:
+ > Depending on your machine's OS, install the required dependencies using pip:
+ > **Windows Machines**
+ > `pip install -r system\\requirements\\requirements_standalone.txt`
+ > **Linux/Mac**
+ > `pip install -r system/requirements/requirements_standalone.txt`
+
+
+
+ ### ๐ช Resolving Update Issues
+
+ If you encounter problems during or after an update, following these steps can help resolve the issue by refreshing your installation while preserving your data:
+
+
+ RESOLVING - Updates
+
+ The process involves renaming your existing `alltalk_tts` directory, setting up a fresh instance, and then migrating your data:
+
+ 1. **Rename Existing Directory**:
+ > First, rename your current `alltalk_tts` folder to keep it safe e.g. `alltalk_tts.old`. This preserves any existing data.
+
+ 2. **Follow the Quick Setup instructions**:
+ > You will now follow the **Quick Setup** instructions, performing the `git clone https://github.com/erew123/alltalk_tts` to pull down a new copy of AllTalk and install the requirements.
+
+ > If you're not familiar with Python environments, see **Understanding Python Environments Simplified** in the Help section for more info.
+
+ 3. **Migrate Your Data**:
+ > **Before** starting the AllTalk, transfer the `models`, `voices`, `outputs` folders and also `confignew.json` from `alltalk_tts.old` to the new `alltalk_tts` directory. This action preserves your voice history and prevents the need to re-download the model.
+
+ 4) **Launch AllTalk**
+ > You're now ready to launch AllTalk and check it works correctly.
+
+ 6. **Final Step**:
+ > Once you've verified that everything is working as expected and you're satisfied with the setup, feel free to delete the `alltalk_tts.old` directory to free up space.
+
+
+ """
+
+ narrator_guides = """
+ Messages intended for the Narrator should be enclosed in asterisks `*` and those for the character inside quotation marks `"`. However, AI systems often deviate from these rules, resulting in text that is neither in quotes nor asterisks. Sometimes, text may appear with only a single asterisk, and AI models may vary their formatting mid-conversation. For example, they might use asterisks initially and then switch to unmarked text. A properly formatted line should look like this:
+
+ `"`Hey! I'm so excited to finally meet you. I've heard so many great things about you and I'm eager to pick your brain about computers.`"` `*`She walked across the room and picked up her cup of coffee`*`
+
+ Most narrator/character systems switch voices upon encountering an asterisk or quotation marks, which is somewhat effective. AllTalk has undergone several revisions in its sentence splitting and identification methods. While some irregularities and AI deviations in message formatting are inevitable, any line beginning or ending with an asterisk should now be recognized as Narrator dialogue. Lines enclosed in double quotes are identified as Character dialogue. For any other text, you can choose how AllTalk handles it: whether it should be interpreted as Character or Narrator dialogue (most AI systems tend to lean more towards one format when generating text not enclosed in quotes or asterisks).
+
+ ### ๐ฆ Narrator Types of Text
+
+ The TTS engine identifies and processes three types of text when the Narrator function is `Enabled` or `Enabled (Silent)`:
+
+ > **Narrated Text**: Text enclosed in asterisks `*Narrated text*`.
+ > **Character Text**: Text enclosed in double quotes `"Character text"`.
+ > **Text-Not-Inside**: Any text not enclosed in asterisks or double quotes. The handling of this text is customizable.
+
+ ### ๐ฆ Settings for Narrated Text
+
+ You can control how narrated text is handled with the following settings:
+
+ > **Enabled**: Narrated text will be spoken by the TTS engine using the narrator voice.
+ > **Enabled (Silent)**: Narrated text is recognized but not spoken. This is useful for adding silent annotations or actions within the text.
+ > **Disabled**: Narrated text will be spoken using the character voice instead of the narrator voice. The narrator engine is turned off, and the Text-Not-Inside setting does not apply.
+
+ Please note, if you set `Enabled` or `Enabled (silent)` as the APi defaults, then all text will go into the narrator function unless `disabled` is sent as part of the TTS generation request.
+
+ ### ๐ฆ Settings for Text-Not-Inside
+
+ You can customize how text-not-inside is handled with these options:
+
+ > **"Character"**: Text-not-inside is treated as character dialogue and will be spoken.
+ > ***Narrator***: Text-not-inside is treated as narration and will be spoken.
+ > **Silent**: Text-not-inside is recognized but not spoken.
+
+ ### ๐ฆ Examples and Variations
+
+ Here are some examples to illustrate how different settings affect the text processing:
+
+ > **Example 1**:
+ **Narrator Enabled, Text-Not-Inside as Character**
+ > ***Narrated text*** will be spoken as narration.
+ > **"Character text"** will be spoken as character dialogue.
+ > **Any other** text will be spoken as character dialogue.
+
+ > **Example 2**:
+ **Narrator Disabled**
+ > ***Narrated text*** will be spoken using the character voice.
+ > **"Character text"** will be spoken as character dialogue.
+ > **Any other** text will be spoken as character dialogue.
+
+ > **Example 3**:
+ **Narrator Enabled (Silent), Text-Not-Inside as Silent**
+ > ***Narrated text*** will be recognized but not spoken.
+ > **"Character text"** will be spoken as character dialogue.
+ > **Any other** text will be recognized but not spoken.
+
+ ### ๐ฆ Potential Confusions and Clarifications
+
+ **Silent Options Impact**
+
+ > **Narrator Enabled (Silent)**: This setting allows for silent annotations within your text. Users can add notes or actions in the narrative without them being spoken.
+ > **Text-Not-Inside as Silent**: This setting enables users to include text that is recognized by the TTS engine but remains unspoken, useful for adding context or instructions within the script that are not meant to be vocalized.
+
+ **Combining Settings**
+
+ > **Full Silence**: If both Narrator is set to `Enabled (Silent)` and Text-Not-Inside is set to `Silent`, **only character text** in quotes will be spoken.
+ > **Partial Silence**: You can configure the settings to have only certain parts of the text spoken while others are silent. For example, you might want all narration silent and only character dialogue spoken, or vice versa.
+
+ ### ๐ฆ Short Story Example
+
+ >*Once upon a time in a small village, there was a young girl named Lily.*
+ Lily woke up early every morning.
+ "I love exploring the forest near my home."
+ * Lily said. One day, she found a mysterious key lying on the ground.*
+ "This key looks old and rusty, but it seems to sparkle in the sunlight,"
+ She thought. Lily decided she would keep the key safe until she could discover its purpose.
+ *Curious and excited, she decided to find out what the key opened.*
+
+ ### Short Story Explanation
+
+ **Narrated Text** (Asterisks surrounding the text):
+ > *Once upon a time in a small village, there was a young girl named Lily.*
+ > * Lily said. One day, she found a mysterious key lying on the ground.*
+ > *Curious and excited, she decided to find out what the key opened.*
+ > These lines are enclosed in asterisks and represent the narration. If Narrator was set to `Enabled (Silent)` these lines would not be spoken.
+
+ **Character Text** (Double Quotes surrounding the text):
+ > "I love exploring the forest near my home."
+ > "This key looks old and rusty, but it seems to sparkle in the sunlight."
+ > These lines are enclosed in double quotes and represent the characterโs spoken dialogue or thoughts.
+
+ **Text-Not-Inside** (Neither Asterisks nor Quotes):
+ > Lily woke up early every morning.
+ > She thought. Lily decided she would keep the key safe until she could discover its purpose.
+ > These lines are not enclosed in asterisks or quotes and will be handled according to the `Text-Not-Inside` settings. If set to `Text-not-inside` > `Silent` these lines would not be spoken.
+
+ """
+
+ rvc_guides = """
+ RVC (Retrieval-based Voice Conversion) enhances TTS by replicating voice characteristics for characters or narrators, adding depth to synthesized speech. It functions as a TTS-to-TTS pipeline and can be used with any TTS engine/model. However, the closer the original TTS generation is to the voice you want to use with RVC, the better the result will be. Therefore, it is recommended to use a voice cloning TTS engine like Coqui XTTS with voice samples for optimal performance.
+
+ When you first enable RVC on the `Global Settings > RVC Settings` tab and click the `Update RVC Settings` button, AllTalk will create the necessary folders and download any of the missing model files required for RVC to work.
+
+ ### ๐ฉ Voice Model Files
+
+ Voice models should be stored in the `/models/rvc_voices/{subfolder}` directory in their own subfolder. Typically, a voice model includes a PTH file and potentially an index file. If an index file is present, AllTalk will automatically select and use it. If multiple index files are found, none will be used, and a message will be output to the console. You can find places on the internet to download pre-generated RVC voice models e.g. https://voice-models.com/ and huggingface etc.
+
+ ### ๐ฉ Purpose of the Index File
+
+ The index file helps improve the quality of the generated audio by providing a reference during the conversion process. The FAISS index enables faster and more accurate retrieval of voice characteristics, leading to more natural and high-quality voice synthesis. The training data size setting determines how much of the index is used, impacting both quality and computation time. By indexing a larger number of data points, the conversion process can produce audio that closely matches the desired voice characteristics.
+
+ ### ๐ฉ RVC Settings Page
+ """
+ rvc_guides2 = """
+ ### Default Character Voice Model
+ > This setting allows you to select the voice model used for character conversion. If "Disabled" is selected, RVC will not be applied to character voices. This option is used only if RVC is enabled and no other voice is specified in the API request. Selecting a specific model will apply RVC to all character dialogues, potentially making them sound more natural and lifelike.
+
+ ### Default Narrator Voice Model
+ > This setting allows you to select the voice model used for narrator conversion. If "Disabled" is selected, RVC will not be applied to the narrator voice. This option is used only if RVC is enabled and no other voice is specified in the API request. Applying RVC to the narrator can enhance the overall narrative experience by providing a consistent and engaging voice.
+
+ ### Index Influence Ratio
+ > Sets the influence exerted by the index file on the final output. A higher value increases the impact of the index, potentially enhancing detail but also increasing the risk of artifacts. Fine-tuning this setting helps achieve the desired balance between audio detail and artifact prevention.
+
+ ### Pitch
+ > This slider sets the pitch of the audio output. Increasing the value raises the pitch, while decreasing the value lowers it. Adjusting the pitch can help match the voice to specific character traits or emotional states, enhancing the overall storytelling experience.
+
+ ### Volume Envelope
+ > This setting substitutes or blends with the volume envelope of the output. A ratio closer to 1 means the output envelope is more heavily employed. This can help maintain the natural dynamics of the voice, making it sound more realistic and less robotic.
+
+ ### Protect Voiceless Consonants/Breath Sounds
+ > Prevents artifacts in voiceless consonants and breath sounds. Higher values (up to 0.5) provide stronger protection but might affect indexing. This setting helps maintain the clarity and intelligibility of speech, especially in parts of the audio where voiceless consonants and breath sounds are present.
+
+ ### AutoTune
+ > Enables or disables auto-tune for the generated audio. Recommended for singing conversions to ensure the output remains in tune. Auto-tune can correct pitch discrepancies and produce a more polished and harmonious sound.
+ """
+ rvc_guides3 = """
+ ### Filter Radius
+ > If the number is greater than or equal to three, employing median filtering on the collected tone results has the potential to decrease respiration. This setting helps reduce noise and unwanted sounds in the audio, leading to cleaner and more professional-sounding output.
+
+ ### Training Data Size (AllTalk Specific)
+ > Determines the number of training data points used to train the FAISS index. Increasing the size may improve the quality of the output but can also increase computation time. Different index files have different sizes. This setting limits the maximum amount of the index used. For example, if an index file has 70,000 points and you set the limit to 50,000, only 50,000 points will be used. A higher training data size generally results in better audio quality because more data points contribute to a more accurate voice conversion.
+
+ ### Embedder Model
+ > Select between different models for learning speaker embedding. "hubert" and "contentvec" are the available options.
+ **hubert**: A model that focuses on capturing the phonetic and linguistic content of the voice, which is useful for a wide range of voice conversion tasks.
+ **contentvec**: A model that captures more detailed voice characteristics and nuances, potentially offering higher fidelity in voice conversion but may require more computational resources.
+
+ ### Split Audio
+ > Splits the audio into chunks for inference to obtain better results in some cases. This can improve the quality of conversion, especially for longer audio inputs, by ensuring more consistent and accurate processing across the entire length of the audio.
+
+ ### Pitch Extraction Algorithm
+ > Choose the algorithm used for extracting the pitch (F0) during audio conversion. The available options include:
+ **crepe**: Provides high accuracy in pitch detection and is robust against various types of noise.
+ **crepe-tiny**: A smaller, faster version of crepe with slightly reduced accuracy.
+ **dio**: A fast pitch extraction algorithm that is less accurate than crepe but suitable for real-time applications.
+ **fcpe**: Focuses on precise pitch extraction, often used for high-fidelity voice conversion.
+ **harvest**: Known for producing smooth and natural pitch contours, suitable for music and singing voice conversion.
+ **hybrid[rmvpe+fcpe]**: Combines the strengths of rmvpe and fcpe to achieve a balance of accuracy and performance.
+ **pm**: A robust algorithm that works well in various conditions, offering a balance of speed and accuracy.
+ **rmvpe**: Recommended for most cases due to its balance of accuracy and performance, especially in TTS applications.
+ """
+
+
+
+ deepspeed_guides = """
+ ### ๐ต๐ข DeepSpeed Installation Options
+ **DeepSpeed requires an Nvidia Graphics card**
+
+ ---
+ ## Documentation yet to be updated. In a Nutshell though....
+ > AllTalk **Standalone** setups **(Windows & Linux)** will install DeepSpeed **automatically**. You can stop reading here and you dont have to do anything.
+
+ > **Windows** users, please see instructions in `atsetup.bat`
+
+ > **Linux users**, please look here https://github.com/erew123/alltalk_tts/releases/tag/DeepSpeed-14.2-Linux
+ ---
+ ### Legacy manual build of DeepSpeed Instructions
+ #### ๐ต Linux Installation
+ DeepSpeed requires access to the **Nvidia CUDA Development Toolkit** to compile on a Linux system. It's important to note that this toolkit is distinct and unrealted to your graphics card driver or the CUDA version the Python environment uses.
+
+
+ Linux DeepSpeed - Text-generation-webui
+
+ ### DeepSpeed Installation for Text generation webUI
+
+ 1. **Nvidia CUDA Development Toolkit Installation**:
+ - The toolkit is crucial for DeepSpeed to compile/build for your version of Linux and requires around 3GB's of disk space.
+ - Install using your package manager **(Recommended)** e.g. **CUDA Toolkit 11.8** or download directly from [Nvidia CUDA Toolkit Archive](https://developer.nvidia.com/cuda-toolkit-archive) (choose 11.8 or 12.1 for Linux).
+
+ 2. **Open a Terminal Console**:
+ - After Nvidia CUDA Development Toolkit installation, access your terminal console.
+
+ 3. **Install libaio-dev**:
+ - Use your Linux distribution's package manager.
+
+ - - `sudo apt install libaio-dev` for Debian-based systems
+ - `sudo yum install libaio-devel` for RPM-based systems.
+
+ 4. **Navigate to Text generation webUI Folder**:
+ - Change directory to your Text generation webUI folder with `cd text-generation-webui`.
+
+ 5. **Activate Text generation webUI Custom Conda Environment**:
+ - Run `./cmd_linux.sh` to start the environment.
+
+ > If you're unfamiliar with Python environments and wish to learn more, consider reviewing **Understanding Python Environments Simplified** in the Help section.
+
+ 6. **Set `CUDA_HOME` Environment Variable**:
+ - DeepSpeed locates the Nvidia toolkit using the `CUDA_HOME` environment variable.
+ - You will only set this temporarily as Text generation webUI sets up its own CUDA_HOME environment each time you use `./cmd_linux.sh` or `./start_linux.sh`
+
+ 7. **Temporarily Configuring `CUDA_HOME`**:
+ - When the Text generation webUI Python environment is active **(step 5)**, set `CUDA_HOME`.
+
+ - - `export CUDA_HOME=/usr/local/cuda`
+ - `export PATH=${CUDA_HOME}/bin:${PATH}`
+ - `export LD_LIBRARY_PATH=${CUDA_HOME}/lib64:$LD_LIBRARY_PATH`
+
+ - You can confirm the path is set correctly and working by running the command `nvcc --version` should confirm `Cuda compilation tools, release 11.8.`.
+ - Incorrect path settings may lead to errors. If you encounter path issues or receive errors like `[Errno 2] No such file or directory` when you run the next step, confirm the path correctness or adjust as necessary.
+
+ 8. **DeepSpeed Installation**:
+ - Install DeepSpeed using `pip install deepspeed`.
+
+ 9. **Troubleshooting**:
+ - Troubleshooting steps for DeepSpeed installation can be located down below.
+ - **NOTE**: You **DO NOT** need to set Text-generation-webUI's **--deepspeed** setting for AllTalk to be able to use DeepSpeed. These are two completely separate things and incorrectly setting that on Text-generation-webUI may cause other complications.
+
+
+ Linux DeepSpeed - Standalone Installation
+
+ ### DeepSpeed Installation for Standalone AllTalk
+
+ 1. **Nvidia CUDA Development Toolkit Installation**:
+ - The toolkit is crucial for DeepSpeed to compile/build for your version of Linux and requires around 3GB's of disk space.
+ - Install using your package manager **(Recommended)** e.g. **CUDA Toolkit 11.8** or download directly from [Nvidia CUDA Toolkit Archive](https://developer.nvidia.com/cuda-toolkit-archive) (choose 11.8 or 12.1 for Linux).
+
+ 2. **Open a Terminal Console**:
+ - After Nvidia CUDA Development Toolkit installation, access your terminal console.
+
+ 3. **Install libaio-dev**:
+ - Use your Linux distribution's package manager.
+
+ - - `sudo apt install libaio-dev` for Debian-based systems
+ - `sudo yum install libaio-devel` for RPM-based systems.
+
+ 4. **Navigate to AllTalk TTS Folder**:
+ - Change directory to your AllTalk TTS folder with `cd alltalk_tts`.
+
+ 5. **Activate AllTalk Custom Conda Environment**:
+ - Run `./start_environment.sh` to start the AllTalk Python environment.
+ - This command will start the custom Python environment that was installed with `./atsetup.sh`.
+
+ > If you're unfamiliar with Python environments and wish to learn more, consider reviewing **Understanding Python Environments Simplified** in the Help section.
+
+ 6. **Set `CUDA_HOME` Environment Variable**:
+ - The DeepSpeed installation routine locates the Nvidia toolkit using the `CUDA_HOME` environment variable. This can be set temporarily for a session or permanently, depending on other requirements you may have for other Python/System environments.
+ - For temporary use, proceed to **step 8**. For a permanent solution, see [Conda's manual on setting environment variables](https://conda.io/projects/conda/en/latest/user-guide/tasks/manage-environments.html#set-env-vars).
+
+ 7. **(Optional) Permanent `CUDA_HOME` Setup**:
+ - If you choose to set `CUDA_HOME` permanently, follow the instructions in the provided Conda manual link above.
+
+ 8. **Configuring `CUDA_HOME`**:
+ - When your Python environment is active **(step 5)**, set `CUDA_HOME`.
+
+ - - `export CUDA_HOME=/usr/local/cuda`
+ - `export PATH=${CUDA_HOME}/bin:${PATH}`
+ - `export LD_LIBRARY_PATH=${CUDA_HOME}/lib64:$LD_LIBRARY_PATH`
+
+ - You can confirm the path is set correctly and working by running the command `nvcc --version` should confirm `Cuda compilation tools, release 11.8.`.
+ - Incorrect path settings may lead to errors. If you encounter path issues or receive errors like `[Errno 2] No such file or directory` when you run the next step, confirm the path correctness or adjust as necessary.
+
+ 9. **DeepSpeed Installation**:
+ - Install DeepSpeed using `pip install deepspeed`.
+
+ 10. **Starting AllTalk TTS WebUI**:
+ - Launch the AllTalk TTS interface with `./start_alltalk.sh` and enable DeepSpeed.
+
+ ### Troubleshooting
+
+ - If setting `CUDA_HOME` results in path duplication errors (e.g., `.../bin/bin/nvcc`), you can correct this by unsetting `CUDA_HOME` with `unset CUDA_HOME` and then adding the correct path to your system's PATH variable.
+ - Always verify paths and compatibility with other CUDA-dependent applications to avoid conflicts.
+ - If you have multiple versions of the Nvidia CUDA Development Toolkit installed, you will have to specify the version number in step 8 for the CUDA_HOME path.
+ - If it became necessary to uninstall DeepSpeed, you can do so by start the Python enviroment and then running `pip uninstall deepspeed`
+
+
+
+ #### ๐ข Windows Installation
+ You have 2x options for how to setup DeepSpeed on Windows. Pre-compiled wheel files for specific Python, CUDA and Pytorch builds, or manually compiling DeepSpeed.
+
+
+ Windows DeepSpeed - Pre-Compiled Wheels (Quick and Easy)
+
+ ### DeepSpeed Installation with Pre-compiled Wheels
+
+ 1. **Introduction to Pre-compiled Wheels**:
+ - The `atsetup.bat` utility simplifies the installation of DeepSpeed by automatically downloading and installing pre-compiled wheel files. These files are tailored for **specific** versions of Python, CUDA, and PyTorch, ensuring compatibility with both the **Standalone Installation** and a standard build of **Text-generation-webui**.
+
+ 2. **Manual Installation of Pre-compiled Wheels**:
+ - If needed, pre-compiled DeepSpeed wheel files that I have built are available on the [Releases Page](https://github.com/erew123/alltalk_tts/releases). You can manually install or uninstall these wheels using the following commands:
+ - Installation: `pip install {deep-speed-wheel-file-name-here}`
+ - Uninstallation: `pip uninstall deepspeed`
+
+ 3. **Using `atsetup.bat` for Simplified Management**:
+ - For those running the Standalone Installation or a standard build of Text-generation-webui, the `atsetup.bat` utility offers the simplest and most efficient way to manage DeepSpeed installations on Windows.
+
+
+
+
+ Windows DeepSpeed - Manual Compilation
+
+ ### Manual DeepSpeed Wheel Compilation
+
+ 1. **Preparation for Manual Compilation**:
+ - Manual compilation of DeepSpeed wheels is an advanced process that requires:
+ - **1-2 hours** of your time for initial setup and compilation.
+ - **6-10GB** of disk space on your computer.
+ - A solid technical understanding of Windows environments and Python.
+
+ 2. **Understanding Wheel Compatibility**:
+ - A compiled DeepSpeed wheel is uniquely tied to the specific versions of Python, PyTorch, and CUDA used during its compilation. If any of these versions are changed, you will need to compile a new DeepSpeed wheel to ensure compatibility.
+
+ 3. **Compiling DeepSpee Resources**:
+ - Myself and [@S95Sedan](https://github.com/S95Sedan) have worked to simplify the compilation process. [@S95Sedan](https://github.com/S95Sedan) has notably improved the process for later versions of DeepSpeed, ensuring ease of build on Windows.
+ - Because [@S95Sedan](https://github.com/S95Sedan) is now maintaining the instructions for compiling DeepSpeed on Windows, please visit [@S95Sedan](https://github.com/S95Sedan)'s [DeepSpeed GitHub page](https://github.com/S95Sedan/Deepspeed-Windows).
+
+
+ """
+
+ help_with_problems = """
+ ### ๐ Support Requests, Troubleshooting & Feature requests
+ I'm thrilled to see the enthusiasm and engagement with AllTalk! Your feedback and questions are invaluable, helping to make this project even better. To ensure everyone gets the help they need efficiently, please consider the following before submitting a support request:
+
+ **Consult the Documentation:** A comprehensive guide and FAQ sections (below) are available to help you navigate AllTalk. Many common questions and troubleshooting steps are covered here.
+
+ **Search Past Discussions:** Your issue or question might already have been addressed in the discussions area or [closed issues](https://github.com/erew123/alltalk_tts/issues?q=is%3Aissue+is%3Aclosed). Please use the search function to see if there's an existing solution or advice that applies to your situation.
+
+ **Bug Reports:** If you've encountered what you believe is a bug, please first check the [Updates & Bug Fixes List](https://github.com/erew123/alltalk_tts/issues/25) to see if it's a known issue or one that's already been resolved. If not, I encourage you to report it by raising a bug report in the [Issues section](https://github.com/erew123/alltalk_tts/issues), providing as much detail as possible to help identify and fix the issue.
+
+ **Feature Requests:** The current Feature request list can be [found here](https://github.com/erew123/alltalk_tts/discussions/74). I love hearing your ideas for new features! While I can't promise to implement every suggestion, I do consider all feedback carefully. Please share your thoughts in the [Discussions area](https://github.com/erew123/alltalk_tts/discussions) or via a Feature Request in the [Issues section](https://github.com/erew123/alltalk_tts/issues).
+
+
+ ### ๐จ Help with problems
+
+ #### ๐ **Minor updates/bug fixes list** can be found [here](https://github.com/erew123/alltalk_tts/issues/25)
+
+
+ ๐จ How to make a diagnostics report file
+
+ If you are on a Windows machine or a Linux machine, you should be able to use the `atsetup.bat` or `./atsetup.sh` utility to create a diagnositcs file. If you are unable to use the `atsetup` utility, please follow the instructions below.
+
+
+ Manually making a diagnostics report file
+
+ 1) Open a command prompt window and start the Python environment. Depending on your setup (Text-generation-webui or Standalone AllTalk), the steps to start the Python environment vary:
+
+ - **For Text-generation-webui Users**:
+ - Navigate to the Text-generation-webui directory:
+ - `cd text-generation-webui`
+ - Start the Python environment suitable for your OS:
+ - Windows: `cmd_windows.bat`
+ - Linux: `./cmd_linux.sh`
+ - macOS: `cmd_macos.sh`
+ - WSL (Windows Subsystem for Linux): `cmd_wsl.bat`
+ - Move into the AllTalk directory:
+ - `cd extensions/alltalk_tts`
+
+ - **For Standalone AllTalk Users**:
+ - Navigate to the `alltalk_tts` folder:
+ - `cd alltalk_tts`
+ - Start the Python environment:
+ - Windows: `start_environment.bat`
+ - Linux: `./start_environment.sh`
+
+ > If you're unfamiliar with Python environments and wish to learn more, consider reviewing **Understanding Python Environments Simplified** in the Help section.
+
+ 2) Run the diagnostics and select the requirements file name you installed AllTalk with:
+ - `python diagnostics.py`
+
+ 3) You will have an on screen output showing your environment setttings, file versions request vs whats installed and details of your graphics card (if Nvidia). This will also create a file called `diagnostics.log` in the `alltalk_tts` folder, that you can upload if you need to create a support ticket on here.
+
+ ### Installation and Setup Issues
+
+
+ ๐จ Understanding Python Environments Simplified
+
+ Think of Python environments like different rooms in your house, each designed for a specific purpose. Just as you wouldn't cook in the bathroom or sleep in the kitchen, different Python applications need their own "spaces" or environments because they have unique requirements. Sometimes, these requirements can clash with those of other applications (imagine trying to cook a meal in a bathroom!). To avoid this, you can create separate Python environments.
+
+ #### Why Separate Environments?
+ Separate environments, like separate rooms, keep everything organized and prevent conflicts. For instance, one Python application might need a specific version of a library or dependency, while another requires a different version. Just as you wouldn't store kitchen utensils in the bathroom, you wouldn't want these conflicting requirements to interfere with each other. Each environment is tailored and customized for its application, ensuring it has everything it needs without disrupting others.
+
+ #### How It Works in Practice:
+
+ **Standalone AllTalk Installation:** When you install AllTalk standalone, it's akin to adding a new room to your house specifically designed for your AllTalk activities. The setup process, using the atsetup utility, constructs this custom "room" (Python environment `alltalk_environment`) with all the necessary tools and furnishings (libraries and dependencies) that AllTalk needs to function smoothly, without meddling with the rest of your "house" (computer system). The AllTalk environment is started each time you run `start_alltalk` or `start_environment` within the AllTalk folder.
+
+ **Text-generation-webui Installation:** Similarly, installing Text-generation-webui is like setting up another specialized room. Upon installation, it automatically creates its own tailored environment, equipped with everything required for text generation, ensuring a seamless and conflict-free operation. The Text-generation-webui environment is started each time you run `start_*your-os-version*` or `cmd_*your-os-version*` within the Text-generation-webui folder.
+
+ #### Managing Environments:
+ Just as you might renovate a room or bring in new furniture, you can also update or modify Python environments as needed. Tools like Conda or venv make it easy to manage these environments, allowing you to create, duplicate, activate, or delete them much like how you might manage different rooms in your house for comfort and functionality.
+
+ Once you're in the right environment, by activating it, installing or updating dependencies (the tools and furniture of your Python application) is straightforward. Using pip, a package installer for Python, you can easily add what you need. For example, to install all required dependencies listed in a requirements.txt file, you'd use:
+
+ `pip install -r requirements.txt`
+
+ This command tells pip to read the list of required packages and versions from the requirements.txt file and install them in the current environment, ensuring your application has everything it needs to operate. It's like having a shopping list for outfitting a room and ensuring you have all the right items delivered and set up.
+
+ Remember, just as it's important to use the right tools for tasks in different rooms of your house, it's crucial to manage your Python environments and dependencies properly to ensure your applications run as intended.
+
+ #### How do I know if I am in a Python environment?:
+ When a Python environment starts up, it changes the command prompt to show the Python environment that it currently running within that terminal/console.
+
+ 
+
+
+
+ ๐จ Windows & Python requirements for compiling packages (ERROR: Could not build wheels for TTS)
+
+ `ERROR: Microsoft Visual C++ 14.0 or greater is required` or `ERROR: Could not build wheels for TTS.` or `ModuleNotFoundError: No module named 'TTS`
+
+ Python requires that you install C++ development tools on Windows. This is detailed on the [Python site here](https://wiki.python.org/moin/WindowsCompilers). You would need to install `MSVCv142 - VS 2019 C++ x64/x86 build tools` and `Windows 10/11 SDK` from the C++ Build tools section.
+
+ You can get hold of the **Community** edition [here](https://visualstudio.microsoft.com/downloads/) the during installation, selecting `C++ Build tools` and then `MSVCv142 - VS 2019 C++ x64/x86 build tools` and `Windows 10/11 SDK`.
+
+ 
+
+
+
+ ๐จ Standalone Install - start_{youros}.xx opens and closes instantly and AllTalk doesnt start
+
+ This is more than likely caused by having a space ` ` in your folder path e.g. `c:\program files\alltalk_tts`. In this circumstance you would be best moving the folder to a path without a space e.g. `c:\myfiles\alltalk_tts`. You would have to delete the `alltalk_environment` folder and `start_alltalk.bat` or `start_alltalk.sh` and then re-run `atsetup` to re-create the environment and startup files.
+
+
+
+ ๐จ Text-geneneration-webui & Stable-Diffusion Plugin - Load Order & stripped text
+
+ The Stable Diffusion plugin for Text-generation-webui **strips out** some of the text, which is passed to Stable Diffusion for image/scene generation. Because this text is stripped, its important to consider the load order of the plugins to get the desired result you want. Lets assume the AI has just generated the following message `*He walks into the room with a smile on his face and says* Hello how are you?`. Depending on the load order will change what text reaches AllTalk for generation e.g.
+
+ **SD Plugin loaded before AllTalk** - Only `Hi how are you?` is sent to AllTalk, with the `*He walks into the room with a smile on his face and says*` being sent over to SD for image generation. Narration of the scene is not possible.
+ **AllTalk loaded before SD Plugin** - `*He walks into the room with a smile on his face and says* Hello how are you?` is sent to AllTalk with the `*He walks into the room with a smile on his face and says*` being sent over to SD for image generation.
+ The load order can be changed within Text-generation-webui's `settings.yaml` file or `cmd_flags.txt` (depending on how you are managing your extensions).
+
+
+
+ ๐จ I think AllTalks requirements file has installed something another extension doesn't like
+
+ Ive paid very close attention to **not** impact what Text-generation-webui is requesting on a factory install. This is one of the requirements of submitting an extension to Text-generation-webui. If you want to look at a comparison of a factory fresh text-generation-webui installed packages (with cuda 12.1, though AllTalk's requirements were set on cuda 11.8) you can find that comparison [here](https://github.com/erew123/alltalk_tts/issues/23). This comparison shows that AllTalk is requesting the same package version numbers as Text-generation-webui or even lower version numbers (meaning AllTalk will not update them to a later version). What other extensions do, I cant really account for that.
+
+ I will note that the TTS engine downgrades Pandas data validator to 1.5.3 though its unlikely to cause any issues. You can upgrade it back to text-generation-webui default (december 2023) with `pip install pandas==2.1.4` when inside of the python environment. I have noticed no ill effects from it being a lower or higher version, as far as AllTalk goes. This is also the same behaviour as the Coqui_tts extension that comes with Text-generation-webui.
+
+ Other people are reporting issues with extensions not starting with errors about Pydantic e.g. ```pydantic.errors.PydanticImportError: BaseSettings` has been moved to the pydantic-settings package. See https://docs.pydantic.dev/2.5/migration/#basesettings-has-moved-to-pydantic-settings for more details.```
+
+ Im not sure if the Pydantic version has been recently updated by the Text-generation-webui installer, but this is nothing to do with AllTalk. The other extension you are having an issue with, need to be updated to work with Pydantic 2.5.x. AllTalk was updated in mid december to work with 2.5.x. I am not specifically condoning doing this, as it may have other knock on effects, but within the text-gen Python environment, you can use `pip install pydantic==2.5.0` or `pip install pydantic==1.10.13` to change the version of Pydantic installed.
+
+
+ ๐จ I am having problems getting AllTalk to start after changing settings or making a custom setup/model setup.
+
+ I would suggest following [Problems Updating](https://github.com/erew123/alltalk_tts#-problems-updating) and if you still have issues after that, you can raise an issue [here](https://github.com/erew123/alltalk_tts/issues)
+
+
+ ### Networking and Access Issues
+
+
+ ๐จ I cannot access AllTalk from another machine on my Network
+
+ You will need to change the IP address within AllTalk's settings from being 127.0.0.1, which only allows access from the local machine its installed on. To do this, please see [Changing AllTalks IP address & Accessing AllTalk over your Network](https://github.com/erew123/alltalk_tts/tree/main?tab=readme-ov-file#-changing-alltalks-ip-address--accessing-alltalk-over-your-network) at the top of this page.
+
+ You may also need to allow access through your firewall or Antivirus package to AllTalk.
+
+
+
+ ๐จ I am running a Headless system and need to change the IP Address manually as I cannot reach the config page
+
+ To do this you can edit the `confignew.json` file within the `alltalk_tts` folder. You would look for `"ip_address": "127.0.0.1",` and change the `127.0.0.1` to your chosen IP address,then save the file and start AllTalk.
+
+ When doing this, be careful not to impact the formatting of the JSON file. Worst case, you can re-download a fresh copy of `confignew.json` from this website and that will put you back to a factory setting.
+
+
+ ### Configuration and Usage Issues
+
+ ๐จ I activated DeepSpeed in the settings page, but I didnt install DeepSpeed yet and now I have issues starting up
+
+ You can either follow the [Problems Updating](https://github.com/erew123/alltalk_tts#-problems-updating) and fresh install your config. Or you can edit the `confignew.json` file within the `alltalk_tts` folder. You would look for '"deepspeed_activate": true,' and change the word true to false `"deepspeed_activate": false,' ,then save the file and try starting again.
+
+ If you want to use DeepSpeed, you need an Nvidia Graphics card and to install DeepSpeed on your system. Instructions are [here](https://github.com/erew123/alltalk_tts#-deepspeed-installation-options)
+
+
+
+ ๐จ I am having problems updating/some other issue where it wont start up/Im sure this is a bug
+
+ Please see [Problems Updating](https://github.com/erew123/alltalk_tts#-problems-updating). If that doesnt help you can raise an ticket [here](https://github.com/erew123/alltalk_tts/issues). It would be handy to have any log files from the console where your error is being shown. I can only losely support custom built Python environments and give general pointers. Please create a `diagnostics.log` report file to submit with a support request.
+
+ Also, is your text-generation-webui up to date? [instructions here](https://github.com/oobabooga/text-generation-webui?tab=readme-ov-file#how-to-install)
+
+
+
+ ๐จ Error Details: HTTPConnectionPool(host='null', port=80): Max retries exceeded with url: /api/tts-generate............ etc
+
+ If you get this error, the gradio window you have open has lost connection to the AllTalk backend. This is most likely caused by AllTalk being restarted in the background and you not opening the gradio window again. Simply refresh the gradio web page and it will be connected back in and working.
+
+
+
+
+ ๐จ I see some red "asyncio" messages
+
+ As far as I am aware, these are to do with the chrome browser the gradio text-generation-webui in some way. I raised an issue about this on the text-generation-webui [here](https://github.com/oobabooga/text-generation-webui/issues/4788) where you can see that AllTalk is not loaded and the messages persist. Either way, this is more a warning than an actual issue, so shouldnt affect any functionality of either AllTalk or text-generation-webui, they are more just an annoyance.
+
+
+ ### Startup, Performance and Compatibility Issues
+
+
+ ๐จ Understanding the AllTalk start-up screen
+
+ The AllTalk start-up screen provides various bits of information about the detected Python environment and errors.
+
+ 
+
+ **Config file check**
+ - Sometimes I need to add/remove something to your existing configuration file settings. Obviously, I donโt want to impact your existing settings, however any new features may need these settings to be created before AllTalk starts up. Ive added extra code that checks `alltalk_tts/system/config/at_configupdate.json` and `alltalk_tts/system/config/at_configdowngrade.json`, either adding or removing items to your configuration as necessary. If a change is made, you will be notified and a backup of the previous configuration file will be created in the `alltalk_tts` folder.
+
+ **AllTalk startup Mode**
+ - informational. This will state if AllTalk has detected it is running as part of Text-generation-webui or as a Standalone Application.
+
+ **WAV file deletion**
+ - If you have set deletion of old generated WAV files, this will state the time frame after which they are purged.
+
+ **DeepSpeed version**
+ - What version of DeepSpeed is installed/detected. This will **not** tell you if the version of DeepSpeed is compiled for your Python, PyTorch or CUDA version. Its important to remember that DeepSpeed has to be compiled for the exact version of Python, PyTorch and CUDA that you are using, so please ensure you have the correct DeepSpeed version installed if necessary.
+
+ **Model is available**
+ - AllTalk is checking if your model files exist. This is not a validity check of the actual model files, they can still be corrupted. If files are missing, AllTalk will attempt to download them from Huggingface, however, if Huggingface has an outage/issue or your internet connection has issues, its possible corrupted or incomplete files will be downloaded. Please read `RuntimeError: PytorchStreamReader failed reading zip archive: failed finding central directory` if you need to confirm your model files are ok.
+
+ **Current Python Version**
+ - Informational. Literally tells you the version of Python running in your Python environment.
+
+ **Current PyTorch Version**
+ - Informational. Tell tells you the version of **PyTorch** running in your Python environment, however if you have an Nvidia card, you should be running a CUDA based version of Pytorch. This is indicated with a `+cXXX` after the PyTorch version e.g. `2.2.2+cu121` would be PyTorch version 2.2.2 with CUDA 12.1 extensions. If you donโt have the PyTorch CUDA extensions installed, but you do have an Nvidia card, you may need to re-install PyTorch.
+
+ **Current CUDA Version**
+ - Informational. This is linked to the Current PyTorch Version, as detailed above.
+
+ **Current TTS Version**
+ - Informational. The current version of the TTS engine that is running.
+
+ **AllTalk Github updated**
+ - As long as you have an internet connection, this will tell you last time AllTalk was updated on Github. It is checking the [commit list](https://github.com/erew123/alltalk_tts/commits/main/) to see when the last commit was made. As such, this could be simply a documentation update, a bug fix or new features. Its simply there as a guide to let you know the last time something was changed on AllTalk's Github.
+
+ **TTS Subprocess**
+ - When AllTalk reaches this stage, the subprocess that loads in the AI model is starting. This is most likely where an error could occur with loading the TTS model, just after the documentation message.
+
+ **AllTalk Settings & Documentation: http ://x.x.x.x**
+ - The link where you can reach AllTalks built in settings and documentation page. The TTS model will be loading immediately after this is displayed.
+
+
+
+
+ ๐จ AllTalk is only loading into CPU, but I have an Nvidia GPU so it should be loading into CUDA
+
+ This is caused by Pytorch (Torch) not having the CUDA extensions installed (You can check by running the diagnostics). Typically this happens (on Standalone installations) because when the setup routine goes to install Pytorch with CUDA, it looks in the PIP cache and if a previous application has downloaded a version of Pytorch that **doesn't** have CUDA extensions, the PIP installer doesnt recognise this fact and just uses the cached version for installation. To resolve this:
+
+ 1) On the `atsetup` utility, on the `Standalone menu` select to `Purge the PIP cache`. This will remove cached packages from the PIP cache, meaning it will have to download fresh copies.
+ 2) As we need to force the upgrade to the Python environment, the easiest way to do this will be to use `atsetup` to `Delete AllTalk's custom Python environment`. This means it will have to rebuild the Python environment. **Note**, you may have to run this step twice, as it has to exit the current Python environment, then you have to re-load `atsetup` and select `Delete AllTalk's custom Python environment` again.
+ 3) You can now use `atsetup` to `Install AllTalk as a Standalone Application` which will download fresh copies of everything and re-install the Python environment.
+ 4) Once this is done you can check if CUDA is now working with the diagnostics or starting AllTalk and checking the model loads into CUDA.
+
+
+
+
+
+ ๐จ RuntimeError: PytorchStreamReader failed reading zip archive: failed finding central directory
+
+ This error message is caused by the model being corrupted or damaged in some way. This error can occur if Huggingface, where the model is downloaded from, have an error (when the model is downloaded) or potentailly internet issues occuring while the model is downloaded on first start-up.
+
+ ```
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ RuntimeError: PytorchStreamReader failed reading zip archive: failed finding central directory
+
+ ERROR: Application startup failed. Exiting.
+ [AllTalk Startup] Warning TTS Subprocess has NOT started up yet, Will keep trying for 120 seconds maximum. Please wait.
+ ```
+
+ To resolve this, first look in your `alltalk_tts/models/xttsv2_2.0.2` (or whichever) model folder and confirm that the file sizes are correct.
+
+ 
+
+ You can delete one or more suspect files and a factory fresh copy of that file or files will be downloaded on next start-up of AllTalk.
+
+
+
+
+ ๐จ RuntimeError: Found no NVIDIA driver on your system.
+
+ This error message is caused by DeepSpeed being enabled when you do not have a Nvidia GPU. To resolve this, edit `confignew.json` and change `"deepspeed_activate": true,` to `"deepspeed_activate": false,` then restart AllTalk.
+
+ ```
+ File "C:\alltalk_tts\alltalk_environment\env\Lib\site-packages\torch\cuda\__init__.py", line 302, in _lazy_init
+ torch._C._cuda_init()
+ RuntimeError: Found no NVIDIA driver on your system. Please check that you have an NVIDIA GPU and installed a driver from http://www.nvidia.com/Download/index.aspx
+
+ ERROR: Application startup failed. Exiting.
+ [AllTalk Startup] Warning TTS Subprocess has NOT started up yet, Will keep trying for 120 seconds maximum. Please wait.
+ ```
+
+
+
+
+ ๐จ raise RuntimeError("PyTorch version mismatch! DeepSpeed ops were compiled and installed.
+
+ This error message is caused by having DeepSpeed enabled, but you have a version of DeepSpeed installed that was compiled for a different version of Python, PyTorch or CUDA (or any mix of those). You will need to start your Python environment and run `pip uninstall deepspeed` to remove DeepSpeed from your Python environment and then install the correct version of DeepSpeed.
+
+ ```
+ raise RuntimeError("PyTorch version mismatch! DeepSpeed ops were compiled and installed
+ RuntimeError: PyTorch version mismatch! DeepSpeed ops were compiled and installed with a different version than what is being used at runtime. Please re-install DeepSpeed or switch torch versions. Install torch version=2.1, Runtime torch version=2.2
+ ```
+
+
+
+
+ ๐จ Warning TTS Subprocess has NOT started up yet, Will keep trying for 120 seconds maximum. Please wait. It times out after 120 seconds.
+ When the subprocess is starting 2x things are occurring:
+
+ **A)** Its trying to load the voice model into your graphics card VRAM (assuming you have a Nvidia Graphics card, otherwise its your system RAM)
+ **B)** Its trying to start up the mini-webserver and send the "ready" signal back to the main process.
+
+ Before giving other possibilities a go, some people with **old machines** are finding their startup times are **very** slow 2-3 minutes. Ive extended the allowed time within the script from 1 minute to 2 minutes. **If you have an older machine** and wish to try extending this further, you can do so by editing `script.py` and changing `startup_wait_time = 120` (120 seconds, aka 2 minutes) at the top of the script.py file, to a larger value e.g `startup_wait_time = 240` (240 seconds aka 4 minutes).
+
+ **Note:** If you need to create a support ticket, please create a `diagnostics.log` report file to submit with a support request. Details on doing this are above.
+
+ Other possibilities for this issue are:
+
+ 1) You are starting AllTalk in both your `CMD FLAG.txt` and `settings.yaml` file. The `CMD FLAG.txt` you would have manually edited and the `settings.yaml` is the one you change and save in the `session` tab of text-generation-webui and you can `Save UI defaults to settings.yaml`. Please only have one of those two starting up AllTalk.
+
+ 2) You are not starting text-generation-webui with its normal Python environment. Please start it with start_{your OS version} as detailed [here](https://github.com/oobabooga/text-generation-webui#how-to-install) (`start_windows.bat`,`./start_linux.sh`, `start_macos.sh` or `start_wsl.bat`) OR (`cmd_windows.bat`, `./cmd_linux.sh`, `cmd_macos.sh` or `cmd_wsl.bat` and then `python server.py`).
+
+ 3) You have installed the wrong version of DeepSpeed on your system, for the wrong version of Python/Text-generation-webui. You can go to your text-generation-webui folder in a terminal/command prompt and run the correct cmd version for your OS e.g. (`cmd_windows.bat`, `./cmd_linux.sh`, `cmd_macos.sh` or `cmd_wsl.bat`) and then you can type `pip uninstall deepspeed` then try loading it again. If that works, please see here for the correct instructions for installing DeepSpeed [here](https://github.com/erew123/alltalk_tts#-deepspeed-installation-options).
+
+ 4) You have an old version of text-generation-webui (pre Dec 2023) I have not tested on older versions of text-generation-webui, so cannot confirm viability on older versions. For instructions on updating the text-generation-webui, please look [here](https://github.com/oobabooga/text-generation-webui#how-to-install) (`update_linux.sh`, `update_windows.bat`, `update_macos.sh`, or `update_wsl.bat`).
+
+ 5) You already have something running on port 7851 on your computer, so the mini-webserver cant start on that port. You can change this port number by editing the `confignew.json` file and changing `"port_number": "7851"` to `"port_number": "7602"` or any port number you wish that isnโt reserved. Only change the number and save the file, do not change the formatting of the document. This will at least discount that you have something else clashing on the same port number.
+
+ 6) You have antivirus/firewalling that is blocking that port from being accessed. If you had to do something to allow text-generation-webui through your antivirus/firewall, you will have to do that for this too.
+
+ 7) You have quite old graphics drivers and may need to update them.
+
+ 8) Something within text-generation-webui is not playing nicely for some reason. You can go to your text-generation-webui folder in a terminal/command prompt and run the correct cmd version for your OS e.g. (`cmd_windows.bat`, `./cmd_linux.sh`, `cmd_macos.sh` or `cmd_wsl.bat`) and then you can type `python extensions\alltalk_tts\script.py` and see if AllTalk starts up correctly. If it does then something else is interfering.
+
+ 9) Something else is already loaded into your VRAM or there is a crashed python process. Either check your task manager for erroneous Python processes or restart your machine and try again.
+
+ 10) You are running DeepSpeed on a Linux machine and although you are starting with `./start_linux.sh` AllTalk is failing there on starting. This is because text-generation-webui will overwrite some environment variables when it loads its python environment. To see if this is the problem, from a terminal go into your text-generation-webui folder and `./cmd_linux.sh` then set your environment variable again e.g. `export CUDA_HOME=/usr/local/cuda` (this may vary depending on your OS, but this is the standard one for Linux, and assuming you have installed the CUDA toolkit), then `python server.py` and see if it starts up. If you want to edit the environment permanently you can do so, I have not managed to write full instructions yet, but here is the conda guide [here](https://conda.io/projects/conda/en/latest/user-guide/tasks/manage-environments.html#set-env-vars).
+
+ 11) You have built yourself a custom Python environment and something is funky with it. This is very hard to diagnose as its not a standard environment. You may want to updating text-generation-webui and re installing its requirements file (whichever one you use that comes down with text-generation-webui).
+
+
+
+ ๐จ I have multiple GPU's and I have problems running Finetuning
+
+ Finetuning pulls in various other scripts and some of those scripts can have issues with multiple Nvidia GPU's being present. Until the people that created those other scripts fix up their code, there is a workaround to temporarily tell your system to only use the 1x of your Nvidia GPU's. To do this:
+
+ - **Windows** - You will start the script with `set CUDA_VISIBLE_DEVICES=0 && python finetune.py`
+ After you have completed training, you can reset back with `set CUDA_VISIBLE_DEVICES=`
+
+ - **Linux** - You will start the script with `CUDA_VISIBLE_DEVICES=0 python finetune.py`
+ After you have completed training, you can reset back with `unset CUDA_VISIBLE_DEVICES`
+
+ Rebooting your system will also unset this. The setting is only applied temporarily.
+
+ Depending on which of your Nvidia GPU's is the more powerful one, you can change the `0` to `1` or whichever of your GPU's is the most powerful.
+
+
+
+
+ ๐จ Firefox - Streaming Audio doesnt work on Firefox
+
+ This is a long standing issue with Mozilla & Firefox and one I am unable to resolve as Mozilla have not resolved the issue with Firefox. The solution is to use another web browser if you want to use Streaming audio. For details of my prior invesitigation please look at this [ticket](https://github.com/erew123/alltalk_tts/issues/143)
+
+
+ ### Application Specific Issues
+
+
+ ๐จ SillyTavern - I changed my IP address and now SillyTavern wont connect with AllTalk
+ SillyTavern checks the IP address when loading extensions, saving the IP to its configuration only if the check succeeds. For whatever reason, SillyTavern's checks dont always allow changing its IP address a second time.
+
+ To manually change the IP address:
+
+ 1) Navigate to the SillyTavern Public folder located at `/sillytavern/public/`.
+ 2) Open the `settings.json` file.
+ 3) Look for the AllTalk section and find the `provider_endpoint` entry.
+ 3) Replace `localhost` with your desired IP address, for example, `192.168.1.64`.
+
+ 
+
+
+ ### TTS Generation Issues & Questions
+
+
+ ๐จ XTTS - Does the XTTS AI Model Support Emotion Control or Singing?
+
+ No, the XTTS AI model does not currently support direct control over emotions or singing capabilities. While XTTS infuses generated speech with a degree of emotional intonation based on the context of the text, users cannot explicitly control this aspect. It's worth noting that regenerating the same line of TTS may yield slightly different emotional inflections, but there is no way to directly control it with XTTS.
+
+
+ ๐จ XTTS - Skips, repeats or pronunciation Issues
+
+ Firstly, it's important to clarify that the development and maintenance of the XTTS AI models and core scripts are handled by [Coqui](https://docs.coqui.ai/en/latest/index.html), with additional scripts and libraries from entities like [huggingface](https://huggingface.co/docs/transformers/en/index) among many other Python scripts and libraries used by AllTalk.
+
+ AllTalk is designed to be a straightforward interface that simplifies setup and interaction with AI TTS models like XTTS. Currently, AllTalk supports the XTTS model, with plans to include more models in the future. Please understand that the deep inner workings of XTTS, including reasons why it may skip, repeat, or mispronounce, along with 3rd party scripts and libraries utilized, are ultimately outside my control.
+
+ Although I ensure the text processed through AllTalk is accurately relayed to the XTTS model speech generation process, and I have aimed to mitigate as many issues as much as possible; skips, repeats and bad pronounciation can still occur.
+
+ Certain aspects I have not been able to investigate due to my own time limitations, are:
+
+ - The impact of DeepSpeed on TTS quality. Is this more likely to cause skips or repetition?
+ - Comparative performance between different XTTS model versions (e.g., 2.0.3 vs. 2.0.2) regarding audio quality and consistency.
+
+ **From my experience and anecdotally gained knowledge:**
+
+ - Lower quality voice samples tend to produce more anomalies in generated speech.
+ - Model finetuning with high-quality voice samples significantly reduces such issues, enhancing overall speech quality.
+ - Unused/Excessive punctuation causes issues e.g. asterisks `*`, hashes `#`, brackets `(` `)` etc. Many of these AllTalk will filter out.
+
+ So for example, the `female_01.wav` file that is provided with AllTalk is a studio quality voice sample, which the XTTS model was trained on. Typically you will find it unlikely that anomolies occur with TTS generation when using this voice sample. Hence good quality samples and finetuning, generally improve results with XTTS.
+
+ If you wish to try out the XTTS version 2.0.3 model and see if it works better, you can download it from [here](https://huggingface.co/coqui/XTTS-v2/tree/v2.0.3), replacing all the files within your `/alltalk_tts/models/xttsv2_2.0.2` folder. This is on my list to both test version 2.0.3 more, but also build a more flexible TTS models downloader, that will not only accomdating other XTTS models, but also other TTS engines. If you try the XTTS version 2.0.3 model and gleen any insights, please let me know.
+
+ """
+
+ finetuning_a_model = """
+ ### โซ Finetuning a model
+ If you have a voice that the model doesnt quite reproduce correctly, or indeed you just want to improve the reproduced voice, then finetuning is a way to train your "XTTSv2 local" model **(stored in `/alltalk_tts/models/xxxxx/`)** on a specific voice. For this you will need:
+
+ - An Nvidia graphics card. (Please see the help section [note](https://github.com/erew123/alltalk_tts/edit/main/README.md#performance-and-compatibility-issues) if you have multiple Nvidia GPU's).
+ - 18GB of disk space free (most of this is used temporarily)
+ - At least 2 minutes of good quality speech from your chosen speaker in mp3, wav or flacc format, in one or more files (have tested as far as 20 minutes worth of audio).
+ - As a side note, many people seem to think that the Whisper v2 model (used on Step 1) is giving better results at generating training datasets, so you may prefer to try that, as opposed to the Whisper 3 model.
+
+ #### โซ How will this work/How complicated is it?
+ Everything has been done to make this as simple as possible. At its simplest, you can literally just download a large chunk of audio from an interview, and tell the finetuning to strip through it, find spoken parts and build your dataset. You can literally click 4 buttons, then copy a few files and you are done. At it's more complicated end you will clean up the audio a little beforehand, but its still only 4x buttons and copying a few files.
+
+ #### โซ The audio you will use
+ I would suggest that if its in an interview format, you cut out the interviewer speaking in audacity or your chosen audio editing package. You dont have to worry about being perfect with your cuts, the finetuning Step 1 will go and find spoken audio and cut it out for you. Is there is music over the spoken parts, for best quality you would cut out those parts, though its not 100% necessary. As always, try to avoid bad quality audio with noises in it (humming sounds, hiss etc). You can try something like [Audioenhancer](https://audioenhancer.ai/) to try clean up noisier audio. There is no need to down-sample any of the audio, all of that is handled for you. Just give the finetuning some good quality audio to work with.
+
+ #### โซ Can I Finetune a model more than once on more than one voice
+ Yes you can. You would do these as multiple finetuning's, but its absolutely possible and fine to do. Finetuning the XTTS model does not restrict it to only being able to reproduce that 1x voice you trained it on. Finetuning is generally nudging the model in a direction to learn the ability to sound a bit more like a voice its not heard before.
+
+ #### โซ A note about anonymous training Telemetry information & disabling it
+ Portions of Coqui's TTS trainer scripts gather anonymous training information which you can disable. Their statement on this is listed [here](https://github.com/coqui-ai/Trainer?tab=readme-ov-file#anonymized-telemetry). If you start AllTalk Finetuning with `start_finetuning.bat` or `./start_finetuning.sh` telemetry will be disabled. If you manually want to disable it, please expand the below:
+
+
+ Manually disable telemetry
+
+ Before starting finetuning, run the following in your terminal/command prompt:
+
+ - On Windows by typing `set TRAINER_TELEMETRY=0`
+ - On Linux & Mac by typing `export TRAINER_TELEMETRY=0`
+
+ Before you start `finetune.py`. You will now be able to finetune offline and no anonymous training data will be sent.
+
+ """
+
+ finetuning_setup = """
+ #### โซ Prerequisites for Fine-tuning with Nvidia CUDA Development Toolkit 11.8
+
+ All the requirements for Finetuning will be installed by using the atsetup utility and installing your correct requirements (Standalone or for Text-generation-webui). The legacy manual instructions are stored below, however these shouldnt be required.
+
+
+ Legacy manual instructions for installing Nvidia CUDA Development Toolkit 11.8
+ - To perform fine-tuning, a specific portion of the **Nvidia CUDA Development Toolkit v11.8** must be installed. This is crucial for step 1 of fine-tuning. The objective is to minimize the installation footprint by installing only the essential components.
+ - The **Nvidia CUDA Development Toolkit v11.8** operates independently from your graphics card drivers and the CUDA version utilized by your Python environment.
+ - This installation process aims to keep the download and install size as minimal as possible, however a full install of the tookit requires 3GB's of disk space.
+ - When running Finetuning it will require upto 20GB's of temporary disk space, so please ensure you have this space available and preferably use a SSD or NVME drive.
+
+ 1. **Download the Toolkit**:
+ - Obtain the **network install** version of the Nvidia CUDA Development Toolkit 11.8 from [Nvidia's Archive](https://developer.nvidia.com/cuda-11-8-0-download-archive).
+
+ 2. **Run the Installer**:
+ - Choose **Custom (Advanced)** installation.
+ - Deselect all options initially.
+ - Select the following components:
+ - `CUDA` > `Development` > `Compiler` > `nvcc`
+ - `CUDA` > `Development` > `Libraries` > `CUBLAS` (**both** development and runtime)
+
+ 3. **Configure Environment Search Path**:
+ - It's essential that `nvcc` and CUDA 11.8 library files are discoverable in your environment's search path. Adjustments can be reverted post-fine-tuning if desired.
+
+ **For Windows**:
+ - Edit the `Path` environment variable to include `C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v11.8\bin`.
+ - Add `CUDA_HOME` and set its path to `C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v11.8.`
+
+ **For Linux**:
+ - The path may vary by Linux distribution. Here's a generic setup:
+ - `export CUDA_HOME=/usr/local/cuda`
+ - `export PATH=${CUDA_HOME}/bin:${PATH}`
+ - `export LD_LIBRARY_PATH=${CUDA_HOME}/lib64:$LD_LIBRARY_PATH`
+
+ - Consider adding these to your `~/.bashrc` for permanence, or apply temporarily for the current session by running the above commands each time you start your Python environment.
+
+ **Note**: If using Text-generation-webui, its best to set these temporarily.
+
+ 4. **Verify Installation**:
+ - Open a **new** terminal/command prompt to refresh the search paths.
+ - In a terminal or command prompt, execute `nvcc --version`.
+ - Success is indicated by a response of `Cuda compilation tools, release 11.8.` Specifically, ensure it is version 11.8.
+
+ 5. **Troubleshooting**:
+ - If the correct version isn't reported, recheck your environment path settings for accuracy and potential conflicts with other CUDA versions.
+
+
+ #### Additional Note on Torch and Torchaudio:
+ - Ensure Torch and Torchaudio are CUDA-enabled (any version), which is separate from the CUDA Toolkit installation. CUDA 11.8 corresponds to `cu118` and CUDA 12.1 to `cu121` in AllTalk diagnostics.
+ - Failure to install CUDA for Torch and Torchaudio will result in Step 2 of fine-tuning failing. These requirements are distinct from the CUDA Toolkit installation, so avoid conflating the two.
+
+
+ #### โซ Starting Fine-tuning
+
+ **NOTE:** Ensure AllTalk has been launched at least once after any updates to download necessary files for fine-tuning.
+
+ 1. **Close Resource-Intensive Applications**:
+ - Terminate any applications that are using your GPU/VRAM to ensure enough resources for fine-tuning.
+
+ 2. **Organize Voice Samples**:
+ - Place your audio samples into the following directory:
+ `/alltalk_tts/finetune/put-voice-samples-in-here/`
+
+ Depending on your setup (Text-generation-webui or Standalone AllTalk), the steps to start the Python environment vary:
+
+ ### **For Standalone AllTalk Users**:
+ Navigate to the `alltalk_tts` folder:
+ > `cd alltalk_tts`
+
+ Start the Python environment:
+ > **Windows**: `start_finetune.bat`
+ **Linux**: `./start_finetune.sh`
+
+ ### **For Text-generation-webui Users**:
+ Navigate to the Text-generation-webui directory:
+ >`cd text-generation-webui`
+
+ Start the Python environment suitable for your OS:
+ **Windows**: `cmd_windows.bat`
+ **Linux**: `./cmd_linux.sh`
+ **macOS**: `cmd_macos.sh`
+ **WSL (Windows Subsystem for Linux)**: `cmd_wsl.bat`
+
+ Move into the AllTalk directory:
+ >`cd extensions/alltalk_tts`
+
+ **Linux** users only need to run this command:
+ `
+ export LD_LIBRARY_PATH=`python3 -c 'import os; import nvidia.cublas.lib; import nvidia.cudnn.lib; print(os.path.dirname(nvidia.cublas.lib.__file__) + ":" + os.path.dirname(nvidia.cudnn.lib.__file__))'`
+ `
+ Start the fine-tuning process with the command:
+ > `python finetune.py`
+
+ > If you're unfamiliar with Python environments and wish to learn more, consider reviewing **Understanding Python Environments Simplified** in the Help section.
+
+ ### Both Standalone & Text-generation-webui users
+ 3. **Pre-Flight Checklist**:
+ > Go through the pre-flight checklist to ensure readiness. Address any issues flagged as "Fail".
+
+ 4. **Post Fine-tuning Actions**:
+ > Upon completing fine-tuning, the final tab will guide you on managing your files and relocating your newly trained model to the appropriate directory.
+ """
+
+ finetuning_details = """
+ #### โซ How many Epochs etc is the right amount?
+
+ In finetuning the suggested/recommended amount of epochs, batch size, evaluation percent etc is already set. However, there is no absolutely correct answer to what the settings should be, it all depends on what you are doing.
+
+ - If you just want to train a normal human voice that is in an existing language, for most peopleโs needs, the base settings would work fine. You may choose to increase the epochs up to maybe 20, or run a second round of training if needed.
+ - If you were training an entirely new language, you would need a huge amount of training data and it requires around 1000 epochs (based on things I can find around the internet of people who tried this).
+ - If you are training a cartoon style voice in an existing language, it may need well upwards of 40 epochs until it can reproduce that voice with some success.
+
+ There are no absolute correct settings, as there are too many variables, ranging from the amount of samples you are using (5 minutes worth? 4 hours worth? etc), if they are similar samples to what the AI model already understands, so on and so forth. Coqui whom originally trained the model usually say something along the lines of, once youโve trained it X amount, if it sounds good then you are done and if it doesnโt, train it more.
+
+ #### โซ Evaluation Data Percentage
+ In the process of finetuning, it's crucial to balance the data used for training the model against the data reserved for evaluating its performance. Typically, a portion of the dataset is set aside as an 'evaluation set' to assess the model's capabilities in dealing with unseen data. On Step 1 of finetuning you have the option to adjust this evaluation data percentage, offering more control over your model training process.
+ **Why Adjust the Evaluation Percentage?**
+ Adjusting the evaluation percentage **can** be beneficial in scenarios with limited voice samples. When dealing with a smaller dataset, allocating a slightly larger portion to training could enhance the model's ability to learn from these scarce samples. Conversely, with abundant data, a higher evaluation percentage might be more appropriate to rigorously test the model's performance. There are currently no absolutely optimal split percentages as it varies by dataset.
+ - **Default Setting:** The default evaluation percentage is set at 15%, which is a balanced choice for most datasets.
+ - **Adjustable Range:** Users can now adjust this percentage, but itโs generally recommend keeping it between 5% and 30%.
+ - **Lower Bound:** A minimum of 5% ensures that there's enough data to evaluate model performance.
+ - **Upper Bound:** Its suggested not exceeding 30% for evaluation to avoid limiting the amount of data available for training.
+
+ - **Understanding the Impact:** Before adjusting this setting, it's important to understand its impact on model training and evaluation. Incorrect adjustments can lead to suboptimal model performance.
+ - **Gradual Adjustments:** For those unfamiliar with the process, we recommend reading up on training data and training sets, then making small, incremental changes and observing their effects.
+ - **Data Quality:** Regardless of the split, the quality of the audio data is paramount. Ensure that your datasets are built from good quality audio with enough data within them.
+
+ #### โซ Using a Finetuned model in Text-generation-webui
+
+ At the end of the finetune process, you will have an option to `Compact and move model to /trainedmodel/` this will compact the raw training file and move it to `/model/trainedmodel/`. When AllTalk starts up within Text-generation-webui, if it finds a model in this location a new loader will appear in the interface for `XTTSv2 FT` and you can use this to load your finetuned model.
**Be careful** not to train a new model from the base model, then overwrite your current `/model/trainedmodel/` **if** you want a seperately trained model. This is why there is an `OPTION B` to move your just trained model to `/models/lastfinetuned/`.
+
+ #### โซ Training one model with multiple voices
+
+ At the end of the finetune process, you will have an option to `Compact and move model to /trainedmodel/` this will compact the raw training file and move it to `/model/trainedmodel/`. This model will become available when you start up finetuning. You will have a choice to train the Base Model or the `Existing finetuned model` (which is the one in `/model/trainedmodel/`). So you can use this to keep further training this model with additional voices, then copying it back to `/model/trainedmodel/` at the end of training.
+
+ #### โซ Do I need to keep the raw training data/model?
+
+ If you've compacted and moved your model, its highly unlikely you would want to keep that data, however the choice is there to keep it if you wish. It will be between 5-10GB in size, so most people will want to delete it.
+
+ #### โซ I have deeper questions about training the XTTS model, where can I find more information?
+
+ If you have deeper questions about the XTTS model, its capabilites, the training process etc, anything thats not covered within the above text or the interface of `finetune.py`, please use the following links to research Coqui's documentation on the XTTS model.
+
+ - https://docs.coqui.ai/en/latest/models/xtts.html
+ - https://github.com/coqui-ai/TTS
+ - https://github.com/coqui-ai/TTS/discussions
+
+ """
+
+ tts_generator = """
+ ### โฌ AllTalk TTS Generator
+ AllTalk TTS Generator is the solution for converting large volumes of text into speech using the voice of your choice. Whether you're creating audio content or just want to hear text read aloud, the TTS Generator is equipped to handle it all efficiently. Please see here for a quick [demo](https://www.youtube.com/watch?v=hunvXn0mLzc)
The link to open the TTS generator can be found on the built-in Settings and Documentation page.
**DeepSpeed** is **highly** recommended to speed up generation. **Low VRAM** would be best turned off and your LLM model unloaded from your GPU VRAM (unload your model). **No Playback** will reduce memory overhead on very large generations (15,000 words or more). Splitting **Export to Wav** into smaller groups will also reduce memory overhead at the point of exporting your wav files (so good for low memory systems).
+
+ #### โฌ Estimated Throughput
+ This will vary by system for a multitude of reasons, however, while generating a 58,000 word document to TTS, with DeepSpeed enabled, LowVram disabled, splitting size 2 and on an Nvidia RTX 4070, throughput was around 1,000 words per minute. Meaning, this took 1 hour to generate the TTS. Exporting to combined wavs took about 2-3 minutes total.
+
+ #### โฌ Quick Start
+ > **Text Input:** Enter the text you wish to convert into speech in the 'Text Input' box.
+ > **Generate TTS:** Hit this to start the text-to-speech conversion.
+ > **Pause/Resume:** Used to pause and resume the playback of the initial generation of wavs or the stream.
+ > **Stop Playback:** This will stop the current audio playing back. It does not stop the text from being generated however.
+
+ Once you have sent text off to be generated, either as a stream or wav file generation, the TTS server will remain busy until this process has competed. As such, think carefully as to how much you want to send to the server.
+ If you are generating wav files and populating the queue, you can generate one lot of text to speech, then input your next lot of text and it will continue adding to the list.
+
+ #### โฌ Customization and Preferences
+ > **Character Voice:** Choose the voice that will read your text.
+ > **Language:** Select the language of your text.
+ > **Chunk Sizes:** Decide the size of text chunks for generation. Smaller sizes are recommended for better TTS quality.
+
+ #### โฌ Interface and Accessibility
+ > **Dark/Light Mode:** Switch between themes for your visual comfort.
+ > **Word Count and Generation Queue:** Keep track of the word count and the generation progress.
+
+ #### โฌ TTS Generation Modes
+ > **Wav Chunks:** Perfect for creating audio books, or anything you want to keep long term. Breaks down your text into manageable wav files and queues them up. Generation begins automatically, and playback will start after a few chunks have been prepared ahead. You can set the volume to 0 if you donโt want to hear playback. With Wav chunks, you can edit and/or regenerate portions of the TTS as needed.
+
+ > **Streaming:** For immediate playback without the ability to save. Ideal for on-the-fly speech generation and listening. This will not generate wav files and it will play back through your browser. You cannot stop the server generating the TTS once it has been sent.
+
+ With wav chunks you can either playback โIn Browserโ which is the web page you are on, or โOn Serverโ which is through the console/terminal where AllTalk is running from, or "No Playback". Only generation โIn Browserโ can play back smoothly and populate the Generated TTS List. Setting the Volume will affect the volume level played back both โIn Browserโ and โOn Serverโ.
+
+ For generating **large amounts of TTS**, it's recommended to select the **No Playback** option. This setting minimizes the memory usage in your web browser by avoiding the loading and playing of audio files directly within the browser, which is particularly beneficial for handling extensive audio generations. The definition of large will vary depending on your system RAM availability (will update when I have more information as to guidelines). Once the audio is generated, you can export your list to JSON (for safety) and use the **Play List** option to play back your audio.
+
+ #### โฌ Playback and List Management
+ > **Playback Controls:** Utilize 'Play List' to start from the beginning or 'Stop Playback' to halt at any time.
+ > **Custom Start:** Jump into your list at a specific ID to hear a particular section.
+ > **Regeneration and Editing:** If a chunk isn't quite right, you can opt to regenerate it or edit the text directly. Click off the text to save changes and hit regenerate for the specific line.
+ > **Export/Import List:** Save your TTS list as a JSON file or import one. Note: Existing wav files are needed for playback. Exporting is handy if you want to take your files away into another program and have a list of which wav is which, or if you keep your audio files, but want to come back at a later date, edit one or two lines, regenerate the speech and re-combine the wavโs into one new long wav.
+
+ #### โฌ Exporting Your Audio
+ > **Export to WAV:** Combine all generated TTS from the list, into one single WAV file for easy download and distribution. Its always recommended to export your list to a JSON before exporting, so that you have a backup, should something go wrong. You can simply re-import the list and try exporting again.
+
+ When exporting, there is a file size limit of 1GB and as such you have the option to choose how many files to include in each block of audio exported. 600 is just on the limit of 1GB, depending on the average file size, so 500 or less is a good amount to work with. You can combine the generated files after if you wish, in Audacity or similar.
+
+ Additionally, lower export batches will lower the memory requirements, so if your system is low on memory (maybe 8 or 16GB system), you can use smaller export batches to keep the memory requirement down.
+
+ #### โฌ Exporting Subtitles (SRT file)
+ > **Export SRT:** This will scan through all wav files in your list and generate a subtitles file that will match your exported wav file.
+
+ #### โฌ Analyzing generated TTS for errors
+ > **Analyze TTS:** This will scan through all wav files comparing each ID's orignal text with the TTS generated for that ID and then flag up inconsistences. Its important to understand this is a **best effort** process and **not 100% perfect**, for example:
+
+ > Your text may have the word `their` and the automated routine that listens to your generated TTS interprets the word as `there`, aka a spelling difference.
+ > Your text may have `Examples are:` (note the colon) and the automated routine that listens to your generated TTS interprets the word as `Examples are` (note NO colon as you cannot sound out a colon in TTS), aka a punctuation difference.
+ > Your text may have `There are 100 items` and the automated routine that listens to your generated TTS interprets the word as `There are one hundred items`, aka numbers vs the number written out in words.
+
+ There will be other examples such as double quotes. As I say, please remember this is a **best effort** to help you identify issues.
+
+ As such, there is a `% Accuracy` setting. This uses a couple of methods to try find things that are similar e.g. taking the `their` and `there` example from above, it would identify that they both sound the same, so even if the text says `their` and the AI listening to the generated TTS interprets the word as `there`, it will realise that both sound the same/are similar so there is no need to flag that as an error. However, there are limits to this and some things may slip through or get picked up when you would prefer them not to be flagged.
+
+ The higher the accuracy you choose, the more things it will flag up, however you may get more unwanted detections. The lower the less detections. Based on my few tests, accuracy settings between 96 to 98 seem to generally give the best results. However, I would highly recommend you test out a small 10-20 line text and test out the **Analyze TTS** button to get a feel for how it responds to different settings, as well as things it flags up.
+
+ You will be able to see the ID's and Text (orignal and as interpreted) by looking at the terminal/command prompt window.
+
+ The Analyze TTS feature uses the Whisper Larger-v2 AI engine, which will download on first use if necessary. This will require about 2.5GB's of disk space and could take a few minutes to download, depending on your internet connection.
+
+ You can use this feature on systems that do not have an Nvidia GPU, however, unless you have a very powerful CPU, expect it to be slow.
+
+ #### โฌ Tricks to get the model to say things correctly
+ Sometimes the AI model wonโt say something the way that you want it to. It could be because itโs a new word, an acronym or just something itโs not good at for whatever reason. There are some tricks you can use to improve the chances of it saying something correctly.
+
+ **Adding pauses**
+ You can use semi-colons ";" and colons ":" to create a pause, similar to a period "." which can be helpful with some splitting issues.
+
+ **Acronyms**
+ Not all acronyms are going to be pronounced correctly. Letโs work with the word `ChatGPT`. We know it is pronounced `"Chat G P T"` but when presented to the model, it doesnโt know how to break it down correctly. So, there are a few ways we could get it to break out "Chat" and the G P and T. e.g.
+
+ > `Chat G P T.`
+ > `Chat G,P,T.`
+ > `Chat G.P.T.`
+ > `Chat G-P-T.`
+ > `Chat gee pee tea`
+
+ All bar the last one are using ways within the English language to split out "Chat" into one word being pronounced and then split the G, P and T into individual letters. The final example, which is to use Phonetics will sound perfectly fine, but clearly would look wrong as far as human readable text goes. The phonetics method is very useful in edge cases where pronunciation difficult.
+
+ #### โฌ Notes on Usage
+ > For seamless TTS generation, it's advised to keep text chunks under 250 characters, which you can control with the Chunk sizes.
+ > Generated audio can be played back from the list, which also highlights the currently playing chunk.
+ > The TTS Generator remembers your settings, so you can pick up where you left off even after refreshing the page.
+ """
+
+ with gr.Tab("Documentation and Help"):
+ with gr.Tab("Narrator Function"): gr.Markdown(narrator_guides)
+ with gr.Tab("RVC"):
+ gr.Markdown(rvc_guides)
+ with gr.Row():
+ with gr.Column(): gr.Markdown(rvc_guides2)
+ with gr.Column(): gr.Markdown(rvc_guides3)
+ with gr.Tab("Low VRAM"): gr.Markdown(low_vram)
+ with gr.Tab("SillyTavern"): gr.Markdown(silly_tavern_support)
+ with gr.Tab("Updating"): gr.Markdown(updating_alltalk)
+ with gr.Tab("DeepSpeed"): gr.Markdown(deepspeed_guides)
+ with gr.Tab("Help with Problems"): gr.Markdown(help_with_problems)
+ with gr.Tab("XTTS Finetuning"):
+ with gr.Tab("XTTS Finetuning basics"): gr.Markdown(finetuning_a_model)
+ with gr.Tab("XTTS Finetuning Setup"): gr.Markdown(finetuning_setup)
+ with gr.Tab("XTTS Finetuning Detailed"): gr.Markdown(finetuning_details)
with gr.Tab("TTS Generator"): gr.Markdown(tts_generator)
\ No newline at end of file
diff --git a/system/gradio_pages/api_documentation.py b/system/gradio_pages/api_documentation.py
index 49844ee..08236a9 100644
--- a/system/gradio_pages/api_documentation.py
+++ b/system/gradio_pages/api_documentation.py
@@ -407,10 +407,10 @@ def api_documentation():
### ๐ Example Command Lines (Standard Generation)
Standard TTS generation supports narration and generates a WAV file/blob.
- > **Standard TTS Speech Example** - Generate a time-stamped file for standard text:
+ > **Standard TTS Speech Example** - Generate a time-stamped file for standard text and play the audio at the command prompt/terminal:
`curl -X POST "http://127.0.0.1:7851/api/tts-generate" -d "text_input=All of this is text spoken by the character. This is text not inside quotes, though that doesnt matter in the slightest" -d "text_filtering=standard" -d "character_voice_gen=female_01.wav" -d "narrator_enabled=false" -d "narrator_voice_gen=male_01.wav" -d "text_not_inside=character" -d "language=en" -d "output_file_name=myoutputfile" -d "output_file_timestamp=true" -d "autoplay=true" -d "autoplay_volume=0.8"`
- > **Narrator Example** - Generate a time-stamped file for text with narrator and character speech:
+ > **Narrator Example** - Generate a time-stamped file for text with narrator and character speech and play the audio at the command prompt/terminal:
`curl -X POST "http://127.0.0.1:7851/api/tts-generate" -d "text_input=*This is text spoken by the narrator* \\\"This is text spoken by the character\\\;". This is text not inside quotes." -d "text_filtering=standard" -d "character_voice_gen=female_01.wav" -d "narrator_enabled=true" -d "narrator_voice_gen=male_01.wav" -d "text_not_inside=character" -d "language=en" -d "output_file_name=myoutputfile" -d "output_file_timestamp=true" -d "autoplay=true" -d "autoplay_volume=0.8"`
> If your text contains double quotes, escape them with \" (see the narrator example).
@@ -420,7 +420,7 @@ def api_documentation():
> `curl -X POST "http://127.0.0.1:7851/api/tts-generate" -d "text_input=All of this is text spoken by the character. This is text not inside quotes, though that doesnt matter in the slightest"`
- Would generate the TTS for whatver engine is curretly loaded in. It will use the default API settings and default TTS engine settings to populate any missing fields, which gives you flexibility on how you want to send API requests to AllTalk.
+ Would generate the TTS for whatver engine is curretly loaded in. It will use the default API settings and default TTS engine settings `Global Settings > AllTalk API Defaults` to populate any missing fields, which gives you flexibility on how you want to send API requests to AllTalk.
### ๐ Request Parameters
#### **text_input**
From 4775d0db3468e355a88a6093c7d230c8053ba092 Mon Sep 17 00:00:00 2001
From: erew123 <35898566+erew123@users.noreply.github.com>
Date: Sat, 24 Aug 2024 13:30:02 +0100
Subject: [PATCH 12/38] work around locked drive letters
---
diagnostics.py | 10 ++++++++--
1 file changed, 8 insertions(+), 2 deletions(-)
diff --git a/diagnostics.py b/diagnostics.py
index f7c42ec..f1f03b0 100644
--- a/diagnostics.py
+++ b/diagnostics.py
@@ -110,8 +110,14 @@ def get_disk_info():
disk_info = []
partitions = psutil.disk_partitions()
for p in partitions:
- usage = psutil.disk_usage(p.mountpoint)
- disk_info.append(f" Drive: {p.device} | Total: {usage.total / (1024 ** 3):.2f} GB | Used: {usage.used / (1024 ** 3):.2f} GB | Free: {usage.free / (1024 ** 3):.2f} GB | Type: {p.fstype}")
+ try:
+ usage = psutil.disk_usage(p.mountpoint)
+ disk_info.append(
+ f"Drive: {p.device} | Total: {usage.total / (1024 ** 3):.2f} GB | Used: {usage.used / (1024 ** 3):.2f} GB | Free: {usage.free / (1024 ** 3):.2f} GB | Type: {p.fstype}"
+ )
+ except PermissionError:
+ # Handle the case where the drive is locked or inaccessible
+ disk_info.append(f"Drive: {p.device} | Inaccessible or locked")
return disk_info
def is_port_in_use(port):
From e5afa958cbb7f33b49b520511f2caf12a1687aef Mon Sep 17 00:00:00 2001
From: erew123 <35898566+erew123@users.noreply.github.com>
Date: Sat, 24 Aug 2024 13:35:34 +0100
Subject: [PATCH 13/38] Update requirements_standalone.txt
---
system/requirements/requirements_standalone.txt | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/system/requirements/requirements_standalone.txt b/system/requirements/requirements_standalone.txt
index 64844af..bbcff58 100644
--- a/system/requirements/requirements_standalone.txt
+++ b/system/requirements/requirements_standalone.txt
@@ -1,6 +1,6 @@
coqui-tts>=0.24.1
faster-whisper>=1.0.3
-fuzzywuzzy
+fuzzywuzzy>=0.18.0
gradio>=4.26.0
importlib_metadata>=7.2.1
inputimeout>=1.0.4
@@ -23,12 +23,12 @@ tqdm>=4.66.5
unidic-lite>=1.0.8
uvicorn>=0.29.0
pillow==10.3.0
-pypinyin
-hangul_romanize
-word2number
+pypinyin>=0.52.0
+hangul_romanize>=0.1.0
+word2number>=1.1
./system/config/fairseq-0.12.4-cp311-cp311-win_amd64.whl; sys_platform == "win32"
./system/config/fairseq-0.12.3.1-cp311-cp311-linux_x86_64.whl; sys_platform == "linux"
piper-tts; sys_platform == "linux"
piper-tts; sys_platform == "darwin"
piper-phonemize==1.1.0; sys_platform == "darwin"
-trainer
+trainer>=0.0.36
From 84db50fc18fb3eada8b0e5d8d4970bc2623231d4 Mon Sep 17 00:00:00 2001
From: erew123 <35898566+erew123@users.noreply.github.com>
Date: Sat, 24 Aug 2024 13:36:56 +0100
Subject: [PATCH 14/38] Update requirements_textgen.txt
---
system/requirements/requirements_textgen.txt | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/system/requirements/requirements_textgen.txt b/system/requirements/requirements_textgen.txt
index 575c4cd..8503aae 100644
--- a/system/requirements/requirements_textgen.txt
+++ b/system/requirements/requirements_textgen.txt
@@ -1,6 +1,6 @@
coqui-tts>=0.24.1
faster-whisper>=1.0.0
-fuzzywuzzy
+fuzzywuzzy>=0.18.0
gradio>=4.26.0
importlib_metadata>=7.1.0
inputimeout>=1.0.4
@@ -22,12 +22,12 @@ torchcrepe>=0.0.2
tqdm>=4.66.0
unidic-lite>=1.0.8
uvicorn>=0.29.0
-pypinyin
-hangul_romanize
-word2number
+pypinyin>=0.52.0
+hangul_romanize>=0.1.0
+word2number>=1.1
./system/config/fairseq-0.12.4-cp311-cp311-win_amd64.whl; sys_platform == "win32"
./system/config/fairseq-0.12.3.1-cp311-cp311-linux_x86_64.whl; sys_platform == "linux"
piper-tts; sys_platform == "linux"
piper-tts; sys_platform == "darwin"
piper-phonemize==1.1.0; sys_platform == "darwin"
-trainer
+trainer>=0.0.36
From c66fcee5e1cd312b4f2a41ee6680d3cd309f3da6 Mon Sep 17 00:00:00 2001
From: erew123 <35898566+erew123@users.noreply.github.com>
Date: Wed, 28 Aug 2024 12:36:51 +0100
Subject: [PATCH 15/38] remove ping_trainingrun
---
trainer_alltalk/trainer.py | 2 --
1 file changed, 2 deletions(-)
diff --git a/trainer_alltalk/trainer.py b/trainer_alltalk/trainer.py
index 9b345ec..f208bed 100644
--- a/trainer_alltalk/trainer.py
+++ b/trainer_alltalk/trainer.py
@@ -23,7 +23,6 @@ from torch.nn.parallel import DistributedDataParallel as DDP_th
from torch.optim.lr_scheduler import ReduceLROnPlateau
from torch.utils.data import DataLoader
-from trainer.analytics import ping_training_run
from trainer.callbacks import TrainerCallback
from trainer.generic_utils import (
KeepAverage,
@@ -597,7 +596,6 @@ class Trainer:
self.callbacks.on_init_end(self)
self.dashboard_logger.add_config(config)
self.save_training_script()
- ping_training_run()
@property
def use_apex(self):
From d76b6c2903e5a4af68bf96fb77b99800a81e7687 Mon Sep 17 00:00:00 2001
From: erew123 <35898566+erew123@users.noreply.github.com>
Date: Wed, 28 Aug 2024 12:48:27 +0100
Subject: [PATCH 16/38] Remove continue training
---
finetune.py | 8 +++++---
1 file changed, 5 insertions(+), 3 deletions(-)
diff --git a/finetune.py b/finetune.py
index 78e3dd7..f675c26 100644
--- a/finetune.py
+++ b/finetune.py
@@ -1791,7 +1791,9 @@ if __name__ == "__main__":
value=False,
label="Continue Previous Project",
scale=1,
+ visible=False,
)
+ # Continue Run has been set to invisible from the interface as its setting a 1000 epoch run when used. Have been unable to track down where this is coming from.
disable_shared_memory = gr.Checkbox(
value=False,
label="Disable Shared Memory Use",
@@ -1803,11 +1805,11 @@ if __name__ == "__main__":
scale=1,
)
train_csv = gr.Textbox(
- label="Train CSV:",
+ label="Train CSV file path:",
scale=2,
)
eval_csv = gr.Textbox(
- label="Eval CSV:",
+ label="Eval CSV file path:",
scale=2,
)
with gr.Row():
@@ -1984,7 +1986,7 @@ if __name__ == "__main__":
- What you are training (A human voice, A cartoon voice, A new language entirely etc)
- How much audio you have (you may want less or more eval or epochs)
โฝ The key indicator of sufficient model training is whether it sounds right to you. Coqui suggests beginning with the base settings for training. If the resulting audio doesn't meet your expectations, additional training sessions may be necessary.
- #### ๐ช Continue Previous Project
+ #### ๐ช Continue Previous Project (CURRENTLY REMOVED AND NOT AVAILABLE)
This option allows you to resume training from where it left off if your previous session was interrupted (e.g., due to a power outage or system crash). When enabled:
โฝ The system will attempt to load the most recent checkpoint from your previous training session.
โฝ It will continue training from that point, preserving your progress and saving time.
From 9a0896f692b887b1895349cf43f6a07595bee3f2 Mon Sep 17 00:00:00 2001
From: erew123 <35898566+erew123@users.noreply.github.com>
Date: Wed, 28 Aug 2024 12:48:50 +0100
Subject: [PATCH 17/38] Update requirements_standalone.txt
---
system/requirements/requirements_standalone.txt | 1 -
1 file changed, 1 deletion(-)
diff --git a/system/requirements/requirements_standalone.txt b/system/requirements/requirements_standalone.txt
index bbcff58..2aeb795 100644
--- a/system/requirements/requirements_standalone.txt
+++ b/system/requirements/requirements_standalone.txt
@@ -31,4 +31,3 @@ word2number>=1.1
piper-tts; sys_platform == "linux"
piper-tts; sys_platform == "darwin"
piper-phonemize==1.1.0; sys_platform == "darwin"
-trainer>=0.0.36
From 28cb6ca3f023c3310f972399eb048be1c3717138 Mon Sep 17 00:00:00 2001
From: erew123 <35898566+erew123@users.noreply.github.com>
Date: Wed, 28 Aug 2024 12:48:58 +0100
Subject: [PATCH 18/38] Update requirements_textgen.txt
---
system/requirements/requirements_textgen.txt | 1 -
1 file changed, 1 deletion(-)
diff --git a/system/requirements/requirements_textgen.txt b/system/requirements/requirements_textgen.txt
index 8503aae..3c44dc3 100644
--- a/system/requirements/requirements_textgen.txt
+++ b/system/requirements/requirements_textgen.txt
@@ -30,4 +30,3 @@ word2number>=1.1
piper-tts; sys_platform == "linux"
piper-tts; sys_platform == "darwin"
piper-phonemize==1.1.0; sys_platform == "darwin"
-trainer>=0.0.36
From c267bb198ed8ad70ae4e39d8a77e7626831c7c3a Mon Sep 17 00:00:00 2001
From: erew123 <35898566+erew123@users.noreply.github.com>
Date: Wed, 28 Aug 2024 12:49:12 +0100
Subject: [PATCH 19/38] Update requirements_colab.txt
---
system/requirements/requirements_colab.txt | 1 -
1 file changed, 1 deletion(-)
diff --git a/system/requirements/requirements_colab.txt b/system/requirements/requirements_colab.txt
index 4af1cee..fc9b8c6 100644
--- a/system/requirements/requirements_colab.txt
+++ b/system/requirements/requirements_colab.txt
@@ -42,4 +42,3 @@ python-multipart
hangul_romanize
word2number
piper-tts; sys_platform == "linux"
-trainer
From 5cb09eb813b4c9ae8ebb93b905d220b73c3b5d50 Mon Sep 17 00:00:00 2001
From: erew123 <35898566+erew123@users.noreply.github.com>
Date: Wed, 28 Aug 2024 12:59:28 +0100
Subject: [PATCH 20/38] Add files via upload
---
diagnostics.py | 22 ++++++++++++++++++----
1 file changed, 18 insertions(+), 4 deletions(-)
diff --git a/diagnostics.py b/diagnostics.py
index f1f03b0..929744c 100644
--- a/diagnostics.py
+++ b/diagnostics.py
@@ -108,16 +108,30 @@ def get_cpu_info():
def get_disk_info():
disk_info = []
- partitions = psutil.disk_partitions()
+ partitions = psutil.disk_partitions(all=True)
for p in partitions:
try:
usage = psutil.disk_usage(p.mountpoint)
disk_info.append(
f"Drive: {p.device} | Total: {usage.total / (1024 ** 3):.2f} GB | Used: {usage.used / (1024 ** 3):.2f} GB | Free: {usage.free / (1024 ** 3):.2f} GB | Type: {p.fstype}"
)
- except PermissionError:
- # Handle the case where the drive is locked or inaccessible
- disk_info.append(f"Drive: {p.device} | Inaccessible or locked")
+ except Exception as e:
+ # Handle various types of errors
+ error_type = type(e).__name__
+ error_message = str(e)
+
+ if isinstance(e, PermissionError):
+ status = "Inaccessible or locked (Permission denied)"
+ elif isinstance(e, OSError):
+ if e.winerror == 1117: # WinError 1117: I/O device error
+ status = "I/O device error"
+ else:
+ status = f"OS Error: {error_message}"
+ else:
+ status = f"Error: {error_type} - {error_message}"
+
+ disk_info.append(f"Drive: {p.device} | Status: {status} | Type: {p.fstype}")
+
return disk_info
def is_port_in_use(port):
From b7aa3a7c5c8df260edede52dd1b31a7b40d9b202 Mon Sep 17 00:00:00 2001
From: erew123 <35898566+erew123@users.noreply.github.com>
Date: Fri, 30 Aug 2024 12:55:06 +0100
Subject: [PATCH 21/38] Multi audio sample TTS generation w/XTTS
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
๐ง Using Single Voice Samples
Voice samples are stored in `/alltalk_tts/voices/` and should be named using the following format `name.wav`. These files will be listed as `name.wav` in the available voices list.
๐ง Using Multiple Voice Samples
If you have multiple voice samples for a single voice, you can organize them into subfolders within the `/alltalk_tts/voices/` directory. Each subfolder should be named according to the voice it contains, up to 5 voice samples will be randomly selected for use.
โข Each subfolder should reflect the name or type of the voice it contains (e.g., female_voice, male_voice).
โข The voice samples inside each subfolder should follow the standard .wav format.
โข An example folder path would be `/alltalk_tts/voices/mynewvoice/` and this would be listed in the available voices list as `mynewvoice/`.
This organization allows for easy selection and management of multiple voice samples while ensuring the system can correctly identify and utilize each voice. Manual CURL API requests would send the folder in the format `mynewvoice/`.
---
system/tts_engines/xtts/model_engine.py | 41 +-
system/tts_engines/xtts/xtts_settings_page.py | 703 +++++++++---------
2 files changed, 391 insertions(+), 353 deletions(-)
diff --git a/system/tts_engines/xtts/model_engine.py b/system/tts_engines/xtts/model_engine.py
index b7abc5f..111cb61 100644
--- a/system/tts_engines/xtts/model_engine.py
+++ b/system/tts_engines/xtts/model_engine.py
@@ -37,6 +37,7 @@ try:
import torchaudio
import wave
import io
+ import random
import numpy as np
from TTS.tts.configs.xtts_config import XttsConfig
from TTS.tts.models.xtts import Xtts
@@ -287,7 +288,19 @@ class tts_class:
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
directory = self.main_dir / "voices"
- voices = [f for f in os.listdir(directory) if os.path.isfile(os.path.join(directory, f)) and f.endswith(".wav")]
+ # Step 1: Add .wav files in the main "voices" directory to the list
+ voices.extend([f for f in os.listdir(directory) if os.path.isfile(os.path.join(directory, f)) and f.endswith(".wav")])
+
+ # Step 2: Walk through subfolders and add subfolder names if they contain .wav files
+ for root, dirs, files in os.walk(directory):
+ # Skip the root directory itself and only consider subfolders
+ if os.path.normpath(root) != os.path.normpath(directory):
+ if any(f.endswith(".wav") for f in files):
+ folder_name = os.path.basename(root) + "/"
+ voices.append(folder_name)
+
+ # Remove "voices/" from the list if it somehow got added
+ voices = [v for v in voices if v != "voices/"]
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
# โโโ Keep everything below this line โโโ
@@ -470,15 +483,31 @@ class tts_class:
# XTTSv2 LOCAL & Xttsv2 FT Method
print(f"[{self.branding}Debug] Deciding if streaming or not") if self.debug_tts else None
- print(f"[{self.branding}Debug] self.current_model_loaded", self.current_model_loaded) if self.debug_tts else None
+ print(f"[{self.branding}Debug] self.current_model_loaded is:", self.current_model_loaded) if self.debug_tts else None
self.current_model_loaded
- if os.path.isdir(voice):
- wavs_files = glob.glob(os.path.join(voice, "*.wav"))
+ print(f"[{self.branding}Debug] Audio Sample Detection") if self.debug_tts else None
+ print(f"[{self.branding}Debug] Voice name sent in request is:", voice) if self.debug_tts else None
+ # Check if the voice ends with a slash, indicating it's a directory
+ if voice.endswith("/") or voice.endswith("\\"):
+ # Remove the trailing slash for proper path detection
+ voice = voice.rstrip("/\\")
+ if os.path.isdir(os.path.join(self.main_dir, "voices", voice)):
+ # Normalize the path for the directory and then search for .wav files
+ normalized_path = os.path.normpath(os.path.join(self.main_dir, "voices", voice))
+ wavs_files = glob.glob(os.path.join(normalized_path, "*.wav"))
+ print(f"[{self.branding}Debug] Directory of multiple voice samples detected. Using multiple WAV files:", wavs_files) if self.debug_tts else None
+ # If there are more than 5 .wav files, randomly select 5
+ if len(wavs_files) > 5:
+ wavs_files = random.sample(wavs_files, 5)
+ print(f"[{self.branding}Debug] More than 5 wav files detected so only using 5x random audio samples:", wavs_files) if self.debug_tts else None
else:
- wavs_files = [f"{self.main_dir}/voices/{voice}"]
+ # Normalize the path for the file
+ normalized_path = os.path.normpath(os.path.join(self.main_dir, "voices", voice))
+ wavs_files = [normalized_path]
+ print(f"[{self.branding}Debug] Single voice sample detected. Using one WAV sample:", wavs_files) if self.debug_tts else None
if self.current_model_loaded.startswith ("xtts"):
- print(f"[{self.branding}Debug] Text arriving at engine {text}") if self.debug_tts else None
+ print(f"[{self.branding}Debug] Text arriving at TTS engine is: {text}") if self.debug_tts else None
gpt_cond_latent, speaker_embedding = self.model.get_conditioning_latents(
audio_path=wavs_files,
gpt_cond_len=self.model.config.gpt_cond_len,
diff --git a/system/tts_engines/xtts/xtts_settings_page.py b/system/tts_engines/xtts/xtts_settings_page.py
index a547aa4..17da289 100644
--- a/system/tts_engines/xtts/xtts_settings_page.py
+++ b/system/tts_engines/xtts/xtts_settings_page.py
@@ -1,348 +1,357 @@
-import os
-import json
-import requests
-import gradio as gr
-from tqdm import tqdm
-from pathlib import Path
-
-this_dir = Path(__file__).parent.resolve() # Sets up self.this_dir as a variable for the folder THIS script is running in.
-main_dir = Path(__file__).parent.parent.parent.parent.resolve() # Sets up self.main_dir as a variable for the folder AllTalk is running in
-
-##########################################################################
-# REQUIRED CHANGE #
-# Populate the voices list, using the method specific to your TTS engine #
-##########################################################################
-# This function is responsible for populating the list of available voices for your TTS engine.
-# You need to modify this function to use the appropriate method for your engine to retrieve the voice list.
-#
-# The current implementation lists all the WAV files in a "voices" directory, which may not be suitable for your engine.
-# You should replace the `xxxx_voices_file_list` function name to match your engine name. For example, if your engine
-# is named "mytts", the function should be named `mytts_voices_file_list`.
-#
-# You will also neef to update the code with your own implementation that retrieves the voice list according to your
-# engine's specific requirements. Typically this is the same code as will be in your model_engine.py file.
-#
-# For example, if your engine has a dedicated API or configuration file for managing voices, you should modify this
-# function to interact with that API or read from that configuration file.
-#
-# After making the necessary changes, this function should return a list of available voices that can be used
-# in your TTS engine's settings page.
-
-def xtts_voices_file_list():
- directory = main_dir / "voices"
- files = [f for f in os.listdir(directory) if os.path.isfile(os.path.join(directory, f)) and f.endswith(".wav")]
- return files
-
-######################################################
-# REQUIRED CHANGE #
-# Imports and saves the TTS engine-specific settings #
-######################################################
-# This function is responsible for importing and saving the settings specific to your TTS engine.
-# You need to make the following change:
-#
-# 1. Change the name of the function `xxxx_model_update_settings` to match your engine's name.
-# For example, if your engine is named "mytts", the function should be named `mytts_model_update_settings`.
-#
-# After making this change, the function will load the model settings from a JSON file, update the settings and voice
-# dictionaries with the values provided as arguments, and save the updated settings back to the JSON file.
-#
-# You do not need to modify the function's logic or any other part of the code.
-
-def xtts_model_update_settings(def_character_voice_gr, def_narrator_voice_gr, lowvram_enabled_gr, deepspeed_enabled_gr, temperature_set_gr, repetitionpenalty_set_gr, pitch_set_gr, generationspeed_set_gr, alloy_gr, echo_gr, fable_gr, nova_gr, onyx_gr, shimmer_gr):
- # Load the model_config_data from the JSON file
- with open(os.path.join(this_dir, "model_settings.json"), "r") as f:
- model_config_data = json.load(f)
-
- # Update the settings and openai_voices dictionaries with the new values
- model_config_data["settings"]["def_character_voice"] = def_character_voice_gr
- model_config_data["settings"]["def_narrator_voice"] = def_narrator_voice_gr
- model_config_data["openai_voices"]["alloy"] = alloy_gr
- model_config_data["openai_voices"]["echo"] = echo_gr
- model_config_data["openai_voices"]["fable"] = fable_gr
- model_config_data["openai_voices"]["nova"] = nova_gr
- model_config_data["openai_voices"]["onyx"] = onyx_gr
- model_config_data["openai_voices"]["shimmer"] = shimmer_gr
- model_config_data["settings"]["lowvram_enabled"] = lowvram_enabled_gr == "Enabled"
- model_config_data["settings"]["deepspeed_enabled"] = deepspeed_enabled_gr == "Enabled"
- model_config_data["settings"]["temperature_set"] = temperature_set_gr
- model_config_data["settings"]["repetitionpenalty_set"] = repetitionpenalty_set_gr
- model_config_data["settings"]["pitch_set"] = pitch_set_gr
- model_config_data["settings"]["generationspeed_set"] = generationspeed_set_gr
-
- # Save the updated model_config_data to the JSON file
- with open(os.path.join(this_dir, "model_settings.json"), "w") as f:
- json.dump(model_config_data, f, indent=4)
-
- return "Settings updated successfully!"
-
-#######################################################
-# REQUIRED CHANGE #
-# Sets up the engine-specific settings page in Gradio #
-#######################################################
-# This function sets up the Gradio interface for the settings page specific to your TTS engine.
-# You need to make the following changes:
-#
-# 1. Change the name of the function `xxxx_model_alltalk_settings` to match your engine's name.
-# For example, if your engine is named "mytts", the function should be named `mytts_model_alltalk_settings`.
-#
-# 2. Change the name of the `submit_button.click` function call to match the name you gave to the function
-# that imports and saves your engine's settings (the function you modified above).
-#
-# 3. Change the name of the `voice_list` function call to match the name of the function that lists
-# the available voices for your TTS engine.
-#
-# 4. Change the 'title' of the `gr.Blocks` to match your engine's name e.g. title="mytts TTS"
-#
-# After making these changes, this function will create and return the Gradio interface for your TTS engine's
-# settings page, allowing users to configure various options and voice selections.
-
-def xtts_model_alltalk_settings(model_config_data):
- features_list = model_config_data['model_capabilties']
- voice_list = xtts_voices_file_list()
- with gr.Blocks(title="Xtts TTS") as app:
- with gr.Tab("Default Settings"):
- with gr.Row():
- lowvram_enabled_gr = gr.Radio(choices={"Enabled": "true", "Disabled": "false"}, label="Low VRAM" if model_config_data["model_capabilties"]["lowvram_capable"] else "Low VRAM N/A", value="Enabled" if model_config_data["settings"]["lowvram_enabled"] else "Disabled", interactive=model_config_data["model_capabilties"]["lowvram_capable"])
- deepspeed_enabled_gr = gr.Radio(choices={"Enabled": "true", "Disabled": "false"}, label="DeepSpeed Activate" if model_config_data["model_capabilties"]["deepspeed_capable"] else "DeepSpeed N/A", value="Enabled" if model_config_data["settings"]["deepspeed_enabled"] else "Disabled", interactive=model_config_data["model_capabilties"]["deepspeed_capable"])
- temperature_set_gr = gr.Slider(value=float(model_config_data["settings"]["temperature_set"]), minimum=0, maximum=1, step=0.05, label="Temperature" if model_config_data["model_capabilties"]["temperature_capable"] else "Temperature N/A", interactive=model_config_data["model_capabilties"]["temperature_capable"])
- repetitionpenalty_set_gr = gr.Slider(value=float(model_config_data["settings"]["repetitionpenalty_set"]), minimum=1, maximum=20, step=1, label="Repetition Penalty" if model_config_data["model_capabilties"]["repetitionpenalty_capable"] else "Repetition N/A", interactive=model_config_data["model_capabilties"]["repetitionpenalty_capable"])
- pitch_set_gr = gr.Slider(value=float(model_config_data["settings"]["pitch_set"]), minimum=-10, maximum=10, step=1, label="Pitch" if model_config_data["model_capabilties"]["pitch_capable"] else "Pitch N/A", interactive=model_config_data["model_capabilties"]["pitch_capable"])
- generationspeed_set_gr = gr.Slider(value=float(model_config_data["settings"]["generationspeed_set"]), minimum=0.25, maximum=2.00, step=0.25, label="Speed" if model_config_data["model_capabilties"]["generationspeed_capable"] else "Speed N/A", interactive=model_config_data["model_capabilties"]["generationspeed_capable"])
- with gr.Row():
- with gr.Column():
- gr.Markdown("### OpenAI Voice Mappings")
- with gr.Group():
- with gr.Row():
- alloy_gr = gr.Dropdown(value=model_config_data["openai_voices"]["alloy"], label="Alloy", choices=voice_list, allow_custom_value=True)
- echo_gr = gr.Dropdown(value=model_config_data["openai_voices"]["echo"], label="Echo", choices=voice_list, allow_custom_value=True)
- with gr.Row():
- fable_gr = gr.Dropdown(value=model_config_data["openai_voices"]["fable"], label="Fable", choices=voice_list, allow_custom_value=True)
- nova_gr = gr.Dropdown(value=model_config_data["openai_voices"]["nova"], label="Nova", choices=voice_list, allow_custom_value=True)
- with gr.Row():
- onyx_gr = gr.Dropdown(value=model_config_data["openai_voices"]["onyx"], label="Onyx", choices=voice_list, allow_custom_value=True)
- shimmer_gr = gr.Dropdown(value=model_config_data["openai_voices"]["shimmer"], label="Shimmer", choices=voice_list, allow_custom_value=True)
- with gr.Column():
- gr.Markdown("### Default Voices")
- with gr.Row():
- def_character_voice_gr = gr.Dropdown(value=model_config_data["settings"]["def_character_voice"], label="Default/Character Voice", choices=voice_list, allow_custom_value=True)
- def_narrator_voice_gr = gr.Dropdown(value=model_config_data["settings"]["def_narrator_voice"], label="Narrator Voice", choices=voice_list, allow_custom_value=True)
- with gr.Group():
- with gr.Row():
- details_text = gr.Textbox(label="Details", show_label=False, lines=5, interactive=False, value="In this section, you can set the default settings for this TTS engine. Settings that are not supported by the current engine will be greyed out and cannot be selected. Default voices specified here will be used when no specific voice is provided in the TTS generation request. If a voice is specified in the request, it will override these default settings. When using the OpenAI API compatable API with this TTS engine, the voice mappings will be applied. As the OpenAI API has a limited set of 6 voices, these mappings ensure compatibility by mapping the OpenAI voices to the available voices in this TTS engine.")
- with gr.Row():
- submit_button = gr.Button("Update Settings")
- output_message = gr.Textbox(label="Output Message", interactive=False, show_label=False)
-
- submit_button.click(xtts_model_update_settings, inputs=[def_character_voice_gr, def_narrator_voice_gr, lowvram_enabled_gr, deepspeed_enabled_gr, temperature_set_gr, repetitionpenalty_set_gr, pitch_set_gr, generationspeed_set_gr, alloy_gr, echo_gr, fable_gr, nova_gr, onyx_gr, shimmer_gr], outputs=output_message)
-
- ###########################################################################################
- # Do not change this section apart from "TTS Engine Name" value to match your engine name #
- ###########################################################################################
- with gr.Tab("Engine Information"):
- with gr.Row():
- with gr.Group():
- gr.Textbox(label="Manufacturer Name", value=model_config_data['model_details']['manufacturer_name'], interactive=False)
- gr.Textbox(label="Manufacturer Website/TTS Engine Support", value=model_config_data['model_details']['manufacturer_website'], interactive=False)
- gr.Textbox(label="Engine/Model Description", value=model_config_data['model_details']['model_description'], interactive=False, lines=13)
- with gr.Column():
- with gr.Row():
- gr.Textbox(label="DeepSpeed Capable", value='Yes' if features_list['deepspeed_capable'] else 'No', interactive=False)
- gr.Textbox(label="Pitch Capable", value='Yes' if features_list['pitch_capable'] else 'No', interactive=False)
- gr.Textbox(label="Generation Speed Capable", value='Yes' if features_list['generationspeed_capable'] else 'No', interactive=False)
- with gr.Row():
- gr.Textbox(label="Repetition Penalty Capable", value='Yes' if features_list['repetitionpenalty_capable'] else 'No', interactive=False)
- gr.Textbox(label="Multi Languages Capable", value='Yes' if features_list['languages_capable'] else 'No', interactive=False)
- gr.Textbox(label="Streaming Capable", value='Yes' if features_list['streaming_capable'] else 'No', interactive=False)
- with gr.Row():
- gr.Textbox(label="Low VRAM Capable", value='Yes' if features_list['lowvram_capable'] else 'No', interactive=False)
- gr.Textbox(label="Temperature Capable", value='Yes' if features_list['temperature_capable'] else 'No', interactive=False)
- gr.Textbox(label="Multi Model Capable Engine", value='Yes' if features_list['multimodel_capable'] else 'No', interactive=False)
- with gr.Row():
- gr.Textbox(label="Multi Voice Capable Models", value='Yes' if features_list['multivoice_capable'] else 'No', interactive=False)
- gr.Textbox(label="Default Audio output format", value=model_config_data['model_capabilties']['audio_format'], interactive=False)
- gr.Textbox(label="TTS Engine Name", value="XTTS", interactive=False)
- with gr.Row():
- gr.Textbox(label="Windows Support", value='Yes' if features_list['windows_capable'] else 'No', interactive=False)
- gr.Textbox(label="Linux Support", value='Yes' if features_list['linux_capable'] else 'No', interactive=False)
- gr.Textbox(label="Mac Support", value='Yes' if features_list['mac_capable'] else 'No', interactive=False)
- with gr.Row():
- gr.Markdown("""
- #### ๐ง DeepSpeed Capable
- DeepSpeed is a deep learning optimization library that can significantly speed up model training and inference. If a model is DeepSpeed capable, it means it can utilize DeepSpeed to accelerate the generation of text-to-speech output. This requires the model to be loaded into CUDA/VRAM on an Nvidia GPU and the model's inference method to support DeepSpeed.
- #### ๐ง Pitch Capable
- Pitch refers to the highness or lowness of a sound. If a model is pitch capable, it can adjust the pitch of the generated speech, allowing for more expressive and varied output.
- #### ๐ง Generation Speed Capable
- Generation speed refers to the rate at which the model can generate text-to-speech output. If a model is generation speed capable, it means the speed of the generated speech can be adjusted, making it faster or slower depending on the desired output.
- #### ๐ง Repetition Penalty Capable
- Repetition penalty is a technique used to discourage the model from repeating the same words or phrases multiple times in the same sounding way. If a model is repetition penalty capable, it can apply this penalty during generation to improve the diversity and naturalness of the output.
- #### ๐ง Multi-Languages Capable
- Multi-language capable models can generate speech in multiple languages. This means that the model has been trained on data from different languages and can switch between them during generation. Some models are language-specific.
- #### ๐ง Multi-Voice Capable
- Multi-voice capable models generate speech in multiple voices or speaking styles. This means that the model has been trained on data from different speakers and can mimic their voices during generation, or is a voice cloning model that can generate speech based on the input sample.
- """)
- gr.Markdown("""
- #### ๐ง Streaming Capable
- Streaming refers to the ability to generate speech output in real-time, without the need to generate the entire output before playback. If a model is streaming capable, it can generate speech on-the-fly, allowing for faster response times and more interactive applications.
- #### ๐ง Low VRAM Capable
- VRAM (Video Random Access Memory) is a type of memory used by GPUs to store and process data. If a model is low VRAM capable, it can efficiently utilize the available VRAM by moving data between CPU and GPU memory as needed, allowing for generation even on systems with limited VRAM where it may be competing with an LLM model for VRAM.
- #### ๐ง Temperature Capable
- Temperature is a hyperparameter that controls the randomness of the generated output. If a model is temperature capable, the temperature can be adjusted to make the output more or less random, affecting the creativity and variability of the generated speech.
- #### ๐ง Multi-Model Capable Engine
- If an engine is multi-model capable, it means that it can support and utilize multiple models for text-to-speech generation. This allows for greater flexibility and the ability to switch between different models depending on the desired output. Different models may be capable of different languages, specific languages, voices, etc.
- #### ๐ง Default Audio Output Format
- Specifies the file format in which the generated speech will be saved. Common audio formats include WAV, MP3, FLAC, Opus, AAC, and PCM. If you want different outputs, you can set the transcode function to change the output audio, though transcoding will add a little time to the generation and is not available for streaming generation.
- #### ๐ง Windows/Linux/Mac Support
- These indicators show whether the model and engine are compatible with Windows, Linux, or macOS. However, additional setup or requirements may be necessary to ensure full functionality on your operating system. Please note that full platform support has not been extensively tested.
- """)
-
-
- with gr.Tab("Models/Voices Download"):
- with gr.Row():
- # Load the available models from the JSON file
- with open(os.path.join(this_dir, "available_models.json"), "r") as f:
- available_models = json.load(f)
- # Extract the model names for the dropdown
- model_names = [model["model_name"] for model in available_models["models"]]
- # Create the dropdown
- model_dropdown = gr.Dropdown(choices=sorted(model_names), label="Select Model", value=model_names[0])
- download_button = gr.Button("Download Model/Missing Files")
-
- with gr.Row():
- download_status = gr.Textbox(label="Download Status")
-
- def download_model(model_name):
- # Find the selected model in the available models
- selected_model = next(model for model in available_models["models"] if model["model_name"] == model_name)
-
- # Get the folder path and files to download
- folder_path = os.path.join(main_dir, "models", "xtts", selected_model["folder_path"])
- files_to_download = selected_model["files_to_download"]
-
- # Check if all files are already downloaded
- all_files_exists = all(os.path.exists(os.path.join(folder_path, file)) for file in files_to_download)
-
- if all_files_exists:
- return "All files are already downloaded. No need to download again."
- else:
- # Create the folder if it doesn't exist
- os.makedirs(folder_path, exist_ok=True)
-
- # Download the missing files
- for file, url in files_to_download.items():
- file_path = os.path.join(folder_path, file)
- if not os.path.exists(file_path):
- print(f"Downloading {file}...")
-
- 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(file_path, "wb") as file:
- for data in response.iter_content(block_size):
- progress_bar.update(len(data))
- file.write(data)
-
- progress_bar.close()
-
- return "Model downloaded successfully!"
-
- download_button.click(download_model, inputs=model_dropdown, outputs=download_status)
-
- def show_confirm_cancel(model_name):
- all_files_exists = all(os.path.exists(os.path.join(main_dir, "models", "xtts", model["folder_path"], file)) for model in available_models["models"] if model["model_name"] == model_name for file in model["files_to_download"])
-
- if all_files_exists:
- return [gr.update(visible=True), gr.update(visible=True), gr.update(visible=True)]
- else:
- return [gr.update(visible=False), gr.update(visible=True), gr.update(visible=False)]
-
- def confirm_download(model_name):
- download_model(model_name)
- return [gr.update(visible=False), gr.update(visible=True), gr.update(visible=False)]
-
- with gr.Row():
- confirm_button = gr.Button("Download Anyway", visible=False)
- cancel_button = gr.Button("Cancel", visible=False)
-
- download_button.click(show_confirm_cancel, model_dropdown, [confirm_button, download_button, cancel_button])
- confirm_button.click(confirm_download, model_dropdown, [confirm_button, download_button, cancel_button])
- cancel_button.click(lambda: [gr.update(visible=False), gr.update(visible=True), gr.update(visible=False)], None, [confirm_button, download_button, cancel_button])
-
- ###################################################################################################
- # REQUIRED CHANGE #
- # Add any engine specific help, bugs, issues, operating system specifc requirements/setup in here #
- # Please use Markdown format, so gr.Markdown() with your markdown inside it. #
- ###################################################################################################
- with gr.Tab("Engine Help"):
- with gr.Row():
- gr.Markdown("""
- ### ๐ง Using my own Finetuned models
- Please your Finetuned models within their own folder in `/alltalk_tts/models/xtts/`. Once they are in here they will become availabe within the interface after reloading the XTTS engine.
-
- ### ๐ง Using Voice Samples
- Voice samples are stored in `/alltalk_tts/voices/` and should be named using the following format `name.wav`
-
- ### ๐ง Where are the outputs stored & Automatic output wav file deletion
- Voice outputs are stored in `/alltalk_tts/outputs/`. You can configure automatic maintenance deletion of old wav files by setting `Del WAV's older than` in the global settings.
-
- > When `Disabled`, your output wav files will be left untouched.
- > When set to a setting `1 Day` or greater, your output wav files older than that time period will be automatically deleted on start-up of AllTalk.
-
- ### ๐ง Where are the models stored?
- This extension will download the models to `/alltalk_tts/models/xtts/` folder.
-
- ### ๐ง How do I create a new voice sample?
- To create a new voice sample, you need to make a wav file that is `22050Hz`, `Mono`, `16 bit` and between 6 to 30 seconds long, though 8 to 10 seconds is usually good enough. The model can handle up to 30 second samples, however I've not noticed any improvement in voice output from much longer clips.
- You want to find a nice clear selection of audio, so lets say you wanted to clone your favourite celebrity. You may go looking for an interview where they are talking. Pay close attention to the audio you are listening to and trying to sample. Are there noises in the background, hiss on the soundtrack, a low humm, some quiet music playing or something? The better quality the audio the better the final TTS result. Don't forget, the AI that processes the sounds can hear everything in your sample and it will use them in the voice its trying to recreate.
- Try make your clip one of nice flowing speech, like the included example files. No big pauses, gaps or other sounds. Preferably a sample that the person you are trying to copy will show a little vocal range and emotion in their voice. Also, try to avoid a clip starting or ending with breathy sounds (breathing in/out etc).
- """)
- gr.Markdown("""
- ### ๐ง Editing your sample!
- So, you've downloaded your favourite celebrity interview off YouTube, from here you need to chop it down to 6 to 30 seconds in length and resample it. If you need to clean it up, do audio processing, volume level changes etc, do this before down-sampling.
- Using the latest version of Audacity `select/highlight` your 6 to 30 second clip and:
- โข `Tracks` > `Resample to 22050Hz`
- โข `Tracks` > `Mix` > `Stereo to Mono`
- โข `File` > `Export Audio` saving it as a `WAV` of `22050Hz`.
- Save your generated wav file in the `/alltalk_tts/voices/` folder.
-
- ### ๐ง Why doesnt it sound like XXX Person?
- Maybe you might be interested in trying Finetuning of the XTTS model. Otherwise, the reasons can be that you:
-
- > Didn't down-sample it as above.
- > Have a bad quality voice sample.
- > Try using the 2x different generation methods `API Local` and `XTTSv2 Local`, as they generate output in slightly different ways.
-
- Additionally, use the RVC pipeline with a matching voice model, however, some samples just never seem to work correctly, so maybe try a different sample. Always remember though, this is an AI model attempting to re-create a voice, so you will never get a 100% match.
-
- ### ๐ง API Local, XTTSv2 Generation methods & Speed
- These two methods both produce sound output in slightly different ways. XTTSv2 is the perferable method a it supports DeepSpeed, which, if you have a system capable of DeepSpeed genereation, can result in a 2-3x speed gain in generation.
- """)
-
- return app
-
-################################
-# REQUIRED CHANGE #
-# Sets up the Gradio interface #
-################################
-# This function sets up the Gradio interface for your TTS engine's settings page.
-# You need to change the name of the function calls to match the names you set in the functions above.
-#
-# Specifically, you need to update the following:
-#
-# 1. The name of the function `xxxx_at_gradio_settings_page` to match your engine's name.
-# For example, if your engine is named "mytts", the function should be named `mytts_at_gradio_settings_page`.
-#
-# 2. The name of the function call `xxxx_model_alltalk_settings(model_config_data)`.
-# This should match the name you gave to the function that sets up the engine-specific settings page in Gradio.
-# If you named that function `mytts_model_alltalk_settings`, then the call should be:
-# `mytts_model_alltalk_settings(model_config_data)`
-#
-# After making these changes, this function will create and return the Gradio app for your TTS engine's settings page.
-
-def xtts_at_gradio_settings_page(model_config_data):
- app = xtts_model_alltalk_settings(model_config_data)
- return app
-def xtts_at_gradio_settings_page(model_config_data):
- app = xtts_model_alltalk_settings(model_config_data)
+import os
+import json
+import requests
+import gradio as gr
+from tqdm import tqdm
+from pathlib import Path
+
+this_dir = Path(__file__).parent.resolve() # Sets up self.this_dir as a variable for the folder THIS script is running in.
+main_dir = Path(__file__).parent.parent.parent.parent.resolve() # Sets up self.main_dir as a variable for the folder AllTalk is running in
+
+##########################################################################
+# REQUIRED CHANGE #
+# Populate the voices list, using the method specific to your TTS engine #
+##########################################################################
+# This function is responsible for populating the list of available voices for your TTS engine.
+# You need to modify this function to use the appropriate method for your engine to retrieve the voice list.
+#
+# The current implementation lists all the WAV files in a "voices" directory, which may not be suitable for your engine.
+# You should replace the `xxxx_voices_file_list` function name to match your engine name. For example, if your engine
+# is named "mytts", the function should be named `mytts_voices_file_list`.
+#
+# You will also neef to update the code with your own implementation that retrieves the voice list according to your
+# engine's specific requirements. Typically this is the same code as will be in your model_engine.py file.
+#
+# For example, if your engine has a dedicated API or configuration file for managing voices, you should modify this
+# function to interact with that API or read from that configuration file.
+#
+# After making the necessary changes, this function should return a list of available voices that can be used
+# in your TTS engine's settings page.
+
+def xtts_voices_file_list():
+ directory = main_dir / "voices"
+ files = [f for f in os.listdir(directory) if os.path.isfile(os.path.join(directory, f)) and f.endswith(".wav")]
+ return files
+
+######################################################
+# REQUIRED CHANGE #
+# Imports and saves the TTS engine-specific settings #
+######################################################
+# This function is responsible for importing and saving the settings specific to your TTS engine.
+# You need to make the following change:
+#
+# 1. Change the name of the function `xxxx_model_update_settings` to match your engine's name.
+# For example, if your engine is named "mytts", the function should be named `mytts_model_update_settings`.
+#
+# After making this change, the function will load the model settings from a JSON file, update the settings and voice
+# dictionaries with the values provided as arguments, and save the updated settings back to the JSON file.
+#
+# You do not need to modify the function's logic or any other part of the code.
+
+def xtts_model_update_settings(def_character_voice_gr, def_narrator_voice_gr, lowvram_enabled_gr, deepspeed_enabled_gr, temperature_set_gr, repetitionpenalty_set_gr, pitch_set_gr, generationspeed_set_gr, alloy_gr, echo_gr, fable_gr, nova_gr, onyx_gr, shimmer_gr):
+ # Load the model_config_data from the JSON file
+ with open(os.path.join(this_dir, "model_settings.json"), "r") as f:
+ model_config_data = json.load(f)
+
+ # Update the settings and openai_voices dictionaries with the new values
+ model_config_data["settings"]["def_character_voice"] = def_character_voice_gr
+ model_config_data["settings"]["def_narrator_voice"] = def_narrator_voice_gr
+ model_config_data["openai_voices"]["alloy"] = alloy_gr
+ model_config_data["openai_voices"]["echo"] = echo_gr
+ model_config_data["openai_voices"]["fable"] = fable_gr
+ model_config_data["openai_voices"]["nova"] = nova_gr
+ model_config_data["openai_voices"]["onyx"] = onyx_gr
+ model_config_data["openai_voices"]["shimmer"] = shimmer_gr
+ model_config_data["settings"]["lowvram_enabled"] = lowvram_enabled_gr == "Enabled"
+ model_config_data["settings"]["deepspeed_enabled"] = deepspeed_enabled_gr == "Enabled"
+ model_config_data["settings"]["temperature_set"] = temperature_set_gr
+ model_config_data["settings"]["repetitionpenalty_set"] = repetitionpenalty_set_gr
+ model_config_data["settings"]["pitch_set"] = pitch_set_gr
+ model_config_data["settings"]["generationspeed_set"] = generationspeed_set_gr
+
+ # Save the updated model_config_data to the JSON file
+ with open(os.path.join(this_dir, "model_settings.json"), "w") as f:
+ json.dump(model_config_data, f, indent=4)
+
+ return "Settings updated successfully!"
+
+#######################################################
+# REQUIRED CHANGE #
+# Sets up the engine-specific settings page in Gradio #
+#######################################################
+# This function sets up the Gradio interface for the settings page specific to your TTS engine.
+# You need to make the following changes:
+#
+# 1. Change the name of the function `xxxx_model_alltalk_settings` to match your engine's name.
+# For example, if your engine is named "mytts", the function should be named `mytts_model_alltalk_settings`.
+#
+# 2. Change the name of the `submit_button.click` function call to match the name you gave to the function
+# that imports and saves your engine's settings (the function you modified above).
+#
+# 3. Change the name of the `voice_list` function call to match the name of the function that lists
+# the available voices for your TTS engine.
+#
+# 4. Change the 'title' of the `gr.Blocks` to match your engine's name e.g. title="mytts TTS"
+#
+# After making these changes, this function will create and return the Gradio interface for your TTS engine's
+# settings page, allowing users to configure various options and voice selections.
+
+def xtts_model_alltalk_settings(model_config_data):
+ features_list = model_config_data['model_capabilties']
+ voice_list = xtts_voices_file_list()
+ with gr.Blocks(title="Xtts TTS") as app:
+ with gr.Tab("Default Settings"):
+ with gr.Row():
+ lowvram_enabled_gr = gr.Radio(choices={"Enabled": "true", "Disabled": "false"}, label="Low VRAM" if model_config_data["model_capabilties"]["lowvram_capable"] else "Low VRAM N/A", value="Enabled" if model_config_data["settings"]["lowvram_enabled"] else "Disabled", interactive=model_config_data["model_capabilties"]["lowvram_capable"])
+ deepspeed_enabled_gr = gr.Radio(choices={"Enabled": "true", "Disabled": "false"}, label="DeepSpeed Activate" if model_config_data["model_capabilties"]["deepspeed_capable"] else "DeepSpeed N/A", value="Enabled" if model_config_data["settings"]["deepspeed_enabled"] else "Disabled", interactive=model_config_data["model_capabilties"]["deepspeed_capable"])
+ temperature_set_gr = gr.Slider(value=float(model_config_data["settings"]["temperature_set"]), minimum=0, maximum=1, step=0.05, label="Temperature" if model_config_data["model_capabilties"]["temperature_capable"] else "Temperature N/A", interactive=model_config_data["model_capabilties"]["temperature_capable"])
+ repetitionpenalty_set_gr = gr.Slider(value=float(model_config_data["settings"]["repetitionpenalty_set"]), minimum=1, maximum=20, step=1, label="Repetition Penalty" if model_config_data["model_capabilties"]["repetitionpenalty_capable"] else "Repetition N/A", interactive=model_config_data["model_capabilties"]["repetitionpenalty_capable"])
+ pitch_set_gr = gr.Slider(value=float(model_config_data["settings"]["pitch_set"]), minimum=-10, maximum=10, step=1, label="Pitch" if model_config_data["model_capabilties"]["pitch_capable"] else "Pitch N/A", interactive=model_config_data["model_capabilties"]["pitch_capable"])
+ generationspeed_set_gr = gr.Slider(value=float(model_config_data["settings"]["generationspeed_set"]), minimum=0.25, maximum=2.00, step=0.25, label="Speed" if model_config_data["model_capabilties"]["generationspeed_capable"] else "Speed N/A", interactive=model_config_data["model_capabilties"]["generationspeed_capable"])
+ with gr.Row():
+ with gr.Column():
+ gr.Markdown("### OpenAI Voice Mappings")
+ with gr.Group():
+ with gr.Row():
+ alloy_gr = gr.Dropdown(value=model_config_data["openai_voices"]["alloy"], label="Alloy", choices=voice_list, allow_custom_value=True)
+ echo_gr = gr.Dropdown(value=model_config_data["openai_voices"]["echo"], label="Echo", choices=voice_list, allow_custom_value=True)
+ with gr.Row():
+ fable_gr = gr.Dropdown(value=model_config_data["openai_voices"]["fable"], label="Fable", choices=voice_list, allow_custom_value=True)
+ nova_gr = gr.Dropdown(value=model_config_data["openai_voices"]["nova"], label="Nova", choices=voice_list, allow_custom_value=True)
+ with gr.Row():
+ onyx_gr = gr.Dropdown(value=model_config_data["openai_voices"]["onyx"], label="Onyx", choices=voice_list, allow_custom_value=True)
+ shimmer_gr = gr.Dropdown(value=model_config_data["openai_voices"]["shimmer"], label="Shimmer", choices=voice_list, allow_custom_value=True)
+ with gr.Column():
+ gr.Markdown("### Default Voices")
+ with gr.Row():
+ def_character_voice_gr = gr.Dropdown(value=model_config_data["settings"]["def_character_voice"], label="Default/Character Voice", choices=voice_list, allow_custom_value=True)
+ def_narrator_voice_gr = gr.Dropdown(value=model_config_data["settings"]["def_narrator_voice"], label="Narrator Voice", choices=voice_list, allow_custom_value=True)
+ with gr.Group():
+ with gr.Row():
+ details_text = gr.Textbox(label="Details", show_label=False, lines=5, interactive=False, value="In this section, you can set the default settings for this TTS engine. Settings that are not supported by the current engine will be greyed out and cannot be selected. Default voices specified here will be used when no specific voice is provided in the TTS generation request. If a voice is specified in the request, it will override these default settings. When using the OpenAI API compatable API with this TTS engine, the voice mappings will be applied. As the OpenAI API has a limited set of 6 voices, these mappings ensure compatibility by mapping the OpenAI voices to the available voices in this TTS engine.")
+ with gr.Row():
+ submit_button = gr.Button("Update Settings")
+ output_message = gr.Textbox(label="Output Message", interactive=False, show_label=False)
+
+ submit_button.click(xtts_model_update_settings, inputs=[def_character_voice_gr, def_narrator_voice_gr, lowvram_enabled_gr, deepspeed_enabled_gr, temperature_set_gr, repetitionpenalty_set_gr, pitch_set_gr, generationspeed_set_gr, alloy_gr, echo_gr, fable_gr, nova_gr, onyx_gr, shimmer_gr], outputs=output_message)
+
+ ###########################################################################################
+ # Do not change this section apart from "TTS Engine Name" value to match your engine name #
+ ###########################################################################################
+ with gr.Tab("Engine Information"):
+ with gr.Row():
+ with gr.Group():
+ gr.Textbox(label="Manufacturer Name", value=model_config_data['model_details']['manufacturer_name'], interactive=False)
+ gr.Textbox(label="Manufacturer Website/TTS Engine Support", value=model_config_data['model_details']['manufacturer_website'], interactive=False)
+ gr.Textbox(label="Engine/Model Description", value=model_config_data['model_details']['model_description'], interactive=False, lines=13)
+ with gr.Column():
+ with gr.Row():
+ gr.Textbox(label="DeepSpeed Capable", value='Yes' if features_list['deepspeed_capable'] else 'No', interactive=False)
+ gr.Textbox(label="Pitch Capable", value='Yes' if features_list['pitch_capable'] else 'No', interactive=False)
+ gr.Textbox(label="Generation Speed Capable", value='Yes' if features_list['generationspeed_capable'] else 'No', interactive=False)
+ with gr.Row():
+ gr.Textbox(label="Repetition Penalty Capable", value='Yes' if features_list['repetitionpenalty_capable'] else 'No', interactive=False)
+ gr.Textbox(label="Multi Languages Capable", value='Yes' if features_list['languages_capable'] else 'No', interactive=False)
+ gr.Textbox(label="Streaming Capable", value='Yes' if features_list['streaming_capable'] else 'No', interactive=False)
+ with gr.Row():
+ gr.Textbox(label="Low VRAM Capable", value='Yes' if features_list['lowvram_capable'] else 'No', interactive=False)
+ gr.Textbox(label="Temperature Capable", value='Yes' if features_list['temperature_capable'] else 'No', interactive=False)
+ gr.Textbox(label="Multi Model Capable Engine", value='Yes' if features_list['multimodel_capable'] else 'No', interactive=False)
+ with gr.Row():
+ gr.Textbox(label="Multi Voice Capable Models", value='Yes' if features_list['multivoice_capable'] else 'No', interactive=False)
+ gr.Textbox(label="Default Audio output format", value=model_config_data['model_capabilties']['audio_format'], interactive=False)
+ gr.Textbox(label="TTS Engine Name", value="XTTS", interactive=False)
+ with gr.Row():
+ gr.Textbox(label="Windows Support", value='Yes' if features_list['windows_capable'] else 'No', interactive=False)
+ gr.Textbox(label="Linux Support", value='Yes' if features_list['linux_capable'] else 'No', interactive=False)
+ gr.Textbox(label="Mac Support", value='Yes' if features_list['mac_capable'] else 'No', interactive=False)
+ with gr.Row():
+ gr.Markdown("""
+ #### ๐ง DeepSpeed Capable
+ DeepSpeed is a deep learning optimization library that can significantly speed up model training and inference. If a model is DeepSpeed capable, it means it can utilize DeepSpeed to accelerate the generation of text-to-speech output. This requires the model to be loaded into CUDA/VRAM on an Nvidia GPU and the model's inference method to support DeepSpeed.
+ #### ๐ง Pitch Capable
+ Pitch refers to the highness or lowness of a sound. If a model is pitch capable, it can adjust the pitch of the generated speech, allowing for more expressive and varied output.
+ #### ๐ง Generation Speed Capable
+ Generation speed refers to the rate at which the model can generate text-to-speech output. If a model is generation speed capable, it means the speed of the generated speech can be adjusted, making it faster or slower depending on the desired output.
+ #### ๐ง Repetition Penalty Capable
+ Repetition penalty is a technique used to discourage the model from repeating the same words or phrases multiple times in the same sounding way. If a model is repetition penalty capable, it can apply this penalty during generation to improve the diversity and naturalness of the output.
+ #### ๐ง Multi-Languages Capable
+ Multi-language capable models can generate speech in multiple languages. This means that the model has been trained on data from different languages and can switch between them during generation. Some models are language-specific.
+ #### ๐ง Multi-Voice Capable
+ Multi-voice capable models generate speech in multiple voices or speaking styles. This means that the model has been trained on data from different speakers and can mimic their voices during generation, or is a voice cloning model that can generate speech based on the input sample.
+ """)
+ gr.Markdown("""
+ #### ๐ง Streaming Capable
+ Streaming refers to the ability to generate speech output in real-time, without the need to generate the entire output before playback. If a model is streaming capable, it can generate speech on-the-fly, allowing for faster response times and more interactive applications.
+ #### ๐ง Low VRAM Capable
+ VRAM (Video Random Access Memory) is a type of memory used by GPUs to store and process data. If a model is low VRAM capable, it can efficiently utilize the available VRAM by moving data between CPU and GPU memory as needed, allowing for generation even on systems with limited VRAM where it may be competing with an LLM model for VRAM.
+ #### ๐ง Temperature Capable
+ Temperature is a hyperparameter that controls the randomness of the generated output. If a model is temperature capable, the temperature can be adjusted to make the output more or less random, affecting the creativity and variability of the generated speech.
+ #### ๐ง Multi-Model Capable Engine
+ If an engine is multi-model capable, it means that it can support and utilize multiple models for text-to-speech generation. This allows for greater flexibility and the ability to switch between different models depending on the desired output. Different models may be capable of different languages, specific languages, voices, etc.
+ #### ๐ง Default Audio Output Format
+ Specifies the file format in which the generated speech will be saved. Common audio formats include WAV, MP3, FLAC, Opus, AAC, and PCM. If you want different outputs, you can set the transcode function to change the output audio, though transcoding will add a little time to the generation and is not available for streaming generation.
+ #### ๐ง Windows/Linux/Mac Support
+ These indicators show whether the model and engine are compatible with Windows, Linux, or macOS. However, additional setup or requirements may be necessary to ensure full functionality on your operating system. Please note that full platform support has not been extensively tested.
+ """)
+
+
+ with gr.Tab("Models/Voices Download"):
+ with gr.Row():
+ # Load the available models from the JSON file
+ with open(os.path.join(this_dir, "available_models.json"), "r") as f:
+ available_models = json.load(f)
+ # Extract the model names for the dropdown
+ model_names = [model["model_name"] for model in available_models["models"]]
+ # Create the dropdown
+ model_dropdown = gr.Dropdown(choices=sorted(model_names), label="Select Model", value=model_names[0])
+ download_button = gr.Button("Download Model/Missing Files")
+
+ with gr.Row():
+ download_status = gr.Textbox(label="Download Status")
+
+ def download_model(model_name):
+ # Find the selected model in the available models
+ selected_model = next(model for model in available_models["models"] if model["model_name"] == model_name)
+
+ # Get the folder path and files to download
+ folder_path = os.path.join(main_dir, "models", "xtts", selected_model["folder_path"])
+ files_to_download = selected_model["files_to_download"]
+
+ # Check if all files are already downloaded
+ all_files_exists = all(os.path.exists(os.path.join(folder_path, file)) for file in files_to_download)
+
+ if all_files_exists:
+ return "All files are already downloaded. No need to download again."
+ else:
+ # Create the folder if it doesn't exist
+ os.makedirs(folder_path, exist_ok=True)
+
+ # Download the missing files
+ for file, url in files_to_download.items():
+ file_path = os.path.join(folder_path, file)
+ if not os.path.exists(file_path):
+ print(f"Downloading {file}...")
+
+ 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(file_path, "wb") as file:
+ for data in response.iter_content(block_size):
+ progress_bar.update(len(data))
+ file.write(data)
+
+ progress_bar.close()
+
+ return "Model downloaded successfully!"
+
+ download_button.click(download_model, inputs=model_dropdown, outputs=download_status)
+
+ def show_confirm_cancel(model_name):
+ all_files_exists = all(os.path.exists(os.path.join(main_dir, "models", "xtts", model["folder_path"], file)) for model in available_models["models"] if model["model_name"] == model_name for file in model["files_to_download"])
+
+ if all_files_exists:
+ return [gr.update(visible=True), gr.update(visible=True), gr.update(visible=True)]
+ else:
+ return [gr.update(visible=False), gr.update(visible=True), gr.update(visible=False)]
+
+ def confirm_download(model_name):
+ download_model(model_name)
+ return [gr.update(visible=False), gr.update(visible=True), gr.update(visible=False)]
+
+ with gr.Row():
+ confirm_button = gr.Button("Download Anyway", visible=False)
+ cancel_button = gr.Button("Cancel", visible=False)
+
+ download_button.click(show_confirm_cancel, model_dropdown, [confirm_button, download_button, cancel_button])
+ confirm_button.click(confirm_download, model_dropdown, [confirm_button, download_button, cancel_button])
+ cancel_button.click(lambda: [gr.update(visible=False), gr.update(visible=True), gr.update(visible=False)], None, [confirm_button, download_button, cancel_button])
+
+ ###################################################################################################
+ # REQUIRED CHANGE #
+ # Add any engine specific help, bugs, issues, operating system specifc requirements/setup in here #
+ # Please use Markdown format, so gr.Markdown() with your markdown inside it. #
+ ###################################################################################################
+ with gr.Tab("Engine Help"):
+ with gr.Row():
+ gr.Markdown("""
+ ### ๐ง Using my own Finetuned models
+ Please your Finetuned models within their own folder in `/alltalk_tts/models/xtts/`. Once they are in here they will become availabe within the interface after reloading the XTTS engine.
+
+ ### ๐ง Using Single Voice Samples
+ Voice samples are stored in `/alltalk_tts/voices/` and should be named using the following format `name.wav`. These files will be listed as `name.wav` in the available voices list.
+
+ ### ๐ง Using Multiple Voice Samples
+ If you have multiple voice samples for a single voice, you can organize them into subfolders within the `/alltalk_tts/voices/` directory. Each subfolder should be named according to the voice it contains, up to 5 voice samples will be randomly selected for use.
+
+ โข Each subfolder should reflect the name or type of the voice it contains (e.g., `female_voice`, `male_voice`).
+ โข The voice samples inside each subfolder should follow the standard `.wav` format.
+ โข An example folder path would be `/alltalk_tts/voices/mynewvoice/` and this would be listed in the available voices list as `mynewvoice/`.
+
+ This organization allows for easy selection and management of multiple voice samples while ensuring the system can correctly identify and utilize each voice. Manual CURL API requests would send the folder in the format `mynewvoice/`.
+
+ ### ๐ง Where are the outputs stored & Automatic output wav file deletion
+ Voice outputs are stored in `/alltalk_tts/outputs/`. You can configure automatic maintenance deletion of old wav files by setting `Del WAV's older than` in the global settings.
+
+ > When `Disabled`, your output wav files will be left untouched.
+ > When set to a setting `1 Day` or greater, your output wav files older than that time period will be automatically deleted on start-up of AllTalk.
+
+ ### ๐ง Where are the models stored?
+ This extension will download the models to `/alltalk_tts/models/xtts/` folder.
+
+ ### ๐ง API Local, XTTSv2 Generation methods & Speed
+ These two methods both produce sound output in slightly different ways. XTTSv2 is the perferable method a it supports DeepSpeed, which, if you have a system capable of DeepSpeed genereation, can result in a 2-3x speed gain in generation.
+ """)
+ gr.Markdown("""
+ ### ๐ง How do I create a new voice sample?
+ To create a new voice sample, you need to make a wav file that is `22050Hz`, `Mono`, `16 bit` and between 6 to 30 seconds long, though 8 to 10 seconds is usually good enough. The model can handle up to 30 second samples, however I've not noticed any improvement in voice output from much longer clips.
+ You want to find a nice clear selection of audio, so lets say you wanted to clone your favourite celebrity. You may go looking for an interview where they are talking. Pay close attention to the audio you are listening to and trying to sample. Are there noises in the background, hiss on the soundtrack, a low humm, some quiet music playing or something? The better quality the audio the better the final TTS result. Don't forget, the AI that processes the sounds can hear everything in your sample and it will use them in the voice its trying to recreate.
+ Try make your clip one of nice flowing speech, like the included example files. No big pauses, gaps or other sounds. Preferably a sample that the person you are trying to copy will show a little vocal range and emotion in their voice. Also, try to avoid a clip starting or ending with breathy sounds (breathing in/out etc).
+
+ ### ๐ง Editing your sample!
+ So, you've downloaded your favourite celebrity interview off YouTube, from here you need to chop it down to 6 to 30 seconds in length and resample it. If you need to clean it up, do audio processing, volume level changes etc, do this before down-sampling.
+ Using the latest version of Audacity `select/highlight` your 6 to 30 second clip and:
+ โข `Tracks` > `Resample to 22050Hz`
+ โข `Tracks` > `Mix` > `Stereo to Mono`
+ โข `File` > `Export Audio` saving it as a `WAV` of `22050Hz`.
+ Save your generated wav file in the `/alltalk_tts/voices/` folder.
+
+ ### ๐ง Why doesnt it sound like XXX Person?
+ Maybe you might be interested in trying Finetuning of the XTTS model. Otherwise, the reasons can be that you:
+
+ > Didn't down-sample it as above.
+ > Have a bad quality voice sample.
+ > Try using the 2x different generation methods `API Local` and `XTTSv2 Local`, as they generate output in slightly different ways.
+
+ Additionally, use the RVC pipeline with a matching voice model, however, some samples just never seem to work correctly, so maybe try a different sample. Always remember though, this is an AI model attempting to re-create a voice, so you will never get a 100% match.
+ """)
+
+ return app
+
+################################
+# REQUIRED CHANGE #
+# Sets up the Gradio interface #
+################################
+# This function sets up the Gradio interface for your TTS engine's settings page.
+# You need to change the name of the function calls to match the names you set in the functions above.
+#
+# Specifically, you need to update the following:
+#
+# 1. The name of the function `xxxx_at_gradio_settings_page` to match your engine's name.
+# For example, if your engine is named "mytts", the function should be named `mytts_at_gradio_settings_page`.
+#
+# 2. The name of the function call `xxxx_model_alltalk_settings(model_config_data)`.
+# This should match the name you gave to the function that sets up the engine-specific settings page in Gradio.
+# If you named that function `mytts_model_alltalk_settings`, then the call should be:
+# `mytts_model_alltalk_settings(model_config_data)`
+#
+# After making these changes, this function will create and return the Gradio app for your TTS engine's settings page.
+
+def xtts_at_gradio_settings_page(model_config_data):
+ app = xtts_model_alltalk_settings(model_config_data)
+ return app
+def xtts_at_gradio_settings_page(model_config_data):
+ app = xtts_model_alltalk_settings(model_config_data)
return app
\ No newline at end of file
From d105ca02a57d87409444b34b8de806b7b39a844e Mon Sep 17 00:00:00 2001
From: erew123 <35898566+erew123@users.noreply.github.com>
Date: Fri, 30 Aug 2024 13:09:53 +0100
Subject: [PATCH 22/38] Update README.md
---
README.md | 7 ++++++-
1 file changed, 6 insertions(+), 1 deletion(-)
diff --git a/README.md b/README.md
index 47978fd..e7dbb2c 100644
--- a/README.md
+++ b/README.md
@@ -1,7 +1,11 @@
# AllTalk TTS v2 BETA
This is the BETA of v2. To be clear, that means they may well be bugs, issues, missing/incomplete documentation, a variety of potential problems etc (This is what BETA refers to in the software world for those whom dont know). This means you may need a bit of technical know how to deal with things that could come up and should only continue if you feel comfortable with that.
-Github discussions on the BETA are [here in the discussion board](https://github.com/erew123/alltalk_tts/discussions/245)
+Github **discussions** on the BETA are [here in the discussion board](https://github.com/erew123/alltalk_tts/discussions/245)
+
+For issues, please open an issue ticket [here in the issues area](https://github.com/erew123/alltalk_tts/issues)
+
+**Please note, my available time has become VERY limited due to unexpected family commitments.**
#### If you are going to run the BETA, please read all the info below....
@@ -20,6 +24,7 @@ Please go see [here](https://github.com/erew123/alltalk_tts/discussions/237)
- FYI there is no limit on how many XTTS models AllTalk will now find/work with.
- AllTalk starts up on 0.0.0.0, meaning it will bind to ALL available IP addresses. There is no setting its IP address any more.
- Updated Coqui TTS engine.
+- XTTS multiple audio sample TTS generation to improve voice reproduction https://github.com/erew123/alltalk_tts/commit/b7aa3a7c5c8df260edede52dd1b31a7b40d9b202
- Gradio web interface (see the screenshots)
- Retrieval based Voice Conversion, AKA, RVC voice Pipeline, re-written to work on Python 3.11.
- TTS Generator will now use any TTS engine.
From 341984315d1d6862d4e1dd88782f1bb5a1a0aa77 Mon Sep 17 00:00:00 2001
From: erew123 <35898566+erew123@users.noreply.github.com>
Date: Fri, 30 Aug 2024 13:11:31 +0100
Subject: [PATCH 23/38] Update README.md
---
README.md | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/README.md b/README.md
index e7dbb2c..beba9d3 100644
--- a/README.md
+++ b/README.md
@@ -3,9 +3,9 @@ This is the BETA of v2. To be clear, that means they may well be bugs, issues, m
Github **discussions** on the BETA are [here in the discussion board](https://github.com/erew123/alltalk_tts/discussions/245)
-For issues, please open an issue ticket [here in the issues area](https://github.com/erew123/alltalk_tts/issues)
+For **Issues/Bugs/Support**, please open an issue ticket [here in the issues area](https://github.com/erew123/alltalk_tts/issues)
-**Please note, my available time has become VERY limited due to unexpected family commitments.**
+**Please note, my available time has become VERY limited due to unexpected family commitments. So please DYOR and refer to the TTS manufacturer (listed in the interface) for issues with their TTS engines.**
#### If you are going to run the BETA, please read all the info below....
From da7ecfbb6ba0a4759a2a1ea36e03b2cb1b3968f2 Mon Sep 17 00:00:00 2001
From: erew123 <35898566+erew123@users.noreply.github.com>
Date: Tue, 10 Sep 2024 10:42:56 +0100
Subject: [PATCH 24/38] SillyTavern Note Added
Added notes on changing the IP/URL and basic details on how to debug/clear out older settings.
---
.../For AllTalk V2/Instructions.txt | 21 +-
.../For AllTalk V2/alltalk.js | 1956 ++++++++---------
2 files changed, 998 insertions(+), 979 deletions(-)
diff --git a/system/SillyTavern Extension/For AllTalk V2/Instructions.txt b/system/SillyTavern Extension/For AllTalk V2/Instructions.txt
index 4673f61..e87aa40 100644
--- a/system/SillyTavern Extension/For AllTalk V2/Instructions.txt
+++ b/system/SillyTavern Extension/For AllTalk V2/Instructions.txt
@@ -1 +1,20 @@
-Place the `alltalk.js` in your `\SillyTavern\public\scripts\extensions\tts\` folder, replacing the existing file.
\ No newline at end of file
+BASIC SETUP
+-----------
+Place the `alltalk.js` in your `\SillyTavern\public\scripts\extensions\tts\` folder, replacing the existing file.
+
+CHANGING THE DEFAULT IP ADDRESS
+-------------------------------
+If you change the default IP/URL in the SillyTavern interface, you need to press Enter for it to save the setting. Just typing in an address/URL will not change the setting.
+
+SIMPLE TROUBLESHOOTING
+----------------------
+The settings for AllTalk are stored, saved and managed by SillyTavern and not the AllTalk extension. You can manually edit OR delete these settings if necessary by editing the `settings.json` file that SillyTavern stores. I would suggest taking a copy of the file before making any changes.
+
+Older versions of SillyTavern store `settings.json` file in `\SillyTavern\public\`
+
+Newer version of SillyTavern store `settings.json` file in `\SillyTavern\data\default-user`
+
+Within the `settings.json` file you will find a section called `"AllTalk": {` and it is closed out a few lines down with `},`
+
+You can either manually edit any of the settings in there OR you can delete the entire section from the start of `"AllTalk":` to the end of `},` and when you refresh the SillyTavern web page OR restart SillyTavern, new settings for AllTalk will be created/saved.
+
diff --git a/system/SillyTavern Extension/For AllTalk V2/alltalk.js b/system/SillyTavern Extension/For AllTalk V2/alltalk.js
index ac8669b..a31f257 100644
--- a/system/SillyTavern Extension/For AllTalk V2/alltalk.js
+++ b/system/SillyTavern Extension/For AllTalk V2/alltalk.js
@@ -1,978 +1,978 @@
-import { doExtrasFetch, getApiUrl, modules } from '../../extensions.js';
-import { saveTtsProviderSettings } from './index.js';
-
-export { AllTalkTtsProvider };
-
-class AllTalkTtsProvider {
- //########//
- // Config //
- //########//
-
- settings = {};
- constructor() {
- // Initialize with default settings if they are not already set
- this.settings = {
- provider_endpoint: this.settings.provider_endpoint || 'http://localhost:7851',
- language: this.settings.language || 'en',
- voiceMap: this.settings.voiceMap || {},
- at_generation_method: this.settings.at_generation_method || 'standard_generation',
- narrator_enabled: this.settings.narrator_enabled || 'false',
- at_narrator_text_not_inside: this.settings.at_narrator_text_not_inside || 'narrator',
- narrator_voice_gen: this.settings.narrator_voice_gen || 'Please set a voice',
- rvc_character_voice: this.settings.rvc_character_voice || 'Disabled',
- rvc_narrator_voice: this.settings.rvc_narrator_voice || 'Disabled',
- finetuned_model: this.settings.finetuned_model || 'false'
- };
- // Separate property for dynamically updated settings from the server
- this.dynamicSettings = {
- modelsAvailable: [],
- currentModel: '',
- deepspeed_available: false,
- deepspeed_enabled: false,
- lowvram_capable: false,
- lowvram_enabled: false,
- };
- this.rvcVoices = []; // Initialize rvcVoices as an empty array
- }
- ready = false;
- voices = [];
- separator = '. ';
- audioElement = document.createElement('audio');
-
- languageLabels = {
- 'Arabic': 'ar',
- 'Brazilian Portuguese': 'pt',
- 'Chinese': 'zh-cn',
- 'Czech': 'cs',
- 'Dutch': 'nl',
- 'English': 'en',
- 'French': 'fr',
- 'German': 'de',
- 'Italian': 'it',
- 'Polish': 'pl',
- 'Russian': 'ru',
- 'Spanish': 'es',
- 'Turkish': 'tr',
- 'Japanese': 'ja',
- 'Korean': 'ko',
- 'Hungarian': 'hu',
- 'Hindi': 'hi',
- };
-
- get settingsHtml() {
- let html = `
+- If you change your TTS engine in AllTalk, you will need to Reload (button above) and re-select your voices.
+- Assuming the server is Status: Ready, most problems will be resolved by hitting Reload and selecting voices that match the loaded TTS engine.
+- Text-generation-webui users - Uncheck Enable TTS in the TGWUI interface, or you will hear 2x voices and file names being generated.
+
+
`;
+
+ return html;
+ }
+
+
+ //#################//
+ // Startup ST & AT //
+ //#################//
+
+ async loadSettings(settings) {
+ updateStatus('Offline');
+
+ if (Object.keys(settings).length === 0) {
+ console.info('Using default AllTalk TTS Provider settings');
+ } else {
+ // Populate settings with provided values, ignoring server-provided settings
+ for (const key in settings) {
+ if (key in this.settings) {
+ this.settings[key] = settings[key];
+ } else {
+ console.debug(`Ignoring non-user-configurable setting: ${key}`);
+ }
+ }
+ }
+
+ // Update UI elements to reflect the loaded settings
+ $('#at_server').val(this.settings.provider_endpoint);
+ $('#language_options').val(this.settings.language);
+ $('#at_generation_method').val(this.settings.at_generation_method);
+ $('#at_narrator_enabled').val(this.settings.narrator_enabled);
+ $('#at_narrator_text_not_inside').val(this.settings.at_narrator_text_not_inside);
+ $('#narrator_voice').val(this.settings.narrator_voice_gen);
+ $('#rvc_character_voice').val(this.settings.rvc_character_voice);
+ $('#rvc_narrator_voice').val(this.settings.rvc_narrator_voice);
+
+ console.debug('AllTalkTTS: Settings loaded');
+ try {
+ // Check if TTS provider is ready
+ await this.checkReady();
+ await this.updateSettingsFromServer(); // Fetch dynamic settings from the TTS server
+ await this.fetchTtsVoiceObjects(); // Fetch voices only if service is ready
+ await this.fetchRvcVoiceObjects(); // Fetch RVC voices
+ this.updateNarratorVoicesDropdown();
+ this.updateLanguageDropdown();
+ this.setupEventListeners();
+ this.applySettingsToHTML();
+ updateStatus('Ready');
+ } catch (error) {
+ console.error("Error loading settings:", error);
+ updateStatus('Offline');
+ }
+ }
+
+
+ applySettingsToHTML() {
+ const narratorVoiceSelect = document.getElementById('narrator_voice');
+ const atNarratorSelect = document.getElementById('at_narrator_enabled');
+ const textNotInsideSelect = document.getElementById('at_narrator_text_not_inside');
+ const generationMethodSelect = document.getElementById('at_generation_method');
+ this.settings.narrator_voice = this.settings.narrator_voice_gen;
+ // Apply settings to Narrator Voice dropdown
+ if (narratorVoiceSelect && this.settings.narrator_voice) {
+ narratorVoiceSelect.value = this.settings.narrator_voice; // Remove the parentheses
+ }
+ // Apply settings to AT Narrator Enabled dropdown
+ if (atNarratorSelect) {
+ const ttsPassAsterisksCheckbox = document.getElementById('tts_pass_asterisks');
+ const ttsNarrateQuotedCheckbox = document.getElementById('tts_narrate_quoted');
+ const ttsNarrateDialoguesCheckbox = document.getElementById('tts_narrate_dialogues');
+ if (this.settings.narrator_enabled) {
+ ttsPassAsterisksCheckbox.checked = false;
+ $('#tts_pass_asterisks').click();
+ $('#tts_pass_asterisks').trigger('change');
+ }
+ if (!this.settings.narrator_enabled) {
+ ttsPassAsterisksCheckbox.checked = true;
+ $('#tts_pass_asterisks').click();
+ $('#tts_pass_asterisks').trigger('change');
+ }
+ if (this.settings.narrator_enabled) {
+ ttsNarrateQuotedCheckbox.checked = true;
+ ttsNarrateDialoguesCheckbox.checked = true;
+ $('#tts_narrate_quoted').click();
+ $('#tts_narrate_quoted').trigger('change');
+ $('#tts_narrate_dialogues').click();
+ $('#tts_narrate_dialogues').trigger('change');
+ }
+ atNarratorSelect.value = this.settings.narrator_enabled.toString();
+ this.settings.narrator_enabled = this.settings.narrator_enabled.toString();
+ }
+ const languageSelect = document.getElementById('language_options');
+ if (languageSelect && this.settings.language) {
+ languageSelect.value = this.settings.language;
+ }
+ if (textNotInsideSelect && this.settings.text_not_inside) {
+ textNotInsideSelect.value = this.settings.text_not_inside;
+ this.settings.at_narrator_text_not_inside = this.settings.text_not_inside;
+ }
+ if (generationMethodSelect && this.settings.at_generation_method) {
+ generationMethodSelect.value = this.settings.at_generation_method;
+ }
+ const isStreamingEnabled = this.settings.at_generation_method === 'streaming_enabled';
+ if (isStreamingEnabled) {
+ if (atNarratorSelect) atNarratorSelect.disabled = true;
+ if (textNotInsideSelect) textNotInsideSelect.disabled = true;
+ if (narratorVoiceSelect) narratorVoiceSelect.disabled = true;
+ } else {
+ if (atNarratorSelect) atNarratorSelect.disabled = false;
+ if (textNotInsideSelect) textNotInsideSelect.disabled = !this.settings.narrator_enabled;
+ if (narratorVoiceSelect) narratorVoiceSelect.disabled = !this.settings.narrator_enabled;
+ }
+ }
+
+
+ //##############################//
+ // Check AT Server is Available //
+ //##############################//
+
+ async checkReady() {
+ try {
+ const response = await fetch(`${this.settings.provider_endpoint}/api/ready`);
+ // Check if the HTTP request was successful
+ if (!response.ok) {
+ throw new Error(`HTTP Error Response: ${response.status} ${response.statusText}`);
+ }
+ const statusText = await response.text();
+ // Check if the response is 'Ready'
+ if (statusText === 'Ready') {
+ this.ready = true; // Set the ready flag to true
+ console.log('TTS service is ready.');
+ } else {
+ this.ready = false;
+ console.log('TTS service is not ready.');
+ }
+ } catch (error) {
+ console.error('Error checking TTS service readiness:', error);
+ this.ready = false; // Ensure ready flag is set to false in case of error
+ }
+ }
+
+ //######################//
+ // Get Available Voices //
+ //######################//
+
+ async fetchTtsVoiceObjects() {
+ const response = await fetch(`${this.settings.provider_endpoint}/api/voices`);
+ if (!response.ok) {
+ const errorText = await response.text();
+ throw new Error(`HTTP ${response.status}: ${errorText}`);
+ }
+ const data = await response.json();
+ const voices = data.voices.map(filename => {
+ return {
+ name: filename,
+ voice_id: filename,
+ preview_url: null, // Preview URL will be dynamically generated
+ lang: 'en' // Default language
+ };
+ });
+ this.voices = voices; // Assign to the class property
+ return voices; // Also return this list
+ }
+
+ async fetchRvcVoiceObjects() {
+ console.log('Fetching RVC Voices');
+ try {
+ const response = await fetch(`${this.settings.provider_endpoint}/api/rvcvoices`);
+ if (!response.ok) {
+ const errorText = await response.text();
+ console.error('Error text:', errorText);
+ throw new Error(`HTTP ${response.status}: ${errorText}`);
+ }
+ const data = await response.json();
+ if (!data || !data.rvcvoices) {
+ console.error('Invalid data format:', data);
+ throw new Error('Invalid data format received from /api/rvcvoices');
+ }
+ const voices = data.rvcvoices.map(filename => {
+ return {
+ name: filename,
+ voice_id: filename,
+ };
+ });
+ console.log('RVC voices:', voices);
+ this.rvcVoices = voices; // Assign to the class property
+ return voices; // Also return this list
+ } catch (error) {
+ console.error('Error fetching RVC voices:', error);
+ throw error;
+ }
+ }
+
+
+
+ //##########################################//
+ // Get Current AT Server Config & Update ST //
+ //##########################################//
+
+ async updateSettingsFromServer() {
+ try {
+ const response = await fetch(`${this.settings.provider_endpoint}/api/currentsettings`);
+ if (!response.ok) {
+ throw new Error(`Failed to fetch current settings: ${response.statusText}`);
+ }
+ const currentSettings = await response.json();
+ currentSettings.models_available.sort((a, b) => a.name.localeCompare(b.name));
+
+ this.settings.enginesAvailable = currentSettings.engines_available;
+ this.settings.currentEngineLoaded = currentSettings.current_engine_loaded;
+ this.settings.modelsAvailable = currentSettings.models_available;
+ this.settings.currentModel = currentSettings.current_model_loaded;
+ this.settings.deepspeed_capable = currentSettings.deepspeed_capable;
+ this.settings.deepspeed_available = currentSettings.deepspeed_available;
+ this.settings.deepspeed_enabled = currentSettings.deepspeed_enabled;
+ this.settings.lowvram_capable = currentSettings.lowvram_capable;
+ this.settings.lowvram_enabled = currentSettings.lowvram_enabled;
+
+ await this.fetchRvcVoiceObjects(); // Fetch RVC voices
+
+ this.updateModelDropdown();
+ this.updateCheckboxes();
+ this.updateRvcVoiceDropdowns(); // Update the RVC voice dropdowns
+ } catch (error) {
+ console.error(`Error updating settings from server: ${error}`);
+ }
+ }
+
+ updateRvcVoiceDropdowns() {
+ const rvcCharacterVoiceSelect = document.getElementById('rvc_character_voice');
+ const rvcNarratorVoiceSelect = document.getElementById('rvc_narrator_voice');
+ if (rvcCharacterVoiceSelect && this.rvcVoices) {
+ rvcCharacterVoiceSelect.innerHTML = '';
+ for (let voice of this.rvcVoices) {
+ const option = document.createElement('option');
+ option.value = voice.voice_id;
+ option.textContent = voice.name;
+ rvcCharacterVoiceSelect.appendChild(option);
+ }
+ }
+ if (rvcNarratorVoiceSelect && this.rvcVoices) {
+ rvcNarratorVoiceSelect.innerHTML = '';
+ for (let voice of this.rvcVoices) {
+ const option = document.createElement('option');
+ option.value = voice.voice_id;
+ option.textContent = voice.name;
+ rvcNarratorVoiceSelect.appendChild(option);
+ }
+ }
+ }
+
+ //###################################################//
+ // Get Current AT Server Config & Update ST (Models) //
+ //###################################################//
+
+ updateModelDropdown() {
+ const modelSelect = document.getElementById('switch_model');
+ if (modelSelect) {
+ modelSelect.innerHTML = ''; // Clear existing options
+ this.settings.modelsAvailable.forEach(model => {
+ const option = document.createElement('option');
+ option.value = model.name;
+ option.textContent = model.name; // Use model name directly
+ option.selected = model.name === this.settings.currentModel;
+ modelSelect.appendChild(option);
+ });
+ }
+ }
+
+ //#######################################################//
+ // Get Current AT Server Config & Update ST (DS and LVR) //
+ //#######################################################//
+
+ updateCheckboxes() {
+ const deepspeedCheckbox = document.getElementById('deepspeed');
+ const lowVramCheckbox = document.getElementById('low_vram');
+
+ // Handle DeepSpeed checkbox
+ if (deepspeedCheckbox) {
+ if (this.settings.deepspeed_capable) {
+ // If TTS engine is capable of using DeepSpeed
+ deepspeedCheckbox.disabled = !this.settings.deepspeed_available;
+ this.settings.deepspeed_enabled = this.settings.deepspeed_available && this.settings.deepspeed_enabled;
+ } else {
+ // If TTS engine is NOT capable of using DeepSpeed
+ deepspeedCheckbox.disabled = true;
+ this.settings.deepspeed_enabled = false;
+ }
+ deepspeedCheckbox.checked = this.settings.deepspeed_enabled;
+ }
+
+ // Handle Low VRAM checkbox
+ if (lowVramCheckbox) {
+ if (this.settings.lowvram_capable) {
+ // If TTS engine is capable of low VRAM
+ lowVramCheckbox.disabled = false;
+ } else {
+ // If TTS engine is NOT capable of low VRAM
+ lowVramCheckbox.disabled = true;
+ this.settings.lowvram_enabled = false;
+ }
+ lowVramCheckbox.checked = this.settings.lowvram_enabled;
+ }
+ }
+
+
+ //###############################################################//
+ // Get Current AT Server Config & Update ST (AT Narrator Voices) //
+ //###############################################################//
+
+ updateNarratorVoicesDropdown() {
+ const narratorVoiceSelect = document.getElementById('narrator_voice');
+ if (narratorVoiceSelect && this.voices) {
+ // Clear existing options
+ narratorVoiceSelect.innerHTML = '';
+ // Add new options
+ for (let voice of this.voices) {
+ const option = document.createElement('option');
+ option.value = voice.voice_id;
+ option.textContent = voice.name;
+ narratorVoiceSelect.appendChild(option);
+ }
+ }
+ }
+
+ //######################################################//
+ // Get Current AT Server Config & Update ST (Languages) //
+ //######################################################//
+
+ updateLanguageDropdown() {
+ const languageSelect = document.getElementById('language_options');
+ if (languageSelect) {
+ // Ensure default language is set
+ this.settings.language = this.settings.language;
+
+ languageSelect.innerHTML = '';
+ for (let language in this.languageLabels) {
+ const option = document.createElement('option');
+ option.value = this.languageLabels[language];
+ option.textContent = language;
+ if (this.languageLabels[language] === this.settings.language) {
+ option.selected = true;
+ }
+ languageSelect.appendChild(option);
+ }
+ }
+ }
+
+ //########################################//
+ // Start AT TTS extenstion page listeners //
+ //########################################//
+
+ setupEventListeners() {
+
+ let debounceTimeout;
+ const debounceDelay = 1400; // Milliseconds
+
+ // Define the event handler function
+ const onModelSelectChange = async (event) => {
+ console.log("Model select change event triggered"); // Debugging statement
+ const selectedModel = event.target.value;
+ console.log(`Selected model: ${selectedModel}`); // Debugging statement
+ // Set status to Processing
+ updateStatus('Processing');
+ try {
+ const response = await fetch(`${this.settings.provider_endpoint}/api/reload?tts_method=${encodeURIComponent(selectedModel)}`, {
+ method: 'POST'
+ });
+ if (!response.ok) {
+ throw new Error(`HTTP Error: ${response.status}`);
+ }
+ const data = await response.json();
+ console.log("POST response data:", data); // Debugging statement
+ // Set status to Ready if successful
+ updateStatus('Ready');
+ } catch (error) {
+ console.error("POST request error:", error); // Debugging statement
+ // Set status to Error in case of failure
+ updateStatus('Error');
+ }
+
+ // Handle response or error
+ };
+
+
+ const rvcCharacterVoiceSelect = document.getElementById('rvc_character_voice');
+ if (rvcCharacterVoiceSelect) {
+ rvcCharacterVoiceSelect.addEventListener('change', (event) => {
+ this.settings.rvccharacter_voice_gen = event.target.value;
+ this.onSettingsChange(); // Save the settings after change
+ });
+ }
+
+ const rvcNarratorVoiceSelect = document.getElementById('rvc_narrator_voice');
+ if (rvcNarratorVoiceSelect) {
+ rvcNarratorVoiceSelect.addEventListener('change', (event) => {
+ this.settings.rvcnarrator_voice_gen = event.target.value;
+ this.onSettingsChange(); // Save the settings after change
+ });
+ }
+
+ const debouncedModelSelectChange = (event) => {
+ clearTimeout(debounceTimeout);
+ debounceTimeout = setTimeout(() => {
+ onModelSelectChange(event);
+ }, debounceDelay);
+ };
+
+ // Switch Model Listener
+ const modelSelect = document.getElementById('switch_model');
+ if (modelSelect) {
+ // Remove the event listener if it was previously added
+ modelSelect.removeEventListener('change', debouncedModelSelectChange);
+ // Add the debounced event listener
+ modelSelect.addEventListener('change', debouncedModelSelectChange);
+ }
+
+ // DeepSpeed Listener
+ const deepspeedCheckbox = document.getElementById('deepspeed');
+ if (deepspeedCheckbox) {
+ deepspeedCheckbox.addEventListener('change', async (event) => {
+ const deepSpeedValue = event.target.checked ? 'True' : 'False';
+ // Set status to Processing
+ updateStatus('Processing');
+ try {
+ const response = await fetch(`${this.settings.provider_endpoint}/api/deepspeed?new_deepspeed_value=${deepSpeedValue}`, {
+ method: 'POST'
+ });
+ if (!response.ok) {
+ throw new Error(`HTTP Error: ${response.status}`);
+ }
+ const data = await response.json();
+ console.log("POST response data:", data); // Debugging statement
+ // Set status to Ready if successful
+ updateStatus('Ready');
+ } catch (error) {
+ console.error("POST request error:", error); // Debugging statement
+ // Set status to Error in case of failure
+ updateStatus('Error');
+ }
+ });
+ }
+
+ function lowvramdebounce(func, delay) {
+ let debounceTimer;
+ return function (...args) {
+ const context = this;
+ clearTimeout(debounceTimer);
+ debounceTimer = setTimeout(() => func.apply(context, args), delay);
+ };
+ }
+
+ // Low VRAM Listener
+ const lowVramCheckbox = document.getElementById('low_vram');
+ if (lowVramCheckbox) {
+ const handleLowVramChange = async (event) => {
+ const lowVramValue = event.target.checked ? 'True' : 'False';
+ // Set status to Processing
+ updateStatus('Processing');
+ try {
+ const response = await fetch(`${this.settings.provider_endpoint}/api/lowvramsetting?new_low_vram_value=${lowVramValue}`, {
+ method: 'POST'
+ });
+ if (!response.ok) {
+ throw new Error(`HTTP Error: ${response.status}`);
+ }
+ const data = await response.json();
+ console.log("POST response data:", data); // Debugging statement
+ // Set status to Ready if successful
+ updateStatus('Ready');
+ } catch (error) {
+ console.error("POST request error:", error); // Debugging statement
+ // Set status to Error in case of failure
+ updateStatus('Error');
+ }
+ };
+
+ const debouncedHandleLowVramChange = lowvramdebounce(handleLowVramChange, 300); // Adjust delay as needed
+
+ lowVramCheckbox.addEventListener('change', debouncedHandleLowVramChange);
+ }
+
+
+ // Narrator Voice Dropdown Listener
+ const narratorVoiceSelect = document.getElementById('narrator_voice');
+ if (narratorVoiceSelect) {
+ narratorVoiceSelect.addEventListener('change', (event) => {
+ this.settings.narrator_voice_gen = `${event.target.value}`;
+ this.onSettingsChange(); // Save the settings after change
+ });
+ }
+
+ const textNotInsideSelect = document.getElementById('at_narrator_text_not_inside');
+ if (textNotInsideSelect) {
+ textNotInsideSelect.addEventListener('change', (event) => {
+ this.settings.text_not_inside = event.target.value;
+ this.onSettingsChange(); // Save the settings after change
+ });
+ }
+
+ // AT Narrator Dropdown Listener
+ const atNarratorSelect = document.getElementById('at_narrator_enabled');
+ const ttsPassAsterisksCheckbox = document.getElementById('tts_pass_asterisks');
+ const ttsNarrateQuotedCheckbox = document.getElementById('tts_narrate_quoted');
+ const ttsNarrateDialoguesCheckbox = document.getElementById('tts_narrate_dialogues');
+
+ if (atNarratorSelect && textNotInsideSelect && narratorVoiceSelect) {
+ atNarratorSelect.addEventListener('change', (event) => {
+ const narratorOption = event.target.value;
+ this.settings.narrator_enabled = narratorOption;
+
+ // Check if narrator is disabled
+ const isNarratorDisabled = narratorOption === 'false';
+ textNotInsideSelect.disabled = isNarratorDisabled;
+ narratorVoiceSelect.disabled = isNarratorDisabled;
+
+ console.log(`Narrator option: ${narratorOption}`);
+ console.log(`textNotInsideSelect disabled: ${textNotInsideSelect.disabled}`);
+ console.log(`narratorVoiceSelect disabled: ${narratorVoiceSelect.disabled}`);
+
+ if (narratorOption === 'true') {
+ ttsPassAsterisksCheckbox.checked = false;
+ $('#tts_pass_asterisks').click();
+ $('#tts_pass_asterisks').trigger('change');
+ ttsNarrateQuotedCheckbox.checked = true;
+ ttsNarrateDialoguesCheckbox.checked = true;
+ $('#tts_narrate_quoted').click();
+ $('#tts_narrate_quoted').trigger('change');
+ $('#tts_narrate_dialogues').click();
+ $('#tts_narrate_dialogues').trigger('change');
+ } else if (narratorOption === 'silent') {
+ ttsPassAsterisksCheckbox.checked = false;
+ $('#tts_pass_asterisks').click();
+ $('#tts_pass_asterisks').trigger('change');
+ } else {
+ ttsPassAsterisksCheckbox.checked = true;
+ $('#tts_pass_asterisks').click();
+ $('#tts_pass_asterisks').trigger('change');
+ }
+
+ this.onSettingsChange();
+ });
+ }
+
+
+ // Event Listener for AT Generation Method Dropdown
+ const atGenerationMethodSelect = document.getElementById('at_generation_method');
+ const atNarratorEnabledSelect = document.getElementById('at_narrator_enabled');
+ if (atGenerationMethodSelect) {
+ atGenerationMethodSelect.addEventListener('change', (event) => {
+ const selectedMethod = event.target.value;
+
+ if (selectedMethod === 'streaming_enabled') {
+ // Disable and unselect AT Narrator
+ atNarratorEnabledSelect.disabled = true;
+ atNarratorEnabledSelect.value = 'false';
+ textNotInsideSelect.disabled = true;
+ narratorVoiceSelect.disabled = true;
+ } else if (selectedMethod === 'standard_generation') {
+ // Enable AT Narrator
+ atNarratorEnabledSelect.disabled = false;
+ }
+ this.settings.at_generation_method = selectedMethod; // Update the setting here
+ this.onSettingsChange(); // Save the settings after change
+ });
+ }
+
+ // Listener for Language Dropdown
+ const languageSelect = document.getElementById('language_options');
+ if (languageSelect) {
+ languageSelect.addEventListener('change', (event) => {
+ this.settings.language = event.target.value;
+ this.onSettingsChange(); // Save the settings after change
+ });
+ }
+
+ // Listener for AllTalk Endpoint Input
+ const atServerInput = document.getElementById('at_server');
+ if (atServerInput) {
+ atServerInput.addEventListener('input', (event) => {
+ this.settings.provider_endpoint = event.target.value;
+ this.onSettingsChange(); // Save the settings after change
+ });
+ }
+
+ }
+
+ //#############################//
+ // Store ST interface settings //
+ //#############################//
+
+ onSettingsChange() {
+ // Update settings based on the UI elements
+ //this.settings.provider_endpoint = $('#at_server').val();
+ this.settings.language = $('#language_options').val();
+ //this.settings.voiceMap = $('#voicemap').val();
+ this.settings.at_generation_method = $('#at_generation_method').val();
+ this.settings.narrator_enabled = $('#at_narrator_enabled').val();
+ this.settings.at_narrator_text_not_inside = $('#at_narrator_text_not_inside').val();
+ this.settings.rvc_character_voice = $('#rvc_character_voice').val();
+ this.settings.rvc_narrator_voice = $('#rvc_narrator_voice').val();
+ this.settings.narrator_voice_gen = $('#narrator_voice').val();
+ // Save the updated settings
+ saveTtsProviderSettings();
+ }
+
+ //#########################//
+ // ST Handle Reload button //
+ //#########################//
+
+ async onRefreshClick() {
+ try {
+ updateStatus('Processing'); // Set status to Processing while refreshing
+ await this.checkReady(); // Check if the TTS provider is ready
+ await this.loadSettings(this.settings); // Reload the settings
+ await this.checkReady(); // Check if the TTS provider is ready
+ updateStatus(this.ready ? 'Ready' : 'Offline'); // Update the status based on readiness
+ } catch (error) {
+ console.error('Error during refresh:', error);
+ updateStatus('Error'); // Set status to Error in case of failure
+ }
+ }
+
+ //##################//
+ // Preview AT Voice //
+ //##################//
+
+ async previewTtsVoice(voiceName) {
+ try {
+ // Prepare data for POST request
+ const postData = new URLSearchParams();
+ postData.append("voice", `${voiceName}`);
+ // Making the POST request
+ const response = await fetch(`${this.settings.provider_endpoint}/api/previewvoice/`, {
+ method: "POST",
+ headers: {
+ 'Content-Type': 'application/x-www-form-urlencoded'
+ },
+ body: postData,
+ });
+ if (!response.ok) {
+ const errorText = await response.text();
+ console.error(`[previewTtsVoice] Error Response Text:`, errorText);
+ throw new Error(`HTTP ${response.status}: ${errorText}`);
+ }
+ // Assuming the server returns a URL to the .wav file
+ const data = await response.json();
+ if (data.output_file_url) {
+ // Prepend the provider endpoint to the output file URL
+ const fullUrl = `${this.settings.provider_endpoint}${data.output_file_url}`;
+ // Use an audio element to play the .wav file
+ const audioElement = new Audio(fullUrl);
+ audioElement.play().catch(e => console.error("Error playing audio:", e));
+ } else {
+ console.warn("[previewTtsVoice] No output file URL received in the response");
+ throw new Error("No output file URL received in the response");
+ }
+
+ } catch (error) {
+ console.error("[previewTtsVoice] Exception caught during preview generation:", error);
+ throw error;
+ }
+ }
+
+ //#####################//
+ // Populate ST voices //
+ //#####################//
+
+ async getVoice(voiceName, generatePreview = false) {
+ // Ensure this.voices is populated
+ if (this.voices.length === 0) {
+ // Fetch voice objects logic
+ }
+ // Find the object where the name matches voiceName
+ const match = this.voices.find(voice => voice.name === voiceName);
+ if (!match) {
+ // Error handling
+ }
+ // Generate preview URL only if requested
+ if (!match.preview_url && generatePreview) {
+ // Generate preview logic
+ }
+ return match; // Return the found voice object
+ }
+
+ //##########################################//
+ // Generate TTS Streaming or call Standard //
+ //##########################################//
+
+ async generateTts(inputText, voiceId) {
+ try {
+ if (this.settings.at_generation_method === 'streaming_enabled') {
+ // Construct the streaming URL
+ const streamingUrl = `${this.settings.provider_endpoint}/api/tts-generate-streaming?text=${encodeURIComponent(inputText)}&voice=${encodeURIComponent(voiceId)}&language=${encodeURIComponent(this.settings.language)}&output_file=stream_output.wav`;
+ console.log("Streaming URL:", streamingUrl);
+
+ // Return the streaming URL directly
+ return streamingUrl;
+ } else {
+ // For standard method
+ const outputUrl = await this.fetchTtsGeneration(inputText, voiceId);
+ const audioResponse = await fetch(outputUrl);
+ if (!audioResponse.ok) {
+ throw new Error(`HTTP ${audioResponse.status}: Failed to fetch audio data`);
+ }
+ return audioResponse; // Return the fetch response directly
+ }
+ } catch (error) {
+ console.error("Error in generateTts:", error);
+ throw error;
+ }
+ }
+
+
+ //####################//
+ // Generate Standard //
+ //####################//
+
+ async fetchTtsGeneration(inputText, voiceId) {
+ const requestBody = new URLSearchParams({
+ 'text_input': inputText,
+ 'text_filtering': "standard",
+ 'character_voice_gen': voiceId,
+ 'rvccharacter_voice_gen': this.settings.rvccharacter_voice_gen || "Disabled",
+ 'narrator_enabled': this.settings.narrator_enabled,
+ 'narrator_voice_gen': this.settings.narrator_voice_gen,
+ 'rvcnarrator_voice_gen': this.settings.rvcnarrator_voice_gen || "Disabled",
+ 'text_not_inside': this.settings.at_narrator_text_not_inside,
+ 'language': this.settings.language,
+ 'output_file_name': "st_output",
+ 'output_file_timestamp': "true",
+ 'autoplay': "false",
+ 'autoplay_volume': "0.8"
+ }).toString();
+
+ try {
+ const response = await doExtrasFetch(
+ `${this.settings.provider_endpoint}/api/tts-generate`,
+ {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/x-www-form-urlencoded',
+ 'Cache-Control': 'no-cache',
+ },
+ body: requestBody
+ }
+ );
+
+ if (!response.ok) {
+ const errorText = await response.text();
+ console.error(`[fetchTtsGeneration] Error Response Text:`, errorText);
+ throw new Error(`HTTP ${response.status}: ${errorText}`);
+ }
+ const data = await response.json();
+ const outputUrl = `${this.settings.provider_endpoint}${data.output_file_url}`;
+ return outputUrl; // Return only the output_file_url
+ } catch (error) {
+ console.error("[fetchTtsGeneration] Exception caught:", error);
+ throw error; // Rethrow the error for further handling
+ }
+ }
+}
+
+//#########################//
+// Update Status Messages //
+//#########################//
+
+function updateStatus(message) {
+ const statusElement = document.getElementById('status_info');
+ if (statusElement) {
+ statusElement.textContent = message;
+ switch (message) {
+ case 'Offline':
+ statusElement.style.color = 'red';
+ break;
+ case 'Ready':
+ statusElement.style.color = 'lightgreen';
+ break;
+ case 'Processing':
+ statusElement.style.color = 'blue';
+ break;
+ case 'Error':
+ statusElement.style.color = 'red';
+ break;
+ }
+ }
+}
From 57a6b3ba3936ecde7362303104c36de3f77afd28 Mon Sep 17 00:00:00 2001
From: erew123 <35898566+erew123@users.noreply.github.com>
Date: Tue, 10 Sep 2024 11:35:33 +0100
Subject: [PATCH 25/38] Add note & double setting firstrun false
Added an additional note to the first run splash screen to explain additional models/engines are managed in the gradio interface.
Doubled the effort to mark first time run has been completed.
---
system/config/firstrun.py | 376 +++++++++++++++++++-------------------
1 file changed, 190 insertions(+), 186 deletions(-)
diff --git a/system/config/firstrun.py b/system/config/firstrun.py
index ef19f30..59c18a4 100644
--- a/system/config/firstrun.py
+++ b/system/config/firstrun.py
@@ -1,186 +1,190 @@
-import json
-import os
-import time
-import shutil
-import zipfile
-import requests
-from tqdm import tqdm
-from inputimeout import inputimeout, TimeoutOccurred
-from pathlib import Path
-
-this_dir = Path(__file__).parent.resolve().parent.resolve().parent.resolve()
-# File paths
-config_path = this_dir / 'confignew.json'
-tts_engines_path = this_dir / 'system/tts_engines/tts_engines.json'
-new_config_template_path = this_dir / 'system/config/confignew.json'
-
-def download_file(url, dest_path):
- response = requests.get(url, stream=True)
- total_size = int(response.headers.get('content-length', 0))
- block_size = 1024 # 1 Kibibyte
- t = tqdm(total=total_size, unit='iB', unit_scale=True, desc=str(dest_path))
- with open(dest_path, 'wb') as file:
- for data in response.iter_content(block_size):
- t.update(len(data))
- file.write(data)
- t.close()
-
-def setup_piper():
- os.makedirs(this_dir / 'models/piper', exist_ok=True)
- os.makedirs(this_dir / 'models/xtts', exist_ok=True)
- download_file("https://huggingface.co/rhasspy/piper-voices/resolve/v1.0.0/en/en_US/ljspeech/high/en_US-ljspeech-high.onnx?download=true",
- this_dir / "models/piper/en_US-ljspeech-high.onnx")
- download_file("https://huggingface.co/rhasspy/piper-voices/resolve/v1.0.0/en/en_US/ljspeech/high/en_US-ljspeech-high.onnx.json?download=true.json",
- this_dir / "models/piper/en_US-ljspeech-high.onnx.json")
-
-def setup_vits():
- os.makedirs(this_dir / 'models/vits', exist_ok=True)
- os.makedirs(this_dir / 'models/xtts', exist_ok=True)
- zip_path = this_dir / 'models/vits/vits.zip'
- download_file("https://coqui.gateway.scarf.sh/v0.6.1_models/tts_models--en--vctk--vits.zip", zip_path)
- with zipfile.ZipFile(zip_path, 'r') as zip_ref:
- zip_ref.extractall(this_dir / 'models/vits')
- os.remove(zip_path)
-
-def setup_xtts():
- os.makedirs(this_dir / 'models/xtts/xttsv2_2.0.3', exist_ok=True)
- base_url = "https://huggingface.co/coqui/XTTS-v2/resolve/v2.0.3/"
- files = [
- "LICENSE.txt",
- "README.md",
- "config.json",
- "model.pth",
- "dvae.pth",
- "mel_stats.pth",
- "speakers_xtts.pth",
- "vocab.json"
- ]
- for file in files:
- download_file(base_url + file + "?download=true", this_dir / f"models/xtts/xttsv2_2.0.3/{file}")
-
-
-
-def update_tts_engines(engine, model):
- with open(tts_engines_path, 'r') as file:
- tts_engines = json.load(file)
-
- tts_engines['engine_loaded'] = engine
- tts_engines['selected_model'] = model
-
- with open(tts_engines_path, 'w') as file:
- json.dump(tts_engines, file, indent=4)
-
-def set_firstrun_model_false():
- with open(config_path, 'r') as file:
- config = json.load(file)
-
- config['firstrun_model'] = False
-
- with open(config_path, 'w') as file:
- json.dump(config, file, indent=4)
-
-def load_config(file_path):
- with open(file_path, "r") as config_file:
- config = json.load(config_file)
- return config
-
-def warning_mesasge():
- print(f"[{branding}TTS]")
- print(f"[{branding}TTS] \033[93mIf you have you have UPGRADED from v1 ensure you have re-installed\033[0m")
- print(f"[{branding}TTS] \033[93mthe requirements. Otherwise you will get failures and errors!\033[0m")
- print(f"[{branding}TTS] \033[93mOn Linux ignore the \033[0m'sparse_attn requires a torch version' \033[93mand\033[0m")
- print(f"[{branding}TTS] \033[0m'using untested triton version' \033[93mmessages.\033[0m")
- print(f"[{branding}TTS]")
-
-# Load confignew.json
-with open(config_path, 'r') as file:
- config = json.load(file)
-
-branding = config['branding']
-
-# Check if the config file is version 1
-if "ip_address" in config:
- print(f"[{branding}TTS] Detected version 1 configuration file.")
- print(f"[{branding}TTS] Upgrading to version 2 configuration file...")
-
- # Backup the old configuration file
- backup_path = config_path + ".backup"
- shutil.copyfile(config_path, backup_path)
-
- # Copy the new configuration template to replace the old config file
- shutil.copyfile(new_config_template_path, config_path)
-
- print(f"[{branding}TTS] Configuration file upgraded successfully.")
-
- # Reload the new config file
- with open(config_path, 'r') as file:
- config = json.load(file)
-
-# Check if firstrun_model is true
-if config.get('firstrun_model', False):
- print(f"[{branding}TTS]")
- print(f"[{branding}TTS] \033[92mThis is the first time startup. Please download a start TTS model.\033[0m")
- print(f"[{branding}TTS]")
-
- # List of available models
- models = [
- {"name": "piper", "model": "piper"},
- {"name": "vits", "model": "tts_models--en--vctk--vits"},
- {"name": "xtts", "model": "xttsv2_2.0.3"},
- ]
-
- # Display models to the user
- print(f"[{branding}TTS] \033[94mAvailable First Time Start-up models:\033[0m")
- print(f"[{branding}TTS]")
- for idx, model in enumerate(models):
- print(f"[{branding}TTS] \033[93m{idx + 1}. \033[94m{model['name']} - {model['model']}\033[0m")
- print(f"[{branding}TTS] \033[93m{len(models) + 1}.\033[94m I have my own models already\033[0m")
- print(f"[{branding}TTS]")
- print(f"[{branding}TTS] \033[94mIn \033[91m60 seconds\033[0m \033[94ma Piper model will be \033[91mdownloaded automatically.\033[0m")
- print(f"[{branding}TTS]")
-
- # Auto-select model after 30 seconds
- selected_model = models[0] # Default to the first model (piper)
- try:
- user_choice = inputimeout(prompt=f"[{branding}TTS] \033[92mEnter your choice 1-4: \033[0m ", timeout=60)
- except TimeoutOccurred:
- user_choice = None
-
- if user_choice is None:
- print(f"[{branding}TTS]")
- print(f"[{branding}TTS] No input received. Proceeding with the default model (piper).")
- elif user_choice.lower() == 'own' or (user_choice.isdigit() and int(user_choice) == len(models) + 1):
- print(f"[{branding}TTS]")
- print(f"[{branding}TTS] If you have your own XTTS models, please move them into the")
- print(f"[{branding}TTS] \033[93m/models/xtts/\033[0m folder")
- print(f"[{branding}TTS]")
- os.makedirs('models/piper', exist_ok=True)
- os.makedirs('models/vits', exist_ok=True)
- os.makedirs('models/xtts/', exist_ok=True)
- update_tts_engines('piper', 'piper')
- set_firstrun_model_false()
- warning_mesasge()
- exit()
-
- if user_choice is not None and user_choice.isdigit() and 1 <= int(user_choice) <= len(models):
- selected_model = models[int(user_choice) - 1]
- warning_mesasge()
-
- if user_choice is None or user_choice.lower() != 'own':
- if selected_model['name'] == 'piper':
- setup_piper()
- update_tts_engines('piper', 'piper')
- elif selected_model['name'] == 'vits':
- setup_vits()
- update_tts_engines('vits', 'vits - tts_models--en--vctk--vits')
- elif selected_model['name'] == 'xtts':
- setup_xtts()
- update_tts_engines('xtts', 'xtts - xttsv2_2.0.3')
-
- print(f"[{branding}TTS] {selected_model['name']} model downloaded and configuration updated successfully.")
- # Set firstrun_model to false
- set_firstrun_model_false()
- warning_mesasge()
- exit()
-else:
- exit()
+import json
+import os
+import time
+import shutil
+import zipfile
+import requests
+from tqdm import tqdm
+from inputimeout import inputimeout, TimeoutOccurred
+from pathlib import Path
+
+this_dir = Path(__file__).parent.resolve().parent.resolve().parent.resolve()
+# File paths
+config_path = this_dir / 'confignew.json'
+tts_engines_path = this_dir / 'system/tts_engines/tts_engines.json'
+new_config_template_path = this_dir / 'system/config/confignew.json'
+
+def download_file(url, dest_path):
+ response = requests.get(url, stream=True)
+ total_size = int(response.headers.get('content-length', 0))
+ block_size = 1024 # 1 Kibibyte
+ t = tqdm(total=total_size, unit='iB', unit_scale=True, desc=str(dest_path))
+ with open(dest_path, 'wb') as file:
+ for data in response.iter_content(block_size):
+ t.update(len(data))
+ file.write(data)
+ t.close()
+
+def setup_piper():
+ os.makedirs(this_dir / 'models/piper', exist_ok=True)
+ os.makedirs(this_dir / 'models/xtts', exist_ok=True)
+ download_file("https://huggingface.co/rhasspy/piper-voices/resolve/v1.0.0/en/en_US/ljspeech/high/en_US-ljspeech-high.onnx?download=true",
+ this_dir / "models/piper/en_US-ljspeech-high.onnx")
+ download_file("https://huggingface.co/rhasspy/piper-voices/resolve/v1.0.0/en/en_US/ljspeech/high/en_US-ljspeech-high.onnx.json?download=true.json",
+ this_dir / "models/piper/en_US-ljspeech-high.onnx.json")
+
+def setup_vits():
+ os.makedirs(this_dir / 'models/vits', exist_ok=True)
+ os.makedirs(this_dir / 'models/xtts', exist_ok=True)
+ zip_path = this_dir / 'models/vits/vits.zip'
+ download_file("https://coqui.gateway.scarf.sh/v0.6.1_models/tts_models--en--vctk--vits.zip", zip_path)
+ with zipfile.ZipFile(zip_path, 'r') as zip_ref:
+ zip_ref.extractall(this_dir / 'models/vits')
+ os.remove(zip_path)
+
+def setup_xtts():
+ os.makedirs(this_dir / 'models/xtts/xttsv2_2.0.3', exist_ok=True)
+ base_url = "https://huggingface.co/coqui/XTTS-v2/resolve/v2.0.3/"
+ files = [
+ "LICENSE.txt",
+ "README.md",
+ "config.json",
+ "model.pth",
+ "dvae.pth",
+ "mel_stats.pth",
+ "speakers_xtts.pth",
+ "vocab.json"
+ ]
+ for file in files:
+ download_file(base_url + file + "?download=true", this_dir / f"models/xtts/xttsv2_2.0.3/{file}")
+
+
+
+def update_tts_engines(engine, model):
+ with open(tts_engines_path, 'r') as file:
+ tts_engines = json.load(file)
+
+ tts_engines['engine_loaded'] = engine
+ tts_engines['selected_model'] = model
+
+ with open(tts_engines_path, 'w') as file:
+ json.dump(tts_engines, file, indent=4)
+
+def set_firstrun_model_false():
+ with open(config_path, 'r') as file:
+ config = json.load(file)
+
+ config['firstrun_model'] = False
+
+ with open(config_path, 'w') as file:
+ json.dump(config, file, indent=4)
+
+def load_config(file_path):
+ with open(file_path, "r") as config_file:
+ config = json.load(config_file)
+ return config
+
+def warning_mesasge():
+ print(f"[{branding}TTS]")
+ print(f"[{branding}TTS] \033[93mIf you have you have UPGRADED from v1 ensure you have re-installed\033[0m")
+ print(f"[{branding}TTS] \033[93mthe requirements. Otherwise you will get failures and errors!\033[0m")
+ print(f"[{branding}TTS] \033[93mOn Linux ignore the \033[0m'sparse_attn requires a torch version' \033[93mand\033[0m")
+ print(f"[{branding}TTS] \033[0m'using untested triton version' \033[93mmessages.\033[0m")
+ print(f"[{branding}TTS]")
+
+# Load confignew.json
+with open(config_path, 'r') as file:
+ config = json.load(file)
+
+branding = config['branding']
+
+# Check if the config file is version 1
+if "ip_address" in config:
+ print(f"[{branding}TTS] Detected version 1 configuration file.")
+ print(f"[{branding}TTS] Upgrading to version 2 configuration file...")
+
+ # Backup the old configuration file
+ backup_path = config_path + ".backup"
+ shutil.copyfile(config_path, backup_path)
+
+ # Copy the new configuration template to replace the old config file
+ shutil.copyfile(new_config_template_path, config_path)
+
+ print(f"[{branding}TTS] Configuration file upgraded successfully.")
+
+ # Reload the new config file
+ with open(config_path, 'r') as file:
+ config = json.load(file)
+
+# Check if firstrun_model is true
+if config.get('firstrun_model', False):
+ print(f"[{branding}TTS]")
+ print(f"[{branding}TTS] \033[92mThis is the first time startup. Please download a start TTS model. Other TTS engines\033[0m")
+ print(f"[{branding}TTS] \033[92mand TTS models can be downloaded/managed in the Gradio Interface `TTS Engines Settings`\033[0m")
+ print(f"[{branding}TTS] \033[92mtab after inital setup.\033[0m")
+ print(f"[{branding}TTS]")
+
+ # List of available models
+ models = [
+ {"name": "piper", "model": "piper"},
+ {"name": "vits", "model": "tts_models--en--vctk--vits"},
+ {"name": "xtts", "model": "xttsv2_2.0.3"},
+ ]
+
+ # Display models to the user
+ print(f"[{branding}TTS] \033[94mAvailable First Time Start-up models:\033[0m")
+ print(f"[{branding}TTS]")
+ for idx, model in enumerate(models):
+ print(f"[{branding}TTS] \033[93m{idx + 1}. \033[94m{model['name']} - {model['model']}\033[0m")
+ print(f"[{branding}TTS] \033[93m{len(models) + 1}.\033[94m I have my own models already\033[0m")
+ print(f"[{branding}TTS]")
+ print(f"[{branding}TTS] \033[94mIn \033[91m60 seconds\033[0m \033[94ma Piper model will be \033[91mdownloaded automatically.\033[0m")
+ print(f"[{branding}TTS]")
+
+ # Auto-select model after 30 seconds
+ selected_model = models[0] # Default to the first model (piper)
+ try:
+ user_choice = inputimeout(prompt=f"[{branding}TTS] \033[92mEnter your choice 1-4: \033[0m ", timeout=60)
+ except TimeoutOccurred:
+ user_choice = None
+
+ if user_choice is None:
+ print(f"[{branding}TTS]")
+ print(f"[{branding}TTS] No input received. Proceeding with the default model (piper).")
+ elif user_choice.lower() == 'own' or (user_choice.isdigit() and int(user_choice) == len(models) + 1):
+ print(f"[{branding}TTS]")
+ print(f"[{branding}TTS] If you have your own XTTS models, please move them into the")
+ print(f"[{branding}TTS] \033[93m/models/xtts/\033[0m folder")
+ print(f"[{branding}TTS]")
+ os.makedirs('models/piper', exist_ok=True)
+ os.makedirs('models/vits', exist_ok=True)
+ os.makedirs('models/xtts/', exist_ok=True)
+ update_tts_engines('piper', 'piper')
+ set_firstrun_model_false()
+ warning_mesasge()
+ exit()
+
+ if user_choice is not None and user_choice.isdigit() and 1 <= int(user_choice) <= len(models):
+ selected_model = models[int(user_choice) - 1]
+ warning_mesasge()
+
+ if user_choice is None or user_choice.lower() != 'own':
+ if selected_model['name'] == 'piper':
+ setup_piper()
+ update_tts_engines('piper', 'piper')
+ set_firstrun_model_false()
+ elif selected_model['name'] == 'vits':
+ setup_vits()
+ update_tts_engines('vits', 'vits - tts_models--en--vctk--vits')
+ set_firstrun_model_false()
+ elif selected_model['name'] == 'xtts':
+ setup_xtts()
+ update_tts_engines('xtts', 'xtts - xttsv2_2.0.3')
+ set_firstrun_model_false()
+ print(f"[{branding}TTS] {selected_model['name']} model downloaded and configuration updated successfully.")
+ # Set firstrun_model to false
+ set_firstrun_model_false()
+ warning_mesasge()
+ exit()
+else:
+ exit()
From 8ae881e3b0f28bc46571254c57b717ad36e740e8 Mon Sep 17 00:00:00 2001
From: erew123 <35898566+erew123@users.noreply.github.com>
Date: Tue, 10 Sep 2024 12:40:43 +0100
Subject: [PATCH 26/38] Pushed back in Cutlett and Fugashi
---
system/requirements/requirements_colab.txt | 2 ++
system/requirements/requirements_standalone.txt | 2 ++
system/requirements/requirements_textgen.txt | 2 ++
3 files changed, 6 insertions(+)
diff --git a/system/requirements/requirements_colab.txt b/system/requirements/requirements_colab.txt
index fc9b8c6..d1c2db6 100644
--- a/system/requirements/requirements_colab.txt
+++ b/system/requirements/requirements_colab.txt
@@ -41,4 +41,6 @@ tomlkit
python-multipart
hangul_romanize
word2number
+cutlet==0.4.0
+fugashi==1.3.1
piper-tts; sys_platform == "linux"
diff --git a/system/requirements/requirements_standalone.txt b/system/requirements/requirements_standalone.txt
index 2aeb795..199b167 100644
--- a/system/requirements/requirements_standalone.txt
+++ b/system/requirements/requirements_standalone.txt
@@ -26,6 +26,8 @@ pillow==10.3.0
pypinyin>=0.52.0
hangul_romanize>=0.1.0
word2number>=1.1
+cutlet==0.4.0
+fugashi==1.3.1
./system/config/fairseq-0.12.4-cp311-cp311-win_amd64.whl; sys_platform == "win32"
./system/config/fairseq-0.12.3.1-cp311-cp311-linux_x86_64.whl; sys_platform == "linux"
piper-tts; sys_platform == "linux"
diff --git a/system/requirements/requirements_textgen.txt b/system/requirements/requirements_textgen.txt
index 3c44dc3..3203646 100644
--- a/system/requirements/requirements_textgen.txt
+++ b/system/requirements/requirements_textgen.txt
@@ -25,6 +25,8 @@ uvicorn>=0.29.0
pypinyin>=0.52.0
hangul_romanize>=0.1.0
word2number>=1.1
+cutlet==0.4.0
+fugashi==1.3.1
./system/config/fairseq-0.12.4-cp311-cp311-win_amd64.whl; sys_platform == "win32"
./system/config/fairseq-0.12.3.1-cp311-cp311-linux_x86_64.whl; sys_platform == "linux"
piper-tts; sys_platform == "linux"
From 3d9d10a4565328670474314ef306f1b7a53e999f Mon Sep 17 00:00:00 2001
From: erew123 <35898566+erew123@users.noreply.github.com>
Date: Fri, 13 Sep 2024 16:21:11 +0100
Subject: [PATCH 27/38] Set FastAPI to 0.112.2
Gradio is still having issues with later version of FastAPI
---
system/requirements/requirements_standalone.txt | 1 +
1 file changed, 1 insertion(+)
diff --git a/system/requirements/requirements_standalone.txt b/system/requirements/requirements_standalone.txt
index 199b167..1d67f87 100644
--- a/system/requirements/requirements_standalone.txt
+++ b/system/requirements/requirements_standalone.txt
@@ -33,3 +33,4 @@ fugashi==1.3.1
piper-tts; sys_platform == "linux"
piper-tts; sys_platform == "darwin"
piper-phonemize==1.1.0; sys_platform == "darwin"
+fastapi==0.112.2
From 7f98ef3905bd5df6abca532604fde5045585d526 Mon Sep 17 00:00:00 2001
From: erew123 <35898566+erew123@users.noreply.github.com>
Date: Fri, 13 Sep 2024 16:21:47 +0100
Subject: [PATCH 28/38] Update requirements_colab.txt
---
system/requirements/requirements_colab.txt | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/system/requirements/requirements_colab.txt b/system/requirements/requirements_colab.txt
index d1c2db6..e17bc5e 100644
--- a/system/requirements/requirements_colab.txt
+++ b/system/requirements/requirements_colab.txt
@@ -23,7 +23,7 @@ tqdm>=4.66.0
unidic-lite>=1.0.8
uvicorn>=0.29.0
pillow==10.3.0
-fastapi
+fastapi==0.112.2
starlette
pypinyin
pyee
From cf3635ea480b5782aba9f8dc78e1e5e21f70a817 Mon Sep 17 00:00:00 2001
From: GenericMale
Date: Fri, 13 Sep 2024 19:09:07 +0200
Subject: [PATCH 29/38] fix(openai-api): KeyError when response_format or speed
omitted
---
tts_server.py | 3376 ++++++++++++++++++++++++-------------------------
1 file changed, 1688 insertions(+), 1688 deletions(-)
diff --git a/tts_server.py b/tts_server.py
index d046e06..4f3d0ac 100644
--- a/tts_server.py
+++ b/tts_server.py
@@ -1,1688 +1,1688 @@
-import os
-import sys
-import json
-import time
-import librosa
-import logging
-import importlib
-import subprocess
-from pathlib import Path
-logging.disable(logging.WARNING)
-#####################
-# Webserver Imports #
-#####################
-from contextlib import asynccontextmanager
-from fastapi.staticfiles import StaticFiles
-from fastapi.templating import Jinja2Templates
-from fastapi.middleware.cors import CORSMiddleware
-from fastapi import FastAPI, Form, Request, Response, Depends, HTTPException, Query
-from fastapi.responses import JSONResponse, HTMLResponse, RedirectResponse, FileResponse, StreamingResponse
-##########################
-# Generation API Imports #
-##########################
-import re
-import html
-import uuid
-import hashlib
-import numpy as np
-import soundfile as sf
-from typing import Union, Dict, List
-from pydantic import BaseModel, ValidationError, Field, field_validator
-########################################################################################
-# START-UP # Silence RVC warning about torch.nn.utils.weight_norm even though not used #
-########################################################################################
-import warnings
-warnings.filterwarnings("ignore", category=UserWarning, module="torch.nn.utils.weight_norm")
-warnings.filterwarnings("ignore", category=UserWarning, module="torch.nn.functional", lineno=5476)
-####################
-# Setup local path #
-####################
-this_dir = Path(__file__).parent.resolve() # Set this_dir as the current alltalk_tts folder
-
-####################################################
-# Load params and api_defailts from confignew.json #
-####################################################
-def load_config():
- global branding, output_directory, debug_transcode, debug_tts, debug_tts_variables, engines_available, engine_loaded, debug_rvc, debug_concat, debug_openai
- # Define the path to the confignew.json file & load in api_defaults & params
- configfile_path = this_dir / "confignew.json"
- with open(configfile_path, "r") as configfile:
- configfile_data = json.load(configfile)
- api_defaults = {
- "api_text_filtering": configfile_data["api_def"]["api_text_filtering"],
- "api_narrator_enabled": configfile_data["api_def"]["api_narrator_enabled"],
- "api_text_not_inside": configfile_data["api_def"]["api_text_not_inside"],
- "api_language": configfile_data["api_def"]["api_language"],
- "api_output_file_name": configfile_data["api_def"]["api_output_file_name"],
- "api_output_file_timestamp": configfile_data["api_def"]["api_output_file_timestamp"],
- "api_autoplay": configfile_data["api_def"]["api_autoplay"],
- "api_autoplay_volume": configfile_data["api_def"]["api_autoplay_volume"],
- "api_port_number": configfile_data["api_def"]["api_port_number"],
- "api_allowed_filter": configfile_data["api_def"]["api_allowed_filter"],
- "api_length_stripping": configfile_data["api_def"]["api_length_stripping"],
- "api_max_characters": configfile_data["api_def"]["api_max_characters"],
- "api_use_legacy_api": configfile_data["api_def"]["api_use_legacy_api"],
- "api_legacy_ip_address": configfile_data["api_def"]["api_legacy_ip_address"],
- }
- rvc_settings = {
- "rvc_enabled": configfile_data["rvc_settings"]["rvc_enabled"],
- "rvc_char_model_file": configfile_data["rvc_settings"]["rvc_char_model_file"],
- "rvc_narr_model_file": configfile_data["rvc_settings"]["rvc_narr_model_file"],
- "split_audio": configfile_data["rvc_settings"]["split_audio"],
- "autotune": configfile_data["rvc_settings"]["autotune"],
- "pitch": configfile_data["rvc_settings"]["pitch"],
- "filter_radius": configfile_data["rvc_settings"]["filter_radius"],
- "index_rate": configfile_data["rvc_settings"]["index_rate"],
- "rms_mix_rate": configfile_data["rvc_settings"]["rms_mix_rate"],
- "protect": configfile_data["rvc_settings"]["protect"],
- "hop_length": configfile_data["rvc_settings"]["hop_length"],
- "f0method": configfile_data["rvc_settings"]["f0method"],
- "embedder_model": configfile_data["rvc_settings"]["embedder_model"],
- "training_data_size": configfile_data["rvc_settings"]["training_data_size"],
- }
- branding = configfile_data.get("branding", "")
- debug_transcode = configfile_data.get("debugging").get("debug_transcode")
- debug_tts = configfile_data.get("debugging").get("debug_tts")
- debug_tts_variables = configfile_data.get("debugging").get("debug_tts_variables")
- debug_rvc = configfile_data.get("debugging").get("debug_rvc")
- debug_concat = configfile_data.get("debugging").get("debug_concat")
- debug_openai = configfile_data.get("debugging").get("debug_openai")
- output_directory = this_dir / configfile_data.get("output_folder", "")
- output_directory.mkdir(parents=True, exist_ok=True)
- tts_engines_file = os.path.join(this_dir, "system", "tts_engines", "tts_engines.json")
- with open(tts_engines_file, "r") as f:
- tts_engines_data = json.load(f)
- engines_available = [engine["name"] for engine in tts_engines_data["engines_available"]] # List of the available TTS engines from tts_engines.json
- engine_loaded = tts_engines_data["engine_loaded"] # The currently set TTS engine from tts_engines.json
- selected_model = tts_engines_data["selected_model"]
- # Conditionally import infer_pipeline if rvc_enabled is true
- if rvc_settings["rvc_enabled"]:
- from system.tts_engines.rvc.infer.infer import infer_pipeline
- else:
- infer_pipeline = None
- return configfile_data, api_defaults, rvc_settings, infer_pipeline
-
-# Call to load in the params and api_defaults
-params, api_defaults, rvc_settings, infer_pipeline = load_config()
-
-####################
-# Check for FFMPEG #
-####################
-def check_ffmpeg(this_dir):
- if sys.platform == "win32":
- # Check for ffmpeg.exe in the specified directory on Windows
- ffmpeg_path = os.path.join(this_dir, "system", "win_ffmpeg", "ffmpeg.exe")
- if os.path.exists(ffmpeg_path):
- return True
- else:
- # Check for FFmpeg on Linux and macOS using the command line
- try:
- subprocess.run(['ffmpeg', '-version'], stdout=subprocess.PIPE, stderr=subprocess.PIPE, check=True)
- return True
- except (subprocess.CalledProcessError, FileNotFoundError):
- pass
- return False
-
-# Check if FFmpeg is installed
-ffmpeg_installed = check_ffmpeg(this_dir)
-
-if not ffmpeg_installed:
- print(f"[{branding}ENG] \033[92mTranscoding :\033[94m ffmpeg not found\033[0m")
- print(f"[{branding}ENG] FFmpeg is not installed. Transcoding will be disabled.")
- print(f"[{branding}ENG] Please install FFmpeg on your system.")
-
- if sys.platform == "win32":
- print(f"[{branding}ENG] \033[92mTranscoding :\033[94m ffmpeg not found\033[0m")
- print(f"[{branding}ENG] Installation instructions for Windows:")
- print(f"[{branding}ENG] Copy the 'ffmpeg.exe' file to '{os.path.join(this_dir, 'system', 'win_ffmpeg')}'")
- else:
- print(f"[{branding}ENG] \033[92mTranscoding :\033[94m ffmpeg not found\033[0m")
- print(f"[{branding}ENG] Installation instructions:")
- print(f"[{branding}ENG] Linux (Debian-based systems): Run 'sudo apt-get install ffmpeg' in the terminal.")
- print(f"[{branding}ENG] macOS: Run 'brew install ffmpeg' in the terminal (requires Homebrew).")
- # You can choose to exit the script or continue without FFmpeg
- # exit(1)
-
-if ffmpeg_installed:
- from ffmpeg.asyncio import FFmpeg
- print(f"[{branding}ENG] \033[92mTranscoding :\033[93m ffmpeg found\033[0m")
-
-################################
-# Check for portaudio on Linux #
-################################
-try:
- import sounddevice as sd
- sounddevice_installed=True
-except OSError:
- print(f"[{branding}Startup] \033[91mInfo\033[0m PortAudio library not found. If you wish to play TTS in standalone mode through the API suite")
- print(f"[{branding}Startup] \033[91mInfo\033[0m please install PortAudio. This will not affect any other features or use of Alltalk.")
- print(f"[{branding}Startup] \033[91mInfo\033[0m If you don't know what the API suite is, then this message is nothing to worry about.")
- sounddevice_installed=False
- if sys.platform.startswith('linux'):
- print(f"[{branding}Startup] \033[91mInfo\033[0m On Linux, you can use the following command to install PortAudio:")
- print(f"[{branding}Startup] \033[91mInfo\033[0m sudo apt-get install portaudio19-dev")
-
-#######################################################################
-# Attempt to import the ModelLoader class for the selected TTS engine #
-#######################################################################
-if engine_loaded in engines_available:
- loader_module = importlib.import_module(f"system.tts_engines.{engine_loaded}.model_engine")
- tts_class = getattr(loader_module, "tts_class")
- # Setup model_engine as the way to call the functions within the Class.
- model_engine = tts_class()
-else:
- raise ValueError(f"Invalid TTS engine: {engine_loaded}")
-
-##########################################
-# Run setup function in the model_engine #
-##########################################
-@asynccontextmanager
-async def startup_shutdown(no_actual_value_it_demanded_something_be_here):
- try:
- await model_engine.setup()
- except FileNotFoundError as e:
- print(f"Error during setup: {e}. Continuing without the TTS model.")
- yield
- # Shutdown logic
-
-###############################
-# Setup FastAPI with Lifespan #
-###############################
-app = FastAPI(lifespan=startup_shutdown)
-# Allow all origins, and set other CORS options
-app.add_middleware(
- CORSMiddleware,
- allow_origins=["*"], # Set this to the specific origins you want to allow
- allow_credentials=True,
- allow_methods=["*"],
- allow_headers=["*"],
-)
-
-##############################
-# API Endpoint - /api/reload #
-##############################
-model_currently_changing = False
-@app.route("/api/reload", methods=["POST"])
-async def apifunction_reload(request: Request):
- global model_currently_changing
- if model_currently_changing == True:
- return Response(content=json.dumps({"status": "model-is currently changing"}), media_type="application/json")
- model_currently_changing == True
- requested_model = request.query_params.get("tts_method")
- if requested_model not in model_engine.available_models:
- model_currently_changing == False
- return {"status": "error", "message": "Invalid TTS method specified"}
- # Call the handle_tts_method_change method from the imported class
- success = await model_engine.handle_tts_method_change(requested_model)
- if success:
- model_engine.current_model_loaded = requested_model
- model_currently_changing == False
- return Response(content=json.dumps({"status": "model-success"}), media_type="application/json")
- else:
- model_currently_changing == False
- return Response(content=json.dumps({"status": "model-failure"}), media_type="application/json")
-
-####################################
-# API Endpoint - /api/enginereload #
-####################################
-import asyncio
-uvicorn_server = None
-def restart_self():
- """Restart the current script."""
- #print(f"[{branding}ENG] Restarting subprocess...")
- os.execv(sys.executable, ['python'] + sys.argv)
-
-async def handle_restart():
- global uvicorn_server
- if uvicorn_server:
- print(f"[{branding}ENG] Stopping uvicorn server...")
- uvicorn_server.should_exit = True
- uvicorn_server.force_exit = True
- # Check if the server has stopped
- while not uvicorn_server.is_stopped:
- await asyncio.sleep(0.1) # Check every 100ms if server is stopped
- restart_self()
-
-@app.post("/api/enginereload")
-async def apifunction_reload(request: Request):
- requested_engine = request.query_params.get("engine")
- if requested_engine not in engines_available:
- return {"status": "error", "message": "Invalid TTS engine specified"}
- print(f"[{branding}ENG]")
- print(f"[{branding}ENG] \033[94mChanging model loaded. Please wait.\033[00m")
- print(f"[{branding}ENG]")
- # Update the tts_engines.json file
- tts_engines_file = os.path.join(this_dir, "system", "tts_engines", "tts_engines.json")
- with open(tts_engines_file, "r") as f:
- tts_engines_data = json.load(f)
- for engine in tts_engines_data["engines_available"]:
- if engine["name"] == requested_engine:
- tts_engines_data["engine_loaded"] = requested_engine
- tts_engines_data["selected_model"] = engine["selected_model"]
- break
- with open(tts_engines_file, "w") as f:
- json.dump(tts_engines_data, f)
- #print(f"[{branding}ENG] Reloading the engine...")
- # Start the restart process in the background
- asyncio.create_task(handle_restart())
- # Never goes past the above asyncio as the script is killed at that point
- return Response(content=json.dumps({"status": "engine-success"}), media_type="application/json")
-
-#######################################
-# API Endpoint - /api/stop-generation #
-#######################################
-# When this endpoint it called it will set tts_stop_generation in the model_engine to True, which can be used to interrupt the current TTS generation.
-@app.put("/api/stop-generation")
-async def apifunction_stop_generation():
- if model_engine.tts_generating_lock and not model_engine.tts_stop_generation:
- model_engine.tts_stop_generation = True
- return {"message": "Cancelling current TTS generation"}
-
-#############################
-# API Endpoint - /api/audio #
-#############################
-# Gives web access to the output files as downloads
-@app.get("/audio/{filename}")
-async def apifunction_get_audio(filename: str):
- audio_path = this_dir / output_directory / filename
- return FileResponse(audio_path)
-
-##################################
-# API Endpoint - /api/audiocache #
-##################################
-@app.get("/audiocache/{filename}")
-async def apifunction_get_audio(filename: str):
- audio_path = Path({output_directory}) / filename
- if not audio_path.is_file():
- raise HTTPException(status_code=404, detail="File not found")
- response = FileResponse(path=audio_path, media_type='audio/wav', filename=filename)
- # Set caching headers
- response.headers["Cache-Control"] = "public, max-age=604800" # Cache for one week
- response.headers["ETag"] = str(audio_path.stat().st_mtime) # Use the file's last modified time as a simple ETag
- return response
-
-##############################
-# API Endpoint - /api/voices #
-##############################
-@app.get("/api/voices")
-async def apifunction_get_voices():
- if not model_engine.multivoice_capable:
- return {"status": "error", "message": f"The currently loaded TTS engine '{model_engine.engine_loaded}' does not support multiple voices."}
-
- available_voices = model_engine.voices_file_list()
- return {"status": "success", "voices": available_voices}
-
-#################################
-# API Endpoint - /api/rvcvoices #
-#################################
-@app.get("/api/rvcvoices")
-async def apifunction_get_voices():
- global params, api_defaults, rvc_settings, infer_pipeline
- params, api_defaults, rvc_settings, infer_pipeline = load_config()
- if not rvc_settings["rvc_enabled"]:
- return {"status": "success", "rvcvoices": ["Disabled"]}
- # Define the directory path
- directory = os.path.join(this_dir, "models", "rvc_voices")
- # Check if the directory exists
- if not os.path.exists(directory):
- return {"status": "success", "rvcvoices": ["Disabled"]}
- # List all .pth files recursively in the directory
- pth_files = []
- for root, dirs, files in os.walk(directory):
- for file in files:
- if file.endswith(".pth"):
- pth_files.append(os.path.relpath(os.path.join(root, file), start=directory))
- # If no .pth files found, return "Disabled"
- if not pth_files:
- return {"status": "success", "rvcvoices": ["Disabled"]}
- # Sort the list alphabetically
- pth_files.sort()
- # Add "Disabled" at the beginning of the list
- pth_files.insert(0, "Disabled")
- return {"status": "success", "rvcvoices": pth_files}
-
-#####################################
-# API Endpoint - /api/reload_config #
-#####################################
-@app.get("/api/reload_config")
-async def apifunction_reload_config():
- global params, api_defaults, rvc_settings, infer_pipeline
- model_engine.available_models = model_engine.scan_models_folder()
- params, api_defaults, rvc_settings, infer_pipeline = load_config()
- return Response("Config file reloaded successfully")
-
-#############################
-# API Endpoint - /api/ready #
-#############################
-@app.get("/api/ready")
-async def apifunction_ready():
- if model_engine.setup_has_run:
- return Response("Ready")
- else:
- return Response("Unloaded")
-
-#######################################
-# API Endpoint - /api/currentsettings #
-#######################################
-@app.get('/api/currentsettings')
-def apifunction_get_current_settings():
- settings = {
- "engines_available": engines_available, # Lists the currently available TTS engines
- "current_engine_loaded": model_engine.engine_loaded, # Lists the currently loaded TTS engine
- "models_available": [{"name": name} for name in model_engine.available_models.keys()], # Lists the currently available models that can be loaded on this TTS engine.
- "current_model_loaded": model_engine.current_model_loaded, # Lists the currently loaded model
- "manufacturer_name": model_engine.manufacturer_name, # The name of the person/company/group who made the current TTS engine
- "audio_format": model_engine.audio_format, # The default audio format the the currently loaded TTS engine generates in
- "deepspeed_capable": model_engine.deepspeed_capable, # Is the TTS engine capable of supporting DeepSpeed
- "deepspeed_available": model_engine.deepspeed_available, # Was DeepSpeed detected as being available for use
- "deepspeed_enabled": model_engine.deepspeed_enabled, # Is DeepSpeed current enabled or disabled
- "generationspeed_capable": model_engine.generationspeed_capable, # Does the TTS engine support changing speed of generated TTS
- "generationspeed_set": model_engine.generationspeed_set, # What is the currently set TTS speed
- "lowvram_capable": model_engine.lowvram_capable, # Does the TTS engine support moving the model between VRAM and System Ram
- "lowvram_enabled": model_engine.lowvram_enabled, # What is the currently enabled/disabled state of low vram
- "pitch_capable": model_engine.pitch_capable, # Does the TTS engine support pitch on tts generation
- "pitch_set": model_engine.pitch_set, # What is the currently set value for pitch
- "repetitionpenalty_capable": model_engine.repetitionpenalty_capable, # Does the TTS engine support differnt repitition penalties
- "repetitionpenalty_set": model_engine.repetitionpenalty_set, # What is the currently set repetition penalty
- "streaming_capable": model_engine.streaming_capable, # Does the model support streaming TTS generation
- "temperature_capable": model_engine.temperature_capable, # Does the TTS engine support model temperature settings
- "temperature_set": model_engine.temperature_set, # What is the currently set temperature
- "ttsengines_installed": model_engine.engine_installed, #
- "languages_capable": model_engine.languages_capable, # Does the TTS engine support multi languages
- "multivoice_capable": model_engine.multivoice_capable, # Does the TTS engine support multi voice models
- "multimodel_capable": model_engine.multimodel_capable, # Does the engine support loading many different models
- }
- return settings
-
-######################################
-# API Endpoint - /api/lowvramsetting #
-######################################
-@app.post("/api/lowvramsetting")
-async def apifunction_low_vram(request: Request, new_low_vram_value: bool):
- if not model_engine.lowvram_capable:
- return {"status": "error", "message": f"The currently loaded TTS engine '{model_engine.engine_loaded}' does not support lowvram."}
- try:
- if new_low_vram_value is None:
- raise ValueError("Missing 'low_vram' parameter")
- if model_engine.lowvram_enabled == new_low_vram_value:
- return Response(content=json.dumps({"status": "success", "message": f"[{branding}Model] LowVRAM is already {'enabled' if new_low_vram_value else 'disabled'}.",}))
- model_engine.lowvram_enabled = new_low_vram_value
- await model_engine.unload_model()
-
- if model_engine.cuda_is_available:
- if model_engine.lowvram_enabled:
- model_engine.device = "cpu"
- print(f"[{branding}Engine] \033[94mLowVRAM Enabled.\033[0m Model will move between \033[93mVRAM(cuda) <> System RAM(cpu)\033[0m")
- else:
- model_engine.device = "cuda"
- print(f"[{branding}Engine] \033[94mLowVRAM Disabled.\033[0m Model will stay in \033[93mVRAM(cuda)\033[0m")
- await model_engine.setup()
- else:
- # Handle the case where CUDA is not available
- print(f"[{branding}Engine] \033[91mError:\033[0m Nvidia CUDA is not available on this system. Unable to use LowVRAM mode.")
- model_engine.lowvram_enabled = False
-
- return Response(content=json.dumps({"status": "lowvram-success"}))
- except Exception as e:
- return Response(content=json.dumps({"status": "error", "message": str(e)}))
-
-#################################
-# API Endpoint - /api/deepspeed #
-#################################
-@app.post("/api/deepspeed")
-async def deepspeed(request: Request, new_deepspeed_value: bool):
- if not model_engine.deepspeed_capable or not model_engine.deepspeed_available:
- return {"status": "error", "message": f"The currently loaded TTS engine '{model_engine.engine_loaded}' does not support DeepSpeed or DeepSpeed is not available on this system."}
- try:
- if new_deepspeed_value is None:
- raise ValueError("Missing 'deepspeed' parameter")
- if model_engine.deepspeed_enabled == new_deepspeed_value:
- return Response(
- content=json.dumps({"status": "success", "message": f"DeepSpeed is already {'enabled' if new_deepspeed_value else 'disabled'}.",}))
- model_engine.deepspeed_enabled = new_deepspeed_value
- await model_engine.handle_deepspeed_change(new_deepspeed_value)
- return Response(content=json.dumps({"status": "deepspeed-success"}))
- except Exception as e:
- return Response(content=json.dumps({"status": "error", "message": str(e)}))
-
-#################################
-# API Endpoint - /api/voice2rvc #
-#################################
-@app.post("/api/voice2rvc")
-async def voice2rvc(input_tts_path: str = Form(...), output_rvc_path: str = Form(...), pth_name: str = Form(...), pitch: str = Form(...), method: str = Form(...)):
- try:
- # Handle "Disabled" case
- if pth_name.lower() in ["disabled", "disable"]:
- print(f"[{branding}TTS] \033[94mVoice2RVC Convert: No voice was specified or the name was Disabled\033[0m")
- return {"status": "error", "message": "No voice was specified or the name was Disabled"}
- input_tts_path = Path(input_tts_path)
- output_rvc_path = Path(output_rvc_path)
- # Check if input file exists
- if not input_tts_path.is_file():
- raise HTTPException(status_code=400, detail=f"Input file {input_tts_path} does not exist.")
- # Define the model path based on the selected model name
- pth_path = this_dir / "models" / "rvc_voices" / pth_name
- # Check if model file exists
- if not pth_path.is_file():
- raise HTTPException(status_code=400, detail=f"Model file {pth_path} does not exist.")
- # Run the RVC conversion
- result_path = run_voice2rvc(input_tts_path, output_rvc_path, pth_path, pitch, method)
- if result_path:
- return {"status": "success", "output_path": str(result_path)}
- else:
- raise HTTPException(status_code=500, detail="RVC conversion failed.")
- except Exception as e:
- print(f"Error during Voice2RVC conversion: {e}")
- raise HTTPException(status_code=500, detail=str(e))
-
-# Define the run_rvc function
-def run_voice2rvc(input_tts_path, output_rvc_path, pth_path, pitch, method):
- print(f"[{branding}GEN] \033[94mVoice2RVC Convert: Started\033[0m")
- generate_start_time = time.time()
- f0up_key = pitch
- filter_radius = rvc_settings["filter_radius"]
- index_rate = rvc_settings["index_rate"]
- rms_mix_rate = rvc_settings["rms_mix_rate"]
- protect = rvc_settings["protect"]
- hop_length = rvc_settings["hop_length"]
- f0method = method
- split_audio = rvc_settings["split_audio"]
- f0autotune = rvc_settings["autotune"]
- embedder_model = rvc_settings["embedder_model"]
- training_data_size = rvc_settings["training_data_size"]
- # Convert path variables to strings
- input_tts_path = str(input_tts_path)
- pth_path = str(pth_path)
- # Check if the model file exists
- if not os.path.isfile(pth_path):
- print(f"Model file {pth_path} does not exist. Exiting.")
- return
- # Get the directory of the model file
- model_dir = os.path.dirname(pth_path)
- # Find all .index files in the model directory
- index_files = [file for file in os.listdir(model_dir) if file.endswith(".index")]
- if len(index_files) == 1:
- index_path = str(os.path.join(model_dir, index_files[0]))
- else:
- index_path = ""
- # Call the infer_pipeline function
- infer_pipeline(f0up_key, filter_radius, index_rate, rms_mix_rate, protect, hop_length, f0method,
- input_tts_path, output_rvc_path, pth_path, index_path, split_audio, f0autotune, embedder_model, training_data_size, debug_rvc)
- generate_end_time = time.time()
- generate_elapsed_time = generate_end_time - generate_start_time
- print(f"[{branding}GEN] \033[94mVoice2RVC Convert: \033[91m{generate_elapsed_time:.2f} seconds.\033[0m")
- return output_rvc_path
-
-##################################
-# Transcode between file formats #
-##################################
-async def transcode_audio(input_file, output_format):
- print(f"[{branding}Debug] *************************************************",) if debug_transcode else None
- print(f"[{branding}Debug] transcode_audio function called (debug_transcode)",) if debug_transcode else None
- print(f"[{branding}Debug] *************************************************",) if debug_transcode else None
- print(f"[{branding}Debug] Input file : {input_file}") if debug_transcode else None
- print(f"[{branding}Debug] Output format : {output_format}") if debug_transcode else None
- if output_format == "Disabled":
- print(f"[{branding}Debug] Transcode format is set to Disabled so skipping transcode.") if debug_transcode else None
- return input_file
- if not ffmpeg_installed:
- print(f"[{branding}TTS] FFmpeg is not installed. Format conversion is not possible.")
- raise Exception("FFmpeg is not installed. Format conversion is not possible.")
- # Get the file extension of the input file
- input_extension = os.path.splitext(input_file)[1][1:].lower()
- print(f"[{branding}Debug] Input file extension: {input_extension}") if debug_transcode else None
- # Check if the input extension matches the requested output format
- if input_extension == output_format.lower():
- print(f"[{branding}Debug] Input file is already in the requested format: {output_format}") if debug_transcode else None
- return input_file
- output_file = os.path.splitext(input_file)[0] + f".{output_format}"
- print(f"[{branding}Debug] Output file: {output_file}") if debug_transcode else None
- ffmpeg_path = "ffmpeg" # Default path for Linux and macOS
- if sys.platform == "win32":
- ffmpeg_path = os.path.join(this_dir, "system", "win_ffmpeg", "ffmpeg.exe")
- ffmpeg = (FFmpeg(ffmpeg_path).option("y").input(input_file).output(output_file))
- print(f"[{branding}Debug] Transcoding to {output_format}") if debug_transcode else None
- if output_format == "opus":
- print(f"[{branding}Debug] Configuring Opus options") if debug_transcode else None
- ffmpeg.output(output_file, {"codec:a": "libopus", "b:a": "128k", "vbr": "on", "compression_level": 10, "frame_duration": 60, "application": "voip"})
- elif output_format == "aac":
- print(f"[{branding}Debug] Configuring AAC options") if debug_transcode else None
- ffmpeg.output(output_file, {"codec:a": "aac", "b:a": "192k"})
- elif output_format == "flac":
- print(f"[{branding}Debug] Configuring FLAC options") if debug_transcode else None
- ffmpeg.output(output_file, {"codec:a": "flac", "compression_level": 8})
- elif output_format == "wav":
- print(f"[{branding}Debug] Configuring WAV options") if debug_transcode else None
- ffmpeg.output(output_file, {"codec:a": "pcm_s16le"})
- elif output_format == "mp3":
- print(f"[{branding}Debug] Configuring MP3 options") if debug_transcode else None
- ffmpeg.output(output_file, {"codec:a": "libmp3lame", "b:a": "192k"})
- else:
- print(f"[{branding}TTS] Unsupported output format: {output_format}")
- raise ValueError(f"Unsupported output format: {output_format}")
- try:
- print(f"[{branding}Debug] Starting transcoding process") if debug_transcode else None
- await ffmpeg.execute()
- print(f"[{branding}Debug] Transcoding completed successfully") if debug_transcode else None
- except Exception as e:
- print(f"[{branding}TTS] Error occurred during transcoding: {str(e)}")
- raise
- print(f"[{branding}Debug] Deleting original input file") if debug_transcode else None
- os.remove(input_file)
- print(f"[{branding}Debug] Transcoding process completed") if debug_transcode else None
- print(f"[{branding}Debug] Transcoded file: {output_file}") if debug_transcode else None
- return output_file
-
-##############################
-# Central Transcode function #
-##############################
-async def transcode_audio_if_necessary(output_file, model_audio_format, output_audio_format):
- if debug_transcode:
- print(f"[{branding}Debug] model_engine.audio_format is:", model_audio_format)
- print(f"[{branding}Debug] audio format is:", output_audio_format)
- print(f"[{branding}Debug] Entering the transcode condition")
- try:
- if debug_transcode:
- print(f"[{branding}Debug] Calling transcode_audio function")
- output_file = await transcode_audio(output_file, output_audio_format)
- if debug_transcode:
- print(f"[{branding}Debug] Transcode completed successfully")
- except Exception as e:
- print(f"[{branding}Debug] Error occurred during transcoding:", str(e))
- raise
- if debug_transcode:
- print(f"[{branding}Debug] Transcode condition completed")
- # Update the output file path and URLs based on the transcoded file
- if debug_tts:
- print(f"[{branding}Debug] Updating output file paths and URLs")
- if params["api_def"]["api_use_legacy_api"]:
- output_file_url = f'http://{api_defaults["api_legacy_ip_address"]}:{api_defaults["api_port_number"]}/audio/{os.path.basename(output_file)}'
- output_cache_url = f'http://{api_defaults["api_legacy_ip_address"]}:{api_defaults["api_port_number"]}/audiocache/{os.path.basename(output_file)}'
- else:
- output_file_url = f'/audio/{os.path.basename(output_file)}'
- output_cache_url = f'/audiocache/{os.path.basename(output_file)}'
- if debug_tts:
- print(f"[{branding}Debug] Output file paths and URLs updated")
- return output_file, output_file_url, output_cache_url
-
-##############################
-#### Streaming Generation ####
-##############################
-@app.get("/api/tts-generate-streaming", response_class=StreamingResponse)
-async def apifunction_generate_streaming(text: str, voice: str, language: str, output_file: str):
- if model_engine.streaming_capable == False:
- print(f"[{branding}GEN] The selected TTS Engine does not support streaming. To use streaming, please select a TTS")
- print(f"[{branding}GEN] Engine that has streaming capability. You can find the streaming support information for")
- print(f"[{branding}GEN] each TTS Engine in the 'Engine Information' section of the Gradio interface.")
- try:
- output_file_path = f'{this_dir / output_directory / output_file}.{model_engine.audio_format}'
- stream = await generate_audio(text, voice, language, model_engine.temperature_set, model_engine.repetitionpenalty_set, 1.0, 1.0, output_file_path, streaming=True)
- return StreamingResponse(stream, media_type="audio/wav")
- except Exception as e:
- print(f"An error occurred: {e}")
- return JSONResponse(content={"error": "An error occurred"}, status_code=500)
-
-@app.post("/api/tts-generate-streaming", response_class=JSONResponse)
-async def tts_generate_streaming(request: Request, text: str = Form(...), voice: str = Form(...), language: str = Form(...), output_file: str = Form(...)):
- if model_engine.streaming_capable == False:
- print(f"[{branding}GEN] The selected TTS Engine does not support streaming. To use streaming, please select a TTS")
- print(f"[{branding}GEN] Engine that has streaming capability. You can find the streaming support information for")
- print(f"[{branding}GEN] each TTS Engine in the 'Engine Information' section of the Gradio interface.")
- try:
- output_file_path = f'{this_dir / output_directory / output_file}.{model_engine.audio_format}'
- await generate_audio(text, voice, language, model_engine.temperature_set, model_engine.repetitionpenalty_set, "1.0", "1.0", output_file_path, streaming=False)
- return JSONResponse(content={"output_file_path": str(output_file)}, status_code=200)
- except Exception as e:
- print(f"An error occurred: {e}")
- return JSONResponse(content={"error": "An error occurred"}, status_code=500)
-
-###################################
-# Central generate_audio function #
-###################################
-async def generate_audio(text, voice, language, temperature, repetition_penalty, speed, pitch, output_file, streaming=False):
- if model_engine.streaming_capable == False and streaming==True:
- print(f"[{branding}GEN] The selected TTS Engine does not support streaming. To use streaming, please select a TTS")
- print(f"[{branding}GEN] Engine that has streaming capability. You can find the streaming support information for")
- print(f"[{branding}GEN] each TTS Engine in the 'Engine Information' section of the Gradio interface.")
- # Get the async generator from the internal function
- response = model_engine.generate_tts(text, voice, language, temperature, repetition_penalty, speed, pitch, output_file, streaming)
- # If streaming, then return the generator as-is, otherwise just exhaust it and return
- if streaming:
- # Return an async generator that yields from the response
- async def stream_response():
- async for chunk in response:
- yield chunk
- return stream_response()
- else:
- # Exhaust the generator and handle any errors
- try:
- async for _ in response:
- pass
- except Exception as e:
- print(f"[GEN] Error during audio generation: {str(e)}")
- raise
-
-###########################
-#### PREVIEW VOICE API ####
-###########################
-@app.post("/api/previewvoice/", response_class=JSONResponse)
-async def apifunction_preview_voice(request: Request, voice: str = Form(...)):
- try:
- # Hardcoded settings
- language = "en"
- output_file_name = "api_preview_voice"
- # Clean the voice filename for inclusion in the text
- clean_voice_filename = re.sub(r'\.wav$', '', voice.replace(' ', '_'))
- clean_voice_filename = re.sub(r'[^a-zA-Z0-9]', ' ', clean_voice_filename)
- # Generate the audio
- text = f"Hello, this is a preview of voice {clean_voice_filename}."
- # Generate the audio
- output_file_path = this_dir / output_directory / f'{output_file_name}.{model_engine.audio_format}'
- await generate_audio(text, voice, language, model_engine.temperature_set, model_engine.repetitionpenalty_set, 1.0, 0, output_file_path, streaming=False)
- # Generate the URL
- output_file_url = f'/audio/{output_file_name}.{model_engine.audio_format}'
- # Return the response with both local file path and URL
- return JSONResponse(
- content={
- "status": "generate-success",
- "output_file_path": str(output_file_path),
- "output_file_url": str(output_file_url),
- },
- status_code=200,
- )
- except Exception as e:
- print(f"An error occurred: {e}")
- return JSONResponse(content={"error": "An error occurred"}, status_code=500)
-
-##################################################################
-# API Endpoint - OpenAI Speech API compatable endpoint Validator #
-##################################################################
-class OpenAIInput(BaseModel):
- model: str = Field(..., description="The TTS model to use. Currently ignored.")
- input: str = Field(..., max_length=4096, description="The text to generate audio for.")
- voice: str = Field(..., description="The voice to use when generating the audio.")
- response_format: str = Field(default="wav", description="The format of the audio. Currently only 'wav' is supported.")
- speed: float = Field(default=1.0, ge=0.25, le=4.0, description="The speed of the generated audio.")
-
- @field_validator('voice')
- def validate_voice(cls, value):
- supported_voices = ["alloy", "echo", "fable", "nova", "onyx", "shimmer"]
- if value not in supported_voices:
- raise ValueError(f'Voice must be one of {supported_voices}')
- return value
-
-class OpenAIGenerator:
- @staticmethod
- def validate_input(json_data: dict) -> Union[None, str]:
- try:
- OpenAIInput(**json_data)
- return None
- except ValidationError as e:
- errors = []
- for err in e.errors():
- field = err['loc'][0]
- message = err['msg']
- description = OpenAIInput.model_fields[field].field_info.description
- errors.append(f"Error in field '{field}': {message}. Description: {description}")
- return ', '.join(errors)
-
-#########################################################################
-# API Endpoint - OpenAI Speech API compatable endpoint /v1/audio/speech #
-#########################################################################
-@app.post("/v1/audio/speech", response_class=JSONResponse)
-async def openai_tts_generate(request: Request):
- try:
- json_data = await request.json()
- print(f"[{branding}Debug] Received JSON data: {json_data}") if debug_openai else None
- validation_error = OpenAIGenerator.validate_input(json_data)
- if validation_error:
- print(f"Validation error: {validation_error}") if debug_openai else None
- return JSONResponse(content={"error": validation_error}, status_code=400)
- model = json_data["model"] # Currently ignored
- input_text = json_data["input"]
- voice = json_data["voice"]
- response_format = json_data["response_format"].lower()
- speed = json_data["speed"]
- print(f"[{branding}Debug] Input text: {input_text}") if debug_openai else None
- print(f"[{branding}Debug] Voice: {voice}") if debug_openai else None
- print(f"[{branding}Debug] Speed: {speed}") if debug_openai else None
- cleaned_string = html.unescape(standard_filtering(input_text))
- # Map the OpenAI voice to the corresponding internal voice
- voice_mapping = {
- "alloy": model_engine.openai_alloy,
- "echo": model_engine.openai_echo,
- "fable": model_engine.openai_fable,
- "nova": model_engine.openai_nova,
- "onyx": model_engine.openai_onyx,
- "shimmer": model_engine.openai_shimmer
- }
- mapped_voice = voice_mapping.get(voice)
- if not mapped_voice:
- print(f"Unsupported voice: {voice}")
- return JSONResponse(content={"error": "Unsupported voice"}, status_code=400)
- print(f"[{branding}Debug]Mapped voice: {mapped_voice}") if debug_openai else None
- # Generate the audio
- # Generate a unique filename
- unique_id = uuid.uuid4()
- timestamp = int(time.time())
- output_file_path = f'{this_dir / output_directory / f"openai_output_{unique_id}_{timestamp}.{model_engine.audio_format}"}'
- await generate_audio(cleaned_string, mapped_voice, "en", model_engine.temperature_set,
- model_engine.repetitionpenalty_set, speed, model_engine.pitch_set,
- output_file_path, streaming=False)
- print(f"[{branding}Debug] Audio generated at: {output_file_path}") if debug_openai else None
- if rvc_settings["rvc_enabled"]:
- if rvc_settings["rvc_char_model_file"].lower() in ["disabled", "disable"]:
- print(f"[{branding}Debug] Pass rvccharacter_voice_gen") if debug_openai else None
- pass # Skip RVC processing for character part
- else:
- print(f"[{branding}Debug] send to rvc") if debug_openai else None
- pth_path = this_dir / "models" / "rvc_voices" / rvc_settings["rvc_char_model_file"]
- run_rvc(output_file_path, pth_path, infer_pipeline)
- # Transcode the audio to the requested format
- transcoded_file_path = await transcode_for_openai(output_file_path, response_format)
- print(f"[{branding}Debug] Audio transcoded to: {transcoded_file_path}") if debug_openai else None
- # Return the audio file
- return FileResponse(transcoded_file_path, media_type=f"audio/{response_format}", filename=f"output.{response_format}")
- except Exception as e:
- print(f"[{branding}Debug] An error occurred: {str(e)}") if debug_openai else None
- return JSONResponse(content={"error": "An error occurred"}, status_code=500)
-
-###########################################################################
-# API Endpoint - OpenAI Speech API compatable endpoint Transcode Function #
-###########################################################################
-async def transcode_for_openai(input_file, output_format):
- print(f"[{branding}Debug] ************************************") if debug_openai else None
- print(f"[{branding}Debug] transcode_for_openai function called") if debug_openai else None
- print(f"[{branding}Debug] ************************************") if debug_openai else None
- print(f"[{branding}Debug] Input file : {input_file}") if debug_openai else None
- print(f"[{branding}Debug] Output format : {output_format}") if debug_openai else None
- if not ffmpeg_installed:
- print(f"[{branding}TTS] FFmpeg is not installed. Format conversion is not possible.")
- raise Exception("FFmpeg is not installed. Format conversion is not possible.")
- # Get the file extension of the input file
- input_extension = os.path.splitext(input_file)[1][1:].lower()
- print(f"[{branding}Debug] Input file extension: {input_extension}") if debug_openai else None
- # Check if the input extension matches the requested output format
- if input_extension == output_format.lower():
- print(f"[{branding}Debug] Input file is already in the requested format: {output_format}") if debug_openai else None
- return input_file
- output_file = os.path.splitext(input_file)[0] + f".{output_format}"
- print(f"[{branding}Debug] Output file: {output_file}") if debug_openai else None
- ffmpeg_path = "ffmpeg" # Default path for Linux and macOS
- if sys.platform == "win32":
- ffmpeg_path = os.path.join(this_dir, "system", "win_ffmpeg", "ffmpeg.exe")
- ffmpeg = (FFmpeg(ffmpeg_path).option("y").input(input_file).output(output_file))
- print(f"[{branding}Debug] Transcoding to {output_format}") if debug_openai else None
- if output_format == "opus":
- print(f"[{branding}Debug] Configuring Opus options") if debug_openai else None
- ffmpeg.output(output_file, {"codec:a": "libopus", "b:a": "128k", "vbr": "on", "compression_level": 10, "frame_duration": 60, "application": "voip"})
- elif output_format == "aac":
- print(f"[{branding}Debug] Configuring AAC options") if debug_openai else None
- ffmpeg.output(output_file, {"codec:a": "aac", "b:a": "192k"})
- elif output_format == "flac":
- print(f"[{branding}Debug] Configuring FLAC options") if debug_openai else None
- ffmpeg.output(output_file, {"codec:a": "flac", "compression_level": 8})
- elif output_format == "wav":
- print(f"[{branding}Debug] Configuring WAV options") if debug_openai else None
- ffmpeg.output(output_file, {"codec:a": "pcm_s16le"})
- elif output_format == "mp3":
- print(f"[{branding}Debug] Configuring MP3 options") if debug_openai else None
- ffmpeg.output(output_file, {"codec:a": "libmp3lame", "b:a": "192k"})
- elif output_format in ["ogg", "m4a"]:
- if output_format == "ogg":
- print(f"[{branding}Debug] Configuring OGG options") if debug_openai else None
- ffmpeg.output(output_file, {"codec:a": "libvorbis"})
- elif output_format == "m4a":
- print(f"[{branding}Debug] Configuring M4A options") if debug_openai else None
- ffmpeg.output(output_file, {"codec:a": "aac", "b:a": "192k"})
- else:
- print(f"[{branding}TTS] Unsupported output format: {output_format}")
- raise ValueError(f"Unsupported output format: {output_format}")
- try:
- print(f"[{branding}Debug] Starting transcoding process") if debug_openai else None
- await ffmpeg.execute()
- print(f"[{branding}Debug] Transcoding completed successfully") if debug_openai else None
- except Exception as e:
- print(f"[{branding}TTS] Error occurred during transcoding: {str(e)}")
- raise
- print(f"[{branding}Debug] Transcoding process completed") if debug_openai else None
- print(f"[{branding}Debug] Transcoded file: {output_file}") if debug_openai else None
- return output_file
-
-######################################################################################
-# API Endpoint - OpenAI Speech API compatable endpoint change engine voices Function #
-######################################################################################
-class VoiceMappings(BaseModel):
- alloy: str
- echo: str
- fable: str
- nova: str
- onyx: str
- shimmer: str
-
-@app.put("/api/openai-voicemap")
-async def update_openai_voice_mappings(mappings: VoiceMappings):
- # Update in-memory versions (wont display in Gradio without a restart as gradio caches)
- model_engine.openai_alloy = mappings.alloy
- model_engine.openai_echo = mappings.echo
- model_engine.openai_fable = mappings.fable
- model_engine.openai_nova = mappings.nova
- model_engine.openai_onyx = mappings.onyx
- model_engine.openai_shimmer = mappings.shimmer
- # Update model_settings.json file for the currently loaded TTS engine
- try:
- settings_file = this_dir / "system" / "tts_engines" / model_engine.engine_loaded / "model_settings.json"
- with open(settings_file, "r+") as f:
- settings = json.load(f)
- settings["openai_voices"] = mappings.dict()
- f.seek(0)
- json.dump(settings, f, indent=4)
- f.truncate()
- except Exception as e:
- raise HTTPException(status_code=500, detail=f"Failed to update model settings file: {str(e)}")
- return {"message": "OpenAI voice mappings updated successfully"}
-
-#######################
-# Play at the console #
-#######################
-def play_audio(file_path, volume):
- print(f"[{branding}GEN] \033[94m File path is: {file_path}") if debug_tts else None
- print(f"[{branding}GEN] \033[94m Volume is: {volume}") if debug_tts else None
- normalized_file_path = os.path.normpath(file_path)
- print(f"[{branding}GEN] \033[94m Normalized file path is: {normalized_file_path}") if debug_tts else None
- directory = os.path.dirname(normalized_file_path)
- if os.path.isdir(directory):
- print(f"[{branding}GEN] \033[94m Directory contents: {os.listdir(directory)}") if debug_tts else None
- else:
- print(f"[{branding}GEN] \033[94mError: Directory does not exist: {directory}") if debug_tts else None
- if not os.path.isfile(normalized_file_path):
- print(f"[{branding}GEN] \033[94mError: File does not exist: {normalized_file_path}\033[0m") if debug_tts else None
- return
- # Check for AAC format
- if normalized_file_path.lower().endswith('.aac'):
- print(f"[{branding}GEN] \033[94mPlay Audio : \033[0mAAC format files cannot be played at the console. Please choose another format")
- return
- try:
- print(f"[{branding}GEN] \033[94mPlay Audio : \033[0mPlaying audio at console")
- data, fs = sf.read(normalized_file_path)
- sd.play(volume * data, fs)
- sd.wait()
- except Exception as e:
- print(f"[{branding}GEN] \033[94mError playing audio file: {e}\033[0m")
-
-class Request(BaseModel):
- # Define the structure of the 'Request' class if needed
- pass
-
-
-class TTSGenerator:
- @staticmethod
- def validate_json_input(json_data: Union[Dict, str]) -> Union[None, str]:
- try:
- if isinstance(json_data, str):
- json_data = json.loads(json_data)
- JSONInput(**json_data)
- return None # JSON is valid
- except ValidationError as e:
- return str(e)
-
-#################################################################
-# /api/tts-generate Generation API Endpoint Narration Filtering #
-#################################################################
-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
-
-def standard_filtering(text_input):
- text_output = (text_input
- .replace("***", "")
- .replace("**", "")
- .replace("*", "")
- .replace("\n\n", "\n")
- .replace("'", "'")
- )
- return text_output
-
-#################################################################
-# /api/tts-generate Generation API Endpoint Narration Combining #
-#################################################################
-def combine(output_file_timestamp, output_file_name, audio_files, target_sample_rate=44100):
- audio = np.array([])
- try:
- for audio_file in audio_files:
- normalized_audio_file = os.path.normpath(audio_file)
- print(f"[{branding}Debug] Processing file: {normalized_audio_file}") if debug_concat else None
- # Check if the file exists
- if not os.path.isfile(normalized_audio_file):
- print(f"[{branding}Debug] Error: File does not exist: {normalized_audio_file}") if debug_concat else None
- return None, None
- # Read the audio file
- audio_data, current_sample_rate = sf.read(normalized_audio_file)
- print(f"[{branding}Debug] Read file: {normalized_audio_file}, Sample rate: {current_sample_rate}, Data shape: {audio_data.shape}") if debug_concat else None
- # Resample if necessary
- if current_sample_rate != target_sample_rate:
- print(f"[{branding}Debug] Resampling file from {current_sample_rate} to {target_sample_rate} Hz") if debug_concat else None
- audio_data = librosa.resample(audio_data, orig_sr=current_sample_rate, target_sr=target_sample_rate)
- # Concatenate audio data
- if audio.size == 0:
- audio = audio_data
- else:
- audio = np.concatenate((audio, audio_data))
- if output_file_timestamp:
- timestamp = int(time.time())
- output_file_path = os.path.join(this_dir / "outputs" / f'{output_file_name}_{timestamp}_combined.wav')
- sf.write(output_file_path, audio, target_sample_rate)
- # Legacy API or New API return
- if params["api_def"]["api_use_legacy_api"]:
- output_file_url = f"http://{api_defaults['api_legacy_ip_address']}:{params['api_def']['api_port_number']}/audio/{output_file_name}_{timestamp}_combined.wav"
- output_cache_url = f"http://{api_defaults['api_legacy_ip_address']}:{params['api_def']['api_port_number']}/audiocache/{output_file_name}_{timestamp}_combined.wav"
- else:
- output_file_url = f'/audio/{output_file_name}_{timestamp}_combined.wav'
- output_cache_url = f'/audiocache/{output_file_name}_{timestamp}_combined.wav'
- else:
- output_file_path = os.path.join(this_dir / "outputs" / f'{output_file_name}_combined.wav')
- sf.write(output_file_path, audio, target_sample_rate)
- # Legacy API or New API return
- if params["api_def"]["api_use_legacy_api"]:
- output_file_url = f"http://{api_defaults['api_legacy_ip_address']}:{api_defaults['api_port_number']}/audio/{output_file_name}_combined.wav"
- output_cache_url = f"http://{api_defaults['api_legacy_ip_address']}:{api_defaults['api_port_number']}/audiocache/{output_file_name}_combined.wav"
- else:
- output_file_url = f'/audio/{output_file_name}_combined.wav'
- output_cache_url = f'/audiocache/{output_file_name}_combined.wav'
- print(f"[{branding}Debug] Output file changed to:", output_file_path) if debug_concat else None
- print(f"[{branding}Debug] Output file changed to:", output_file_url) if debug_concat else None
- print(f"[{branding}Debug] Output file changed to:", output_cache_url) if debug_concat else None
- except Exception as e:
- print(f"Error occurred: {e}")
- return output_file_path, output_file_url, output_cache_url
-
-##################################
-# Central RVC Generation request #
-##################################
-def run_rvc(input_tts_path, pth_path, pitch, infer_pipeline):
- generate_start_time = time.time()
- f0up_key = pitch
- filter_radius = rvc_settings["filter_radius"]
- index_rate = rvc_settings["index_rate"]
- rms_mix_rate = rvc_settings["rms_mix_rate"]
- protect = rvc_settings["protect"]
- hop_length = rvc_settings["hop_length"]
- f0method = rvc_settings["f0method"]
- split_audio = rvc_settings["split_audio"]
- f0autotune = rvc_settings["autotune"]
- embedder_model = rvc_settings["embedder_model"]
- training_data_size = rvc_settings["training_data_size"]
- # Convert path variables to strings
- input_tts_path = str(input_tts_path)
- pth_path = str(pth_path)
- # Check if the model file exists
- if not os.path.isfile(pth_path):
- print(f"Model file {pth_path} does not exist. Exiting.")
- return
- # Get the directory of the model file
- model_dir = os.path.dirname(pth_path)
- # Get the filename of pth_path
- pth_filename = os.path.basename(pth_path)
- # Find all .index files in the model directory
- index_files = [file for file in os.listdir(model_dir) if file.endswith(".index")]
- if len(index_files) == 1:
- index_path = str(os.path.join(model_dir, index_files[0]))
- # Get the filename of index_path
- index_filename = os.path.basename(index_path)
- index_filename_print = index_filename
- index_size_print = training_data_size
- elif len(index_files) > 1:
- print(f"[{branding}GEN] \033[94mRVC Convert :\033[0m Multiple RVC index files found in the models folder where \033[93m{pth_filename}\033[0m is")
- print(f"[{branding}GEN] \033[94mRVC Convert :\033[0m located. Unable to determine which index to use. Continuing without an index file.")
- index_path = ""
- index_filename = None
- index_filename_print = "None used"
- index_size_print = "N/A"
- else:
- index_path = ""
- index_filename = None
- index_filename_print = "None used"
- index_size_print = "N/A"
- # Set the output path to be the same as the input path
- output_rvc_path = input_tts_path
- if debug_rvc:
- debug_lines = [
- f"[{branding}Debug] ************************************",
- f"[{branding}Debug] run_rvc function called (debug_rvc)",
- f"[{branding}Debug] ***********************************",
- f"[{branding}Debug] f0up_key : {f0up_key}",
- f"[{branding}Debug] filter_radius : {filter_radius}",
- f"[{branding}Debug] index_rate : {index_rate}",
- f"[{branding}Debug] rms_mix_rate : {rms_mix_rate}",
- f"[{branding}Debug] protect : {protect}",
- f"[{branding}Debug] hop_length : {hop_length}",
- f"[{branding}Debug] f0method : {f0method}",
- f"[{branding}Debug] input_tts_path : {input_tts_path}",
- f"[{branding}Debug] output_rvc_path : {output_rvc_path}",
- f"[{branding}Debug] pth_path : {pth_path}",
- f"[{branding}Debug] index_path : {index_path}",
- f"[{branding}Debug] split_audio : {split_audio}",
- f"[{branding}Debug] f0autotune : {f0autotune}",
- f"[{branding}Debug] embedder_model : {embedder_model}",
- f"[{branding}Debug] training_data_size: {training_data_size}"
- ]
- debug_info = "\n".join(debug_lines)
- print(debug_info)
- # Call the infer_pipeline function
- infer_pipeline(f0up_key, filter_radius, index_rate, rms_mix_rate, protect, hop_length, f0method,
- input_tts_path, output_rvc_path, pth_path, index_path, split_audio, f0autotune, embedder_model, training_data_size, debug_rvc)
- generate_end_time = time.time()
- generate_elapsed_time = generate_end_time - generate_start_time
- print(f"[{branding}GEN] \033[94mRVC Convert : \033[94mModel: \033[93m{pth_filename} \033[94mIndex: \033[93m{index_filename_print}\033[0m")
- print(f"[{branding}GEN] \033[94mRVC Convert : \033[93m{generate_elapsed_time:.2f} seconds. \033[94mMethod: \033[93m{f0method} \033[94mIndex size used \033[93m{index_size_print}\033[0m")
- return
-
-####################################################################
-# /api/tts-generate Generation API Endpoint Narration RVC handling #
-####################################################################
-def process_rvc_narrator(part_type, voice_gen, pitch, default_model_file, output_file, infer_pipeline):
- if voice_gen.lower() in ["disabled", "disable"]:
- return
- else:
- voice_gen_path = this_dir / "models" / "rvc_voices" / voice_gen
- pth_path = voice_gen_path if voice_gen else default_model_file
- run_rvc(output_file, pth_path, pitch, infer_pipeline)
-
-#############################################################
-# /api/tts-generate Generation API Endpoint JSON validation #
-#############################################################
-class JSONInput(BaseModel):
- text_input: str = Field(..., max_length=int(api_defaults["api_max_characters"]), description=f"text_input needs to be {api_defaults['api_max_characters']} characters or less.")
- text_filtering: str = Field(..., pattern="^(none|standard|html)$", description="text_filtering needs to be 'none', 'standard' or 'html'.")
- #character_voice_gen: str = Field(..., pattern=r'^[\(\)a-zA-Z0-9\_\-./\s]+$', description="character_voice_gen needs to be the name of a valid voice for the loaded TTS engine.")
- rvccharacter_voice_gen: str = Field(..., description="rvccharacter_voice_gen needs to be the name of a valid pth file in the 'folder\\file.pth' format or the word 'Disabled'.")
- rvccharacter_pitch: float = Field(..., description="RVC Character pitch needs to be a number between -24 and 24")
- narrator_enabled: str = Field(..., pattern="^(true|false|silent)$", description="narrator_enabled needs to be 'true', 'false' or 'silent'.")
- narrator_voice_gen: str = Field(..., pattern=r'^[\(\)a-zA-Z0-9\_\-./\s]+$', description="character_voice_gen needs to be the name of a valid voice for the loaded TTS engine.")
- rvcnarrator_voice_gen: str = Field(..., description="rvcnarrator_voice_gen needs to be the name of a valid pth file in the 'folder\\file.pth' format or the word 'Disabled'.")
- rvcnarrator_pitch: float = Field(..., description="RVC Narrator pitch needs to be a number between -24 and 24")
- text_not_inside: str = Field(..., pattern="^(character|narrator|silent)$", description="text_not_inside needs to be 'character', 'narrator' or 'silent'.")
- language: str = Field(..., pattern="^(ar|zh-cn|zh|cs|nl|en|fr|de|hu|hi|it|ja|ko|pl|pt|ru|es|tr)$", description="language needs to be one of the following: ar, zh-cn, zh, cs, nl, en, fr, de, hu, hi, it, ja, ko, pl, pt, ru, es, tr.")
- output_file_name: str = Field(..., pattern="^[a-zA-Z0-9_]+$", description="output_file_name needs to be the name without any special characters or file extension, e.g., 'filename'.")
- output_file_timestamp: bool = Field(..., description="output_file_timestamp needs to be true or false.")
- autoplay: bool = Field(..., description="autoplay needs to be a true or false value.")
- autoplay_volume: float = Field(..., ge=0.1, le=1.0, description="autoplay_volume needs to be from 0.1 to 1.0.")
- speed: float = Field(..., ge=0.25, le=2.0, description="speed needs to be between 0.25 and 2.0.")
- pitch: float = Field(..., ge=-10, le=10, description="pitch needs to be between -10 and 10.")
- temperature: float = Field(..., ge=0.1, le=1.0, description="temperature needs to be between 0.1 and 1.0.")
- repetition_penalty: float = Field(..., ge=1.0, le=20.0, description="repetition_penalty needs to be between 1.0 and 20.0.")
-
- @classmethod
- def validate_autoplay_volume(cls, value):
- if not (0.1 <= value <= 1.0):
- raise ValueError("Autoplay volume must be between 0.1 and 1.0")
- return value
-
- def validate_rvccharacter_voice_gen(cls, v):
- if v.lower() == "disabled":
- return v
- pattern = re.compile(r'^.*\.(pth)$')
- if not pattern.match(v):
- raise ValueError("rvccharacter_voice_gen needs to be the name of a valid pth file in the 'folder\\file.pth' format or 'Disabled'.")
- return v
-
- def validate_rvnarrator_voice_gen(cls, v):
- if v.lower() == "disabled":
- return v
- pattern = re.compile(r'^.*\.(pth)$')
- if not pattern.match(v):
- raise ValueError("rvcnarrator_voice_gen needs to be the name of a valid pth file in the 'folder\\file.pth' format or 'Disabled'.")
- return v
-
- def validate_pitches(cls, value):
- try:
- num_value = float(value)
- except ValueError:
- raise ValueError("Pitch must be a number or a string representing a number.")
- if not -24 <= num_value <= 24:
- raise ValueError("Pitch needs to be a number between -24 and 24.")
- return value
-
-def validate_json_input(json_input_data):
- try:
- JSONInput(**json_input_data)
- except ValidationError as e:
- error_messages = []
- for error in e.errors():
- field = error["loc"][0]
- description = JSONInput.__fields__[field].description
- error_messages.append(f"{field}: {description}")
- error_message = "\n".join(error_messages)
- print(f"[{branding}API] \033[91mError with API request:\033[0m", error_message)
- return error_message
- return None
-
-###############################################################
-# /api/tts-generate Generation API Endpoint pull fresh values #
-###############################################################
-def get_api_text_filtering():
- return api_defaults["api_text_filtering"]
-def get_api_narrator_enabled():
- return api_defaults["api_narrator_enabled"]
-def get_api_text_not_inside():
- return api_defaults["api_text_not_inside"]
-def get_api_language():
- return api_defaults["api_language"]
-def get_api_output_file_name():
- return api_defaults["api_output_file_name"]
-def get_api_output_file_timestamp():
- return api_defaults["api_output_file_timestamp"]
-def get_api_autoplay():
- return api_defaults["api_autoplay"]
-def get_api_autoplay_volume():
- return api_defaults["api_autoplay_volume"]
-def get_params_speed():
- return model_engine.generationspeed_set
-def get_params_temperature():
- return model_engine.temperature_set
-def get_params_repetition():
- value = model_engine.repetitionpenalty_set
- return float(str(value).replace(',', '.'))
-def get_params_pitch():
- return model_engine.pitch_set
-def get_character_voice_gen():
- return model_engine.def_character_voice
-def get_rvccharacter_voice_gen():
- return rvc_settings["rvc_char_model_file"]
-def get_rvccharacter_pitch():
- return rvc_settings["pitch"]
-def get_narrator_voice_gen():
- return model_engine.def_narrator_voice
-def get_rvcnarrator_voice_gen():
- return rvc_settings["rvc_narr_model_file"]
-def get_rvcnarrator_pitch():
- return rvc_settings["pitch"]
-
-#############################################
-# /api/tts-generate Generation API Endpoint #
-#############################################
-@app.post("/api/tts-generate", response_class=JSONResponse)
-async def apifunction_generate_tts_standard(
- text_input: str = Form(...),
- text_filtering: str = Form(None),
- character_voice_gen: str = Form(None),
- rvccharacter_voice_gen: str = Form(None),
- rvccharacter_pitch: float = Form(None),
- narrator_enabled: str = Form(None),
- narrator_voice_gen: str = Form(None),
- rvcnarrator_voice_gen: str = Form(None),
- rvcnarrator_pitch: float = Form(None),
- text_not_inside: str = Form(None),
- language: str = Form(None),
- output_file_name: str = Form(None),
- output_file_timestamp: bool = Form(None),
- autoplay: bool = Form(None),
- autoplay_volume: float = Form(None),
- streaming: bool = Form(False),
- speed: float = Form(None),
- temperature: float = Form(None),
- repetition_penalty: float = Form(None),
- pitch: float = Form(None),
- _text_filtering: str = Depends(get_api_text_filtering),
- _character_voice_gen: str = Depends(get_character_voice_gen),
- _rvccharacter_voice_gen: str = Depends(get_rvccharacter_voice_gen),
- _rvccharacter_pitch: str = Depends(get_rvccharacter_pitch),
- _narrator_enabled: str = Depends(get_api_narrator_enabled),
- _narrator_voice_gen: str = Depends(get_narrator_voice_gen),
- _rvcnarrator_voice_gen: str = Depends(get_rvcnarrator_voice_gen),
- _rvcnarrator_pitch: str = Depends(get_rvcnarrator_pitch),
- _text_not_inside: str = Depends(get_api_text_not_inside),
- _language: str = Depends(get_api_language),
- _output_file_name: str = Depends(get_api_output_file_name),
- _output_file_timestamp: bool = Depends(get_api_output_file_timestamp),
- _autoplay: bool = Depends(get_api_autoplay),
- _autoplay_volume: float = Depends(get_api_autoplay_volume),
- _speed: float = Depends(get_params_speed),
- _temperature: float = Depends(get_params_temperature),
- _repetition_penalty: float = Depends(get_params_repetition),
- _pitch: float = Depends(get_params_pitch),
-):
- if debug_tts or debug_tts_variables:
- debug_lines = [
- f"[{branding}Debug] *******************************************************",
- f"[{branding}Debug] /api/tts_generate function called (debug_tts_variables)",
- f"[{branding}Debug] **** PRE validation checks ****",
- f"[{branding}Debug] *******************************************************",
- f"[{branding}Debug] Defaults will be pulled from Settings if not specified in API Request!",
- f"[{branding}Debug] max_characters : {api_defaults['api_max_characters']}",
- f"[{branding}Debug] length_stripping : {api_defaults['api_length_stripping']}",
- f"[{branding}Debug] legacy_api : {api_defaults['api_use_legacy_api']}",
- f"[{branding}Debug] legacy_api IPadd : {api_defaults['api_legacy_ip_address']}",
- f"[{branding}Debug] allowed_filter : {api_defaults['api_allowed_filter']}",
- f"[{branding}Debug] text_filtering : {text_filtering}",
- f"[{branding}Debug] character_voice_gen : {character_voice_gen}",
- f"[{branding}Debug] rvccharacter_voice_gen : {rvccharacter_voice_gen}",
- f"[{branding}Debug] rvccharacter_pitch : {rvccharacter_pitch}",
- f"[{branding}Debug] narrator_enabled : {narrator_enabled}",
- f"[{branding}Debug] narrator_voice_gen : {narrator_voice_gen}",
- f"[{branding}Debug] rvcnarrator_voice_gen : {rvcnarrator_voice_gen}",
- f"[{branding}Debug] rvcnarrator_pitch : {rvcnarrator_pitch}",
- f"[{branding}Debug] text_not_inside : {text_not_inside}",
- f"[{branding}Debug] language : {language}",
- f"[{branding}Debug] output_file_name : {output_file_name}",
- f"[{branding}Debug] output_file_timestamp : {output_file_timestamp}",
- f"[{branding}Debug] autoplay : {autoplay}",
- f"[{branding}Debug] autoplay_volume : {autoplay_volume}",
- f"[{branding}Debug] streaming : {streaming}",
- f"[{branding}Debug] speed : {speed}",
- f"[{branding}Debug] temperature : {temperature}",
- f"[{branding}Debug] repetition_penalty : {repetition_penalty}",
- f"[{branding}Debug] pitch : {pitch}"
- ]
- debug_info = "\n".join(debug_lines)
- print(debug_info)
- text_filtering = text_filtering or _text_filtering
- character_voice_gen = character_voice_gen or _character_voice_gen
- rvccharacter_voice_gen = rvccharacter_voice_gen or _rvccharacter_voice_gen
- rvccharacter_pitch = rvccharacter_pitch if rvccharacter_pitch is not None else _rvccharacter_pitch
- narrator_enabled = narrator_enabled or _narrator_enabled # NEED TO COME BACK AND LOOK AT THIS!!!
- narrator_voice_gen = narrator_voice_gen or _narrator_voice_gen
- rvcnarrator_voice_gen = rvcnarrator_voice_gen or _rvcnarrator_voice_gen
- rvcnarrator_pitch = rvcnarrator_pitch if rvcnarrator_pitch is not None else _rvcnarrator_pitch
- text_not_inside = text_not_inside or _text_not_inside
- language = language or _language
- output_file_name = output_file_name or _output_file_name
- output_file_timestamp = output_file_timestamp if output_file_timestamp is not None else _output_file_timestamp
- autoplay = autoplay if autoplay is not None else _autoplay
- autoplay_volume = autoplay_volume or _autoplay_volume
- speed = speed or _speed
- temperature = temperature or _temperature
- repetition_penalty = repetition_penalty or _repetition_penalty
- pitch = pitch or _pitch
- try:
- json_input_data = {
- "text_input": text_input,
- "text_filtering": text_filtering,
- "character_voice_gen": character_voice_gen,
- "rvccharacter_voice_gen": rvccharacter_voice_gen,
- "rvccharacter_pitch": rvccharacter_pitch,
- "narrator_enabled": narrator_enabled,
- "narrator_voice_gen": narrator_voice_gen,
- "rvcnarrator_voice_gen": rvcnarrator_voice_gen,
- "rvcnarrator_pitch": rvcnarrator_pitch,
- "text_not_inside": text_not_inside,
- "language": language,
- "output_file_name": output_file_name,
- "output_file_timestamp": output_file_timestamp,
- "autoplay": autoplay,
- "autoplay_volume": autoplay_volume,
- "streaming": streaming,
- "speed": speed,
- "temperature": temperature,
- "repetition_penalty": repetition_penalty,
- "pitch": pitch,
- }
- JSONresult = validate_json_input(json_input_data)
- if JSONresult is None:
- pass
- else:
- return JSONResponse(content={"error": JSONresult}, status_code=400)
-
- if debug_tts or debug_tts_variables:
- debug_lines = [
- f"[{branding}Debug] *******************************************************",
- f"[{branding}Debug] /api/tts_generate function called (debug_tts_variables)",
- f"[{branding}Debug] **** POST validation checks ****",
- f"[{branding}Debug] *******************************************************",
- f"[{branding}Debug] Defaults will be pulled from Settings if not specified in API Request!",
- f"[{branding}Debug] max_characters : {api_defaults['api_max_characters']}",
- f"[{branding}Debug] length_stripping : {api_defaults['api_length_stripping']}",
- f"[{branding}Debug] legacy_api : {api_defaults['api_use_legacy_api']}",
- f"[{branding}Debug] legacy_api IPadd : {api_defaults['api_legacy_ip_address']}",
- f"[{branding}Debug] allowed_filter : {api_defaults['api_allowed_filter']}",
- f"[{branding}Debug] text_filtering : {text_filtering}",
- f"[{branding}Debug] character_voice_gen : {character_voice_gen}",
- f"[{branding}Debug] rvccharacter_voice_gen : {rvccharacter_voice_gen}",
- f"[{branding}Debug] rvccharacter_pitch : {rvccharacter_pitch}",
- f"[{branding}Debug] narrator_enabled : {narrator_enabled}",
- f"[{branding}Debug] narrator_voice_gen : {narrator_voice_gen}",
- f"[{branding}Debug] rvcnarrator_voice_gen : {rvcnarrator_voice_gen}",
- f"[{branding}Debug] rvcnarrator_pitch : {rvcnarrator_pitch}",
- f"[{branding}Debug] text_not_inside : {text_not_inside}",
- f"[{branding}Debug] language : {language}",
- f"[{branding}Debug] output_file_name : {output_file_name}",
- f"[{branding}Debug] output_file_timestamp : {output_file_timestamp}",
- f"[{branding}Debug] autoplay : {autoplay}",
- f"[{branding}Debug] autoplay_volume : {autoplay_volume}",
- f"[{branding}Debug] streaming : {streaming}",
- f"[{branding}Debug] speed : {speed}",
- f"[{branding}Debug] temperature : {temperature}",
- f"[{branding}Debug] repetition_penalty : {repetition_penalty}",
- f"[{branding}Debug] pitch : {pitch}"
- ]
- debug_info = "\n".join(debug_lines)
- print(debug_info)
- if narrator_enabled.lower() == "silent" and text_not_inside.lower() == "silent":
- print(f"[{branding}GEN] \033[92mBoth Narrator & Text-not-inside are set to \033[91msilent\033[92m. If you get no TTS, this is why.\033[0m")
- if narrator_enabled.lower() in ["true", "silent"]:
- if model_engine.lowvram_enabled and model_engine.device == "cpu":
- await model_engine.handle_lowvram_change()
- model_engine.tts_narrator_generatingtts = True
- print(f"[{branding}Debug] Moved into Narrator") if debug_tts else None
- print(f"[{branding}Debug] original text : {text_input}") if debug_tts else None
- processed_parts = process_text(text_input)
- audio_files_all_paragraphs = []
- for part_type, part in processed_parts:
- # Skip parts that are too short
- if len(part.strip()) <= int(params["api_def"]["api_length_stripping"]):
- continue
- # Determine the voice to use based on the part type
- if part_type == 'narrator':
- if narrator_enabled.lower() == "silent":
- print(f"[{branding}GEN] \033[95mNarrator Silent:\033[0m", part) # Green
- continue # Skip generating audio for narrator parts if set to "silent"
- voice_to_use = narrator_voice_gen
- print(f"[{branding}GEN] \033[92mNarrator:\033[0m", part) # Purple
- elif part_type == 'character':
- voice_to_use = character_voice_gen
- print(f"[{branding}GEN] \033[36mCharacter:\033[0m", part) # Yellow
- else:
- # Handle ambiguous parts based on user preference
- if text_not_inside == "silent":
- print(f"[{branding}GEN] \033[95mText-not-inside Silent:\033[0m", part) # Purple
- continue # Skip generating audio for ambiguous parts if set to "silent"
- voice_to_use = character_voice_gen if text_not_inside == "character" else narrator_voice_gen
- voice_description = "\033[36mCharacter (Text-not-inside)\033[0m" if text_not_inside == "character" else "\033[92mNarrator (Text-not-inside)\033[0m"
- print(f"[{branding}GEN] {voice_description}:", part)
- # 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(rf'{api_defaults["api_allowed_filter"]}', '', cleaned_part)
- print(f"[{branding}Debug] text after api_allowed: {text_input}") if debug_tts else None
- # Remove all newline characters (single or multiple)
- cleaned_part = re.sub(r'\n+', ' ', cleaned_part)
- output_file = this_dir / output_directory / f'{output_file_name}_{uuid.uuid4()}_{int(time.time())}.{model_engine.audio_format}'
- output_file_str = output_file.as_posix()
- response = await generate_audio(cleaned_part, voice_to_use, language,temperature, repetition_penalty, speed, pitch, output_file_str, streaming)
- audio_path = output_file_str
- if rvc_settings["rvc_enabled"]:
- if part_type == 'character':
- process_rvc_narrator(part_type, rvccharacter_voice_gen, rvccharacter_pitch, rvc_settings["rvc_char_model_file"], output_file, infer_pipeline)
- elif part_type == 'narrator':
- process_rvc_narrator(part_type, rvcnarrator_voice_gen, rvcnarrator_pitch, rvc_settings["rvc_narr_model_file"], output_file, infer_pipeline)
- else:
- if text_not_inside == 'character':
- process_rvc_narrator('character', rvccharacter_voice_gen, rvccharacter_pitch, rvc_settings["rvc_char_model_file"], output_file, infer_pipeline)
- elif text_not_inside == 'narrator':
- process_rvc_narrator('narrator', rvcnarrator_voice_gen, rvcnarrator_pitch, rvc_settings["rvc_narr_model_file"], output_file, infer_pipeline)
- print(f"[{branding}Debug] Appending audio path to list") if debug_tts else None
- audio_files_all_paragraphs.append(audio_path)
- # Combine audio files across paragraphs
- model_engine.tts_narrator_generatingtts = False
- print(f"[{branding}Debug] Narrator sending to combine") if debug_tts else None
- output_file_path, output_file_url, output_cache_url = combine(output_file_timestamp, output_file_name, audio_files_all_paragraphs)
- # Transcode audio if necessary
- model_audio_format = str(model_engine.audio_format).lower()
- output_audio_format = str(params["transcode_audio_format"]).lower()
- if output_audio_format == "disabled":
- pass
- else:
- if model_audio_format != output_audio_format and not model_engine.tts_narrator_generatingtts:
- print(f"[{branding}Debug] Pre Transcode call - Output file: {output_file_path}") if debug_tts else None
- output_file_path, output_file_url, output_cache_url = await transcode_audio_if_necessary(output_file_path, model_audio_format, output_audio_format)
- print(f"[{branding}Debug] Post Transcode call - Output file: {output_file_path}") if debug_tts else None
- if sounddevice_installed == False:
- autoplay = False
- if autoplay:
- play_audio(output_file_path, autoplay_volume)
- # Move model back to cpu system ram if needed.
- if model_engine.lowvram_enabled and model_engine.device == "cuda" and model_engine.lowvram_capable:
- await model_engine.handle_lowvram_change()
- return JSONResponse(content={"status": "generate-success", "output_file_path": str(output_file_path), "output_file_url": str(output_file_url), "output_cache_url": str(output_cache_url)}, status_code=200)
- else:
- print(f"[{branding}Debug] Moved into Standard generation") if debug_tts else None
- print(f"[{branding}Debug] original text : {text_input}") if debug_tts else None
- if output_file_timestamp:
- timestamp = int(time.time())
- # Generate a standard UUID
- original_uuid = uuid.uuid4()
- # Hash the UUID using SHA-256
- hash_object = hashlib.sha256(str(original_uuid).encode())
- hashed_uuid = hash_object.hexdigest()
- # Truncate to the desired length, for example, 16 characters
- short_uuid = hashed_uuid[:5]
- output_file_path = this_dir / output_directory / f'{output_file_name}_{timestamp}{short_uuid}.{model_engine.audio_format}'
- #Legacy API or New API return
- if params["api_def"]["api_use_legacy_api"]:
- output_file_url = f'http://{api_defaults["api_legacy_ip_address"]}:{api_defaults["api_port_number"]}/audio/{output_file_name}_{timestamp}{short_uuid}.{model_engine.audio_format}'
- output_cache_url = f'http://{api_defaults["api_legacy_ip_address"]}:{api_defaults["api_port_number"]}/audiocache/{output_file_name}_{timestamp}{short_uuid}.{model_engine.audio_format}'
- else:
- output_file_url = f'/audio/{output_file_name}_{timestamp}{short_uuid}.{model_engine.audio_format}'
- output_cache_url = f'/audiocache/{output_file_name}_{timestamp}{short_uuid}.{model_engine.audio_format}'
- else:
- output_file_path = this_dir / output_directory / f"{output_file_name}.{model_engine.audio_format}"
- # Legacy API or New API return
- if params["api_def"]["api_use_legacy_api"]:
- output_file_url = f'http://{api_defaults["api_legacy_ip_address"]}:{api_defaults["api_port_number"]}/audio/{output_file_name}.{model_engine.audio_format}'
- output_cache_url = f'http://{api_defaults["api_legacy_ip_address"]}:{api_defaults["api_port_number"]}/audiocache/{output_file_name}.{model_engine.audio_format}'
- else:
- output_file_url = f'/audio/{output_file_name}.{model_engine.audio_format}'
- output_cache_url = f'/audiocache/{output_file_name}.{model_engine.audio_format}'
- if text_filtering == "html":
- cleaned_string = html.unescape(standard_filtering(text_input))
- cleaned_string = re.sub(r'([!?.])\1+', r'\1', cleaned_string)
- # Further clean to remove any other unwanted characters
- cleaned_string = re.sub(rf'{api_defaults["api_allowed_filter"]}', '', cleaned_string)
- print(f"[{branding}Debug] HTML Filtering - text after api_allowed: {cleaned_string}") if debug_tts else None
- # Remove all newline characters (single or multiple)
- cleaned_string = re.sub(r'\n+', ' ', cleaned_string)
- elif text_filtering == "standard":
- cleaned_string = re.sub(r'([!?.])\1+', r'\1', text_input)
- # Further clean to remove any other unwanted characters
- cleaned_string = re.sub(rf'{api_defaults["api_allowed_filter"]}', '', cleaned_string)
- print(f"[{branding}Debug] Standard Filtering - text after api_allowed: {cleaned_string}") if debug_tts else None
- # Remove all newline characters (single or multiple)
- cleaned_string = re.sub(r'\n+', ' ', cleaned_string)
- else:
- print(f"[{branding}Debug] No filtering text : {text_input}") if debug_tts else None
- cleaned_string = text_input
- print(f"[{branding}GEN]", cleaned_string)
- print(f"[{branding}Debug] Sending request to generate_audio in tts_server.py") if debug_tts else None
- try:
- if streaming:
- response = await generate_audio(cleaned_string, character_voice_gen, language, temperature, repetition_penalty, speed, pitch, output_file_path, streaming)
- return StreamingResponse(response, media_type="audio/wav")
- else:
- response = await generate_audio(cleaned_string, character_voice_gen, language, temperature, repetition_penalty, speed, pitch, output_file_path, streaming)
- if rvc_settings["rvc_enabled"]:
- if rvccharacter_voice_gen.lower() in ["disabled", "disable"]:
- print(f"[{branding}Debug] Pass rvccharacter_voice_gen") if debug_tts else None
- pass # Skip RVC processing for character part
- else:
- print(f"[{branding}Debug] send to rvc") if debug_tts else None
- rvccharacter_voice_gen = this_dir / "models" / "rvc_voices" / rvccharacter_voice_gen
- pth_path = rvccharacter_voice_gen if rvccharacter_voice_gen else rvc_settings["rvc_char_model_file"]
- run_rvc(output_file_path, pth_path, rvccharacter_pitch, infer_pipeline)
- # Transcode audio if necessary
- model_audio_format = str(model_engine.audio_format).lower()
- output_audio_format = str(params["transcode_audio_format"]).lower()
- print(f"[{branding}Debug] At Model audio format") if debug_tts else None
- if output_audio_format == "disabled":
- pass
- else:
- if model_audio_format != output_audio_format and not model_engine.tts_narrator_generatingtts:
- print(f"[{branding}Debug] Pre Transcode call - Output file: {output_file_path}") if debug_tts else None
- output_file_path, output_file_url, output_cache_url = await transcode_audio_if_necessary(output_file_path, model_audio_format, output_audio_format)
- print(f"[{branding}Debug] Post Transcode call - Output file: {output_file_path}") if debug_tts else None
- if sounddevice_installed == False:
- autoplay = False
- if autoplay:
- play_audio(output_file_path, autoplay_volume)
- return JSONResponse(content={"status": "generate-success", "output_file_path": str(output_file_path), "output_file_url": str(output_file_url), "output_cache_url": str(output_cache_url)}, status_code=200)
- except Exception as e:
- return JSONResponse(content={"status": "generate-failure", "error": "An error occurred"}, status_code=500)
- except Exception as e:
- return JSONResponse(content={"status": "generate-failure", "error": "An error occurred"}, status_code=500)
-
-#############################
-#### Word Add-in Sharing ####
-#############################
-# Mount the static files from the 'word_addin' directory
-app.mount("/api/word_addin", StaticFiles(directory=os.path.join(this_dir / 'system' / 'word_addin')), name="word_addin")
-
-#############################################
-#### TTS Generator Comparision Endpoints ####
-#############################################
-import aiofiles
-class TTSItem(BaseModel):
- id: int
- fileUrl: str
- text: str
- characterVoice: str
- language: str
-
-class TTSData(BaseModel):
- ttsList: List[TTSItem]
-
-@app.post("/api/save-tts-data")
-async def apifunction_save_tts_data(tts_data: List[TTSItem]):
- # Convert the list of Pydantic models to a list of dictionaries
- tts_data_list = [item.dict() for item in tts_data]
- # Serialize the list of dictionaries to a JSON string
- tts_data_json = json.dumps(tts_data_list, indent=4)
- async with aiofiles.open(this_dir / output_directory / "ttsList.json", 'w') as f:
- await f.write(tts_data_json)
- return {"message": "Data saved successfully"}
-
-########################################
-# Trigger TTS Gen Text/Speech Analysis #
-########################################
-@app.get("/api/trigger-analysis")
-async def apifunction_trigger_analysis(threshold: int = Query(default=98)):
- venv_path = sys.prefix
- env = os.environ.copy()
- env["PATH"] = os.path.join(venv_path, "bin") + ":" + env["PATH"]
- ttslist_path = this_dir / output_directory / "ttsList.json"
- wavfile_path = this_dir / output_directory
- subprocess.run(["python", "tts_diff.py", f"--threshold={threshold}", f"--ttslistpath={ttslist_path}", f"--wavfilespath={wavfile_path}"], cwd=this_dir / "system" / "tts_diff", env=env)
- # Read the analysis summary
- try:
- with open(this_dir / output_directory / "analysis_summary.json", "r") as summary_file:
- summary_data = json.load(summary_file)
- except FileNotFoundError:
- summary_data = {"error": "Analysis summary file not found."}
- return {"message": "Analysis Completed", "summary": summary_data}
-
-###########################################
-# TTS Generator SRT Subtitiles generation #
-###########################################
-@app.get("/api/srt-generation")
-async def apifunction_srt_generation():
- venv_path = sys.prefix
- env = os.environ.copy()
- env["PATH"] = os.path.join(venv_path, "bin") + ":" + env["PATH"]
- ttslist_path = this_dir / output_directory / "ttsList.json"
- wavfile_path = this_dir / output_directory
- subprocess.run(["python", "tts_srt.py", f"--ttslistpath={ttslist_path}", f"--wavfilespath={wavfile_path}"], cwd=this_dir / "system" / "tts_srt", env=env)
- srt_file_path = this_dir / output_directory / "subtitles.srt"
- if not srt_file_path.exists():
- raise HTTPException(status_code=404, detail="Subtitle file not found.")
- return FileResponse(path=srt_file_path, filename="subtitles.srt", media_type='application/octet-stream')
-
-#################################
-# Static Mount for file serving #
-#################################
-app.mount("/static", StaticFiles(directory=str(this_dir / "system")), name="static")
-
-########################################
-# Legacy JSON update settings function #
-########################################
-# Setup logging
-logging.basicConfig(level=logging.DEBUG)
-def get_json_data():
- with open(this_dir / "confignew.json", "r") as json_file:
- data = json.load(json_file)
- return data
-
-@app.get("/settings")
-async def get_settings(request: Request):
- data = get_json_data()
- return templates.TemplateResponse("admin.html", {"request": request, "data": data})
-
-@app.get("/settings-json")
-async def get_settings_json():
- return get_json_data()
-
-@app.post("/update-settings")
-async def update_settings(
- request: Request,
- delete_output_wavs: str = Form(...),
- gradio_interface: str = Form(...),
- gradio_port_number: int = Form(...),
- api_port_number: int = Form(...),
-):
- data = get_json_data()
- # Update the settings based on the form values
- data["delete_output_wavs"] = delete_output_wavs
- data["gradio_interface"] = gradio_interface.lower() == 'true'
- data["gradio_port_number"] = gradio_port_number
- data["api_def"]["api_port_number"] = api_port_number
- # Save the updated settings back to the JSON file
- with open(this_dir / "confignew.json", "w") as json_file:
- json.dump(data, json_file, indent=4)
- # Redirect to the settings page to display the updated settings
- return templates.TemplateResponse("admin.html", {"request": request, "data": data})
-
-# Create an instance of Jinja2Templates for rendering HTML templates
-templates = Jinja2Templates(directory=this_dir / "system")
-# Get the admin interface template
-template = templates.get_template("admin.html")
-# Render the template with the dynamic values
-rendered_html = template.render(params=params)
-
-###################################################
-#### Webserver Startup & Initial model Loading ####
-###################################################
-@app.get("/")
-async def read_root():
- return HTMLResponse(content=rendered_html, status_code=200)
-
-# Start Uvicorn Webserver
-port_parameter = int(params["api_def"]["api_port_number"])
-
-if __name__ == "__main__":
- import uvicorn
-
- # Start Uvicorn Webserver
- uvicorn_server = uvicorn.run(app, host="0.0.0.0", port=port_parameter, log_level="debug")
-
+import os
+import sys
+import json
+import time
+import librosa
+import logging
+import importlib
+import subprocess
+from pathlib import Path
+logging.disable(logging.WARNING)
+#####################
+# Webserver Imports #
+#####################
+from contextlib import asynccontextmanager
+from fastapi.staticfiles import StaticFiles
+from fastapi.templating import Jinja2Templates
+from fastapi.middleware.cors import CORSMiddleware
+from fastapi import FastAPI, Form, Request, Response, Depends, HTTPException, Query
+from fastapi.responses import JSONResponse, HTMLResponse, RedirectResponse, FileResponse, StreamingResponse
+##########################
+# Generation API Imports #
+##########################
+import re
+import html
+import uuid
+import hashlib
+import numpy as np
+import soundfile as sf
+from typing import Union, Dict, List
+from pydantic import BaseModel, ValidationError, Field, field_validator
+########################################################################################
+# START-UP # Silence RVC warning about torch.nn.utils.weight_norm even though not used #
+########################################################################################
+import warnings
+warnings.filterwarnings("ignore", category=UserWarning, module="torch.nn.utils.weight_norm")
+warnings.filterwarnings("ignore", category=UserWarning, module="torch.nn.functional", lineno=5476)
+####################
+# Setup local path #
+####################
+this_dir = Path(__file__).parent.resolve() # Set this_dir as the current alltalk_tts folder
+
+####################################################
+# Load params and api_defailts from confignew.json #
+####################################################
+def load_config():
+ global branding, output_directory, debug_transcode, debug_tts, debug_tts_variables, engines_available, engine_loaded, debug_rvc, debug_concat, debug_openai
+ # Define the path to the confignew.json file & load in api_defaults & params
+ configfile_path = this_dir / "confignew.json"
+ with open(configfile_path, "r") as configfile:
+ configfile_data = json.load(configfile)
+ api_defaults = {
+ "api_text_filtering": configfile_data["api_def"]["api_text_filtering"],
+ "api_narrator_enabled": configfile_data["api_def"]["api_narrator_enabled"],
+ "api_text_not_inside": configfile_data["api_def"]["api_text_not_inside"],
+ "api_language": configfile_data["api_def"]["api_language"],
+ "api_output_file_name": configfile_data["api_def"]["api_output_file_name"],
+ "api_output_file_timestamp": configfile_data["api_def"]["api_output_file_timestamp"],
+ "api_autoplay": configfile_data["api_def"]["api_autoplay"],
+ "api_autoplay_volume": configfile_data["api_def"]["api_autoplay_volume"],
+ "api_port_number": configfile_data["api_def"]["api_port_number"],
+ "api_allowed_filter": configfile_data["api_def"]["api_allowed_filter"],
+ "api_length_stripping": configfile_data["api_def"]["api_length_stripping"],
+ "api_max_characters": configfile_data["api_def"]["api_max_characters"],
+ "api_use_legacy_api": configfile_data["api_def"]["api_use_legacy_api"],
+ "api_legacy_ip_address": configfile_data["api_def"]["api_legacy_ip_address"],
+ }
+ rvc_settings = {
+ "rvc_enabled": configfile_data["rvc_settings"]["rvc_enabled"],
+ "rvc_char_model_file": configfile_data["rvc_settings"]["rvc_char_model_file"],
+ "rvc_narr_model_file": configfile_data["rvc_settings"]["rvc_narr_model_file"],
+ "split_audio": configfile_data["rvc_settings"]["split_audio"],
+ "autotune": configfile_data["rvc_settings"]["autotune"],
+ "pitch": configfile_data["rvc_settings"]["pitch"],
+ "filter_radius": configfile_data["rvc_settings"]["filter_radius"],
+ "index_rate": configfile_data["rvc_settings"]["index_rate"],
+ "rms_mix_rate": configfile_data["rvc_settings"]["rms_mix_rate"],
+ "protect": configfile_data["rvc_settings"]["protect"],
+ "hop_length": configfile_data["rvc_settings"]["hop_length"],
+ "f0method": configfile_data["rvc_settings"]["f0method"],
+ "embedder_model": configfile_data["rvc_settings"]["embedder_model"],
+ "training_data_size": configfile_data["rvc_settings"]["training_data_size"],
+ }
+ branding = configfile_data.get("branding", "")
+ debug_transcode = configfile_data.get("debugging").get("debug_transcode")
+ debug_tts = configfile_data.get("debugging").get("debug_tts")
+ debug_tts_variables = configfile_data.get("debugging").get("debug_tts_variables")
+ debug_rvc = configfile_data.get("debugging").get("debug_rvc")
+ debug_concat = configfile_data.get("debugging").get("debug_concat")
+ debug_openai = configfile_data.get("debugging").get("debug_openai")
+ output_directory = this_dir / configfile_data.get("output_folder", "")
+ output_directory.mkdir(parents=True, exist_ok=True)
+ tts_engines_file = os.path.join(this_dir, "system", "tts_engines", "tts_engines.json")
+ with open(tts_engines_file, "r") as f:
+ tts_engines_data = json.load(f)
+ engines_available = [engine["name"] for engine in tts_engines_data["engines_available"]] # List of the available TTS engines from tts_engines.json
+ engine_loaded = tts_engines_data["engine_loaded"] # The currently set TTS engine from tts_engines.json
+ selected_model = tts_engines_data["selected_model"]
+ # Conditionally import infer_pipeline if rvc_enabled is true
+ if rvc_settings["rvc_enabled"]:
+ from system.tts_engines.rvc.infer.infer import infer_pipeline
+ else:
+ infer_pipeline = None
+ return configfile_data, api_defaults, rvc_settings, infer_pipeline
+
+# Call to load in the params and api_defaults
+params, api_defaults, rvc_settings, infer_pipeline = load_config()
+
+####################
+# Check for FFMPEG #
+####################
+def check_ffmpeg(this_dir):
+ if sys.platform == "win32":
+ # Check for ffmpeg.exe in the specified directory on Windows
+ ffmpeg_path = os.path.join(this_dir, "system", "win_ffmpeg", "ffmpeg.exe")
+ if os.path.exists(ffmpeg_path):
+ return True
+ else:
+ # Check for FFmpeg on Linux and macOS using the command line
+ try:
+ subprocess.run(['ffmpeg', '-version'], stdout=subprocess.PIPE, stderr=subprocess.PIPE, check=True)
+ return True
+ except (subprocess.CalledProcessError, FileNotFoundError):
+ pass
+ return False
+
+# Check if FFmpeg is installed
+ffmpeg_installed = check_ffmpeg(this_dir)
+
+if not ffmpeg_installed:
+ print(f"[{branding}ENG] \033[92mTranscoding :\033[94m ffmpeg not found\033[0m")
+ print(f"[{branding}ENG] FFmpeg is not installed. Transcoding will be disabled.")
+ print(f"[{branding}ENG] Please install FFmpeg on your system.")
+
+ if sys.platform == "win32":
+ print(f"[{branding}ENG] \033[92mTranscoding :\033[94m ffmpeg not found\033[0m")
+ print(f"[{branding}ENG] Installation instructions for Windows:")
+ print(f"[{branding}ENG] Copy the 'ffmpeg.exe' file to '{os.path.join(this_dir, 'system', 'win_ffmpeg')}'")
+ else:
+ print(f"[{branding}ENG] \033[92mTranscoding :\033[94m ffmpeg not found\033[0m")
+ print(f"[{branding}ENG] Installation instructions:")
+ print(f"[{branding}ENG] Linux (Debian-based systems): Run 'sudo apt-get install ffmpeg' in the terminal.")
+ print(f"[{branding}ENG] macOS: Run 'brew install ffmpeg' in the terminal (requires Homebrew).")
+ # You can choose to exit the script or continue without FFmpeg
+ # exit(1)
+
+if ffmpeg_installed:
+ from ffmpeg.asyncio import FFmpeg
+ print(f"[{branding}ENG] \033[92mTranscoding :\033[93m ffmpeg found\033[0m")
+
+################################
+# Check for portaudio on Linux #
+################################
+try:
+ import sounddevice as sd
+ sounddevice_installed=True
+except OSError:
+ print(f"[{branding}Startup] \033[91mInfo\033[0m PortAudio library not found. If you wish to play TTS in standalone mode through the API suite")
+ print(f"[{branding}Startup] \033[91mInfo\033[0m please install PortAudio. This will not affect any other features or use of Alltalk.")
+ print(f"[{branding}Startup] \033[91mInfo\033[0m If you don't know what the API suite is, then this message is nothing to worry about.")
+ sounddevice_installed=False
+ if sys.platform.startswith('linux'):
+ print(f"[{branding}Startup] \033[91mInfo\033[0m On Linux, you can use the following command to install PortAudio:")
+ print(f"[{branding}Startup] \033[91mInfo\033[0m sudo apt-get install portaudio19-dev")
+
+#######################################################################
+# Attempt to import the ModelLoader class for the selected TTS engine #
+#######################################################################
+if engine_loaded in engines_available:
+ loader_module = importlib.import_module(f"system.tts_engines.{engine_loaded}.model_engine")
+ tts_class = getattr(loader_module, "tts_class")
+ # Setup model_engine as the way to call the functions within the Class.
+ model_engine = tts_class()
+else:
+ raise ValueError(f"Invalid TTS engine: {engine_loaded}")
+
+##########################################
+# Run setup function in the model_engine #
+##########################################
+@asynccontextmanager
+async def startup_shutdown(no_actual_value_it_demanded_something_be_here):
+ try:
+ await model_engine.setup()
+ except FileNotFoundError as e:
+ print(f"Error during setup: {e}. Continuing without the TTS model.")
+ yield
+ # Shutdown logic
+
+###############################
+# Setup FastAPI with Lifespan #
+###############################
+app = FastAPI(lifespan=startup_shutdown)
+# Allow all origins, and set other CORS options
+app.add_middleware(
+ CORSMiddleware,
+ allow_origins=["*"], # Set this to the specific origins you want to allow
+ allow_credentials=True,
+ allow_methods=["*"],
+ allow_headers=["*"],
+)
+
+##############################
+# API Endpoint - /api/reload #
+##############################
+model_currently_changing = False
+@app.route("/api/reload", methods=["POST"])
+async def apifunction_reload(request: Request):
+ global model_currently_changing
+ if model_currently_changing == True:
+ return Response(content=json.dumps({"status": "model-is currently changing"}), media_type="application/json")
+ model_currently_changing == True
+ requested_model = request.query_params.get("tts_method")
+ if requested_model not in model_engine.available_models:
+ model_currently_changing == False
+ return {"status": "error", "message": "Invalid TTS method specified"}
+ # Call the handle_tts_method_change method from the imported class
+ success = await model_engine.handle_tts_method_change(requested_model)
+ if success:
+ model_engine.current_model_loaded = requested_model
+ model_currently_changing == False
+ return Response(content=json.dumps({"status": "model-success"}), media_type="application/json")
+ else:
+ model_currently_changing == False
+ return Response(content=json.dumps({"status": "model-failure"}), media_type="application/json")
+
+####################################
+# API Endpoint - /api/enginereload #
+####################################
+import asyncio
+uvicorn_server = None
+def restart_self():
+ """Restart the current script."""
+ #print(f"[{branding}ENG] Restarting subprocess...")
+ os.execv(sys.executable, ['python'] + sys.argv)
+
+async def handle_restart():
+ global uvicorn_server
+ if uvicorn_server:
+ print(f"[{branding}ENG] Stopping uvicorn server...")
+ uvicorn_server.should_exit = True
+ uvicorn_server.force_exit = True
+ # Check if the server has stopped
+ while not uvicorn_server.is_stopped:
+ await asyncio.sleep(0.1) # Check every 100ms if server is stopped
+ restart_self()
+
+@app.post("/api/enginereload")
+async def apifunction_reload(request: Request):
+ requested_engine = request.query_params.get("engine")
+ if requested_engine not in engines_available:
+ return {"status": "error", "message": "Invalid TTS engine specified"}
+ print(f"[{branding}ENG]")
+ print(f"[{branding}ENG] \033[94mChanging model loaded. Please wait.\033[00m")
+ print(f"[{branding}ENG]")
+ # Update the tts_engines.json file
+ tts_engines_file = os.path.join(this_dir, "system", "tts_engines", "tts_engines.json")
+ with open(tts_engines_file, "r") as f:
+ tts_engines_data = json.load(f)
+ for engine in tts_engines_data["engines_available"]:
+ if engine["name"] == requested_engine:
+ tts_engines_data["engine_loaded"] = requested_engine
+ tts_engines_data["selected_model"] = engine["selected_model"]
+ break
+ with open(tts_engines_file, "w") as f:
+ json.dump(tts_engines_data, f)
+ #print(f"[{branding}ENG] Reloading the engine...")
+ # Start the restart process in the background
+ asyncio.create_task(handle_restart())
+ # Never goes past the above asyncio as the script is killed at that point
+ return Response(content=json.dumps({"status": "engine-success"}), media_type="application/json")
+
+#######################################
+# API Endpoint - /api/stop-generation #
+#######################################
+# When this endpoint it called it will set tts_stop_generation in the model_engine to True, which can be used to interrupt the current TTS generation.
+@app.put("/api/stop-generation")
+async def apifunction_stop_generation():
+ if model_engine.tts_generating_lock and not model_engine.tts_stop_generation:
+ model_engine.tts_stop_generation = True
+ return {"message": "Cancelling current TTS generation"}
+
+#############################
+# API Endpoint - /api/audio #
+#############################
+# Gives web access to the output files as downloads
+@app.get("/audio/{filename}")
+async def apifunction_get_audio(filename: str):
+ audio_path = this_dir / output_directory / filename
+ return FileResponse(audio_path)
+
+##################################
+# API Endpoint - /api/audiocache #
+##################################
+@app.get("/audiocache/{filename}")
+async def apifunction_get_audio(filename: str):
+ audio_path = Path({output_directory}) / filename
+ if not audio_path.is_file():
+ raise HTTPException(status_code=404, detail="File not found")
+ response = FileResponse(path=audio_path, media_type='audio/wav', filename=filename)
+ # Set caching headers
+ response.headers["Cache-Control"] = "public, max-age=604800" # Cache for one week
+ response.headers["ETag"] = str(audio_path.stat().st_mtime) # Use the file's last modified time as a simple ETag
+ return response
+
+##############################
+# API Endpoint - /api/voices #
+##############################
+@app.get("/api/voices")
+async def apifunction_get_voices():
+ if not model_engine.multivoice_capable:
+ return {"status": "error", "message": f"The currently loaded TTS engine '{model_engine.engine_loaded}' does not support multiple voices."}
+
+ available_voices = model_engine.voices_file_list()
+ return {"status": "success", "voices": available_voices}
+
+#################################
+# API Endpoint - /api/rvcvoices #
+#################################
+@app.get("/api/rvcvoices")
+async def apifunction_get_voices():
+ global params, api_defaults, rvc_settings, infer_pipeline
+ params, api_defaults, rvc_settings, infer_pipeline = load_config()
+ if not rvc_settings["rvc_enabled"]:
+ return {"status": "success", "rvcvoices": ["Disabled"]}
+ # Define the directory path
+ directory = os.path.join(this_dir, "models", "rvc_voices")
+ # Check if the directory exists
+ if not os.path.exists(directory):
+ return {"status": "success", "rvcvoices": ["Disabled"]}
+ # List all .pth files recursively in the directory
+ pth_files = []
+ for root, dirs, files in os.walk(directory):
+ for file in files:
+ if file.endswith(".pth"):
+ pth_files.append(os.path.relpath(os.path.join(root, file), start=directory))
+ # If no .pth files found, return "Disabled"
+ if not pth_files:
+ return {"status": "success", "rvcvoices": ["Disabled"]}
+ # Sort the list alphabetically
+ pth_files.sort()
+ # Add "Disabled" at the beginning of the list
+ pth_files.insert(0, "Disabled")
+ return {"status": "success", "rvcvoices": pth_files}
+
+#####################################
+# API Endpoint - /api/reload_config #
+#####################################
+@app.get("/api/reload_config")
+async def apifunction_reload_config():
+ global params, api_defaults, rvc_settings, infer_pipeline
+ model_engine.available_models = model_engine.scan_models_folder()
+ params, api_defaults, rvc_settings, infer_pipeline = load_config()
+ return Response("Config file reloaded successfully")
+
+#############################
+# API Endpoint - /api/ready #
+#############################
+@app.get("/api/ready")
+async def apifunction_ready():
+ if model_engine.setup_has_run:
+ return Response("Ready")
+ else:
+ return Response("Unloaded")
+
+#######################################
+# API Endpoint - /api/currentsettings #
+#######################################
+@app.get('/api/currentsettings')
+def apifunction_get_current_settings():
+ settings = {
+ "engines_available": engines_available, # Lists the currently available TTS engines
+ "current_engine_loaded": model_engine.engine_loaded, # Lists the currently loaded TTS engine
+ "models_available": [{"name": name} for name in model_engine.available_models.keys()], # Lists the currently available models that can be loaded on this TTS engine.
+ "current_model_loaded": model_engine.current_model_loaded, # Lists the currently loaded model
+ "manufacturer_name": model_engine.manufacturer_name, # The name of the person/company/group who made the current TTS engine
+ "audio_format": model_engine.audio_format, # The default audio format the the currently loaded TTS engine generates in
+ "deepspeed_capable": model_engine.deepspeed_capable, # Is the TTS engine capable of supporting DeepSpeed
+ "deepspeed_available": model_engine.deepspeed_available, # Was DeepSpeed detected as being available for use
+ "deepspeed_enabled": model_engine.deepspeed_enabled, # Is DeepSpeed current enabled or disabled
+ "generationspeed_capable": model_engine.generationspeed_capable, # Does the TTS engine support changing speed of generated TTS
+ "generationspeed_set": model_engine.generationspeed_set, # What is the currently set TTS speed
+ "lowvram_capable": model_engine.lowvram_capable, # Does the TTS engine support moving the model between VRAM and System Ram
+ "lowvram_enabled": model_engine.lowvram_enabled, # What is the currently enabled/disabled state of low vram
+ "pitch_capable": model_engine.pitch_capable, # Does the TTS engine support pitch on tts generation
+ "pitch_set": model_engine.pitch_set, # What is the currently set value for pitch
+ "repetitionpenalty_capable": model_engine.repetitionpenalty_capable, # Does the TTS engine support differnt repitition penalties
+ "repetitionpenalty_set": model_engine.repetitionpenalty_set, # What is the currently set repetition penalty
+ "streaming_capable": model_engine.streaming_capable, # Does the model support streaming TTS generation
+ "temperature_capable": model_engine.temperature_capable, # Does the TTS engine support model temperature settings
+ "temperature_set": model_engine.temperature_set, # What is the currently set temperature
+ "ttsengines_installed": model_engine.engine_installed, #
+ "languages_capable": model_engine.languages_capable, # Does the TTS engine support multi languages
+ "multivoice_capable": model_engine.multivoice_capable, # Does the TTS engine support multi voice models
+ "multimodel_capable": model_engine.multimodel_capable, # Does the engine support loading many different models
+ }
+ return settings
+
+######################################
+# API Endpoint - /api/lowvramsetting #
+######################################
+@app.post("/api/lowvramsetting")
+async def apifunction_low_vram(request: Request, new_low_vram_value: bool):
+ if not model_engine.lowvram_capable:
+ return {"status": "error", "message": f"The currently loaded TTS engine '{model_engine.engine_loaded}' does not support lowvram."}
+ try:
+ if new_low_vram_value is None:
+ raise ValueError("Missing 'low_vram' parameter")
+ if model_engine.lowvram_enabled == new_low_vram_value:
+ return Response(content=json.dumps({"status": "success", "message": f"[{branding}Model] LowVRAM is already {'enabled' if new_low_vram_value else 'disabled'}.",}))
+ model_engine.lowvram_enabled = new_low_vram_value
+ await model_engine.unload_model()
+
+ if model_engine.cuda_is_available:
+ if model_engine.lowvram_enabled:
+ model_engine.device = "cpu"
+ print(f"[{branding}Engine] \033[94mLowVRAM Enabled.\033[0m Model will move between \033[93mVRAM(cuda) <> System RAM(cpu)\033[0m")
+ else:
+ model_engine.device = "cuda"
+ print(f"[{branding}Engine] \033[94mLowVRAM Disabled.\033[0m Model will stay in \033[93mVRAM(cuda)\033[0m")
+ await model_engine.setup()
+ else:
+ # Handle the case where CUDA is not available
+ print(f"[{branding}Engine] \033[91mError:\033[0m Nvidia CUDA is not available on this system. Unable to use LowVRAM mode.")
+ model_engine.lowvram_enabled = False
+
+ return Response(content=json.dumps({"status": "lowvram-success"}))
+ except Exception as e:
+ return Response(content=json.dumps({"status": "error", "message": str(e)}))
+
+#################################
+# API Endpoint - /api/deepspeed #
+#################################
+@app.post("/api/deepspeed")
+async def deepspeed(request: Request, new_deepspeed_value: bool):
+ if not model_engine.deepspeed_capable or not model_engine.deepspeed_available:
+ return {"status": "error", "message": f"The currently loaded TTS engine '{model_engine.engine_loaded}' does not support DeepSpeed or DeepSpeed is not available on this system."}
+ try:
+ if new_deepspeed_value is None:
+ raise ValueError("Missing 'deepspeed' parameter")
+ if model_engine.deepspeed_enabled == new_deepspeed_value:
+ return Response(
+ content=json.dumps({"status": "success", "message": f"DeepSpeed is already {'enabled' if new_deepspeed_value else 'disabled'}.",}))
+ model_engine.deepspeed_enabled = new_deepspeed_value
+ await model_engine.handle_deepspeed_change(new_deepspeed_value)
+ return Response(content=json.dumps({"status": "deepspeed-success"}))
+ except Exception as e:
+ return Response(content=json.dumps({"status": "error", "message": str(e)}))
+
+#################################
+# API Endpoint - /api/voice2rvc #
+#################################
+@app.post("/api/voice2rvc")
+async def voice2rvc(input_tts_path: str = Form(...), output_rvc_path: str = Form(...), pth_name: str = Form(...), pitch: str = Form(...), method: str = Form(...)):
+ try:
+ # Handle "Disabled" case
+ if pth_name.lower() in ["disabled", "disable"]:
+ print(f"[{branding}TTS] \033[94mVoice2RVC Convert: No voice was specified or the name was Disabled\033[0m")
+ return {"status": "error", "message": "No voice was specified or the name was Disabled"}
+ input_tts_path = Path(input_tts_path)
+ output_rvc_path = Path(output_rvc_path)
+ # Check if input file exists
+ if not input_tts_path.is_file():
+ raise HTTPException(status_code=400, detail=f"Input file {input_tts_path} does not exist.")
+ # Define the model path based on the selected model name
+ pth_path = this_dir / "models" / "rvc_voices" / pth_name
+ # Check if model file exists
+ if not pth_path.is_file():
+ raise HTTPException(status_code=400, detail=f"Model file {pth_path} does not exist.")
+ # Run the RVC conversion
+ result_path = run_voice2rvc(input_tts_path, output_rvc_path, pth_path, pitch, method)
+ if result_path:
+ return {"status": "success", "output_path": str(result_path)}
+ else:
+ raise HTTPException(status_code=500, detail="RVC conversion failed.")
+ except Exception as e:
+ print(f"Error during Voice2RVC conversion: {e}")
+ raise HTTPException(status_code=500, detail=str(e))
+
+# Define the run_rvc function
+def run_voice2rvc(input_tts_path, output_rvc_path, pth_path, pitch, method):
+ print(f"[{branding}GEN] \033[94mVoice2RVC Convert: Started\033[0m")
+ generate_start_time = time.time()
+ f0up_key = pitch
+ filter_radius = rvc_settings["filter_radius"]
+ index_rate = rvc_settings["index_rate"]
+ rms_mix_rate = rvc_settings["rms_mix_rate"]
+ protect = rvc_settings["protect"]
+ hop_length = rvc_settings["hop_length"]
+ f0method = method
+ split_audio = rvc_settings["split_audio"]
+ f0autotune = rvc_settings["autotune"]
+ embedder_model = rvc_settings["embedder_model"]
+ training_data_size = rvc_settings["training_data_size"]
+ # Convert path variables to strings
+ input_tts_path = str(input_tts_path)
+ pth_path = str(pth_path)
+ # Check if the model file exists
+ if not os.path.isfile(pth_path):
+ print(f"Model file {pth_path} does not exist. Exiting.")
+ return
+ # Get the directory of the model file
+ model_dir = os.path.dirname(pth_path)
+ # Find all .index files in the model directory
+ index_files = [file for file in os.listdir(model_dir) if file.endswith(".index")]
+ if len(index_files) == 1:
+ index_path = str(os.path.join(model_dir, index_files[0]))
+ else:
+ index_path = ""
+ # Call the infer_pipeline function
+ infer_pipeline(f0up_key, filter_radius, index_rate, rms_mix_rate, protect, hop_length, f0method,
+ input_tts_path, output_rvc_path, pth_path, index_path, split_audio, f0autotune, embedder_model, training_data_size, debug_rvc)
+ generate_end_time = time.time()
+ generate_elapsed_time = generate_end_time - generate_start_time
+ print(f"[{branding}GEN] \033[94mVoice2RVC Convert: \033[91m{generate_elapsed_time:.2f} seconds.\033[0m")
+ return output_rvc_path
+
+##################################
+# Transcode between file formats #
+##################################
+async def transcode_audio(input_file, output_format):
+ print(f"[{branding}Debug] *************************************************",) if debug_transcode else None
+ print(f"[{branding}Debug] transcode_audio function called (debug_transcode)",) if debug_transcode else None
+ print(f"[{branding}Debug] *************************************************",) if debug_transcode else None
+ print(f"[{branding}Debug] Input file : {input_file}") if debug_transcode else None
+ print(f"[{branding}Debug] Output format : {output_format}") if debug_transcode else None
+ if output_format == "Disabled":
+ print(f"[{branding}Debug] Transcode format is set to Disabled so skipping transcode.") if debug_transcode else None
+ return input_file
+ if not ffmpeg_installed:
+ print(f"[{branding}TTS] FFmpeg is not installed. Format conversion is not possible.")
+ raise Exception("FFmpeg is not installed. Format conversion is not possible.")
+ # Get the file extension of the input file
+ input_extension = os.path.splitext(input_file)[1][1:].lower()
+ print(f"[{branding}Debug] Input file extension: {input_extension}") if debug_transcode else None
+ # Check if the input extension matches the requested output format
+ if input_extension == output_format.lower():
+ print(f"[{branding}Debug] Input file is already in the requested format: {output_format}") if debug_transcode else None
+ return input_file
+ output_file = os.path.splitext(input_file)[0] + f".{output_format}"
+ print(f"[{branding}Debug] Output file: {output_file}") if debug_transcode else None
+ ffmpeg_path = "ffmpeg" # Default path for Linux and macOS
+ if sys.platform == "win32":
+ ffmpeg_path = os.path.join(this_dir, "system", "win_ffmpeg", "ffmpeg.exe")
+ ffmpeg = (FFmpeg(ffmpeg_path).option("y").input(input_file).output(output_file))
+ print(f"[{branding}Debug] Transcoding to {output_format}") if debug_transcode else None
+ if output_format == "opus":
+ print(f"[{branding}Debug] Configuring Opus options") if debug_transcode else None
+ ffmpeg.output(output_file, {"codec:a": "libopus", "b:a": "128k", "vbr": "on", "compression_level": 10, "frame_duration": 60, "application": "voip"})
+ elif output_format == "aac":
+ print(f"[{branding}Debug] Configuring AAC options") if debug_transcode else None
+ ffmpeg.output(output_file, {"codec:a": "aac", "b:a": "192k"})
+ elif output_format == "flac":
+ print(f"[{branding}Debug] Configuring FLAC options") if debug_transcode else None
+ ffmpeg.output(output_file, {"codec:a": "flac", "compression_level": 8})
+ elif output_format == "wav":
+ print(f"[{branding}Debug] Configuring WAV options") if debug_transcode else None
+ ffmpeg.output(output_file, {"codec:a": "pcm_s16le"})
+ elif output_format == "mp3":
+ print(f"[{branding}Debug] Configuring MP3 options") if debug_transcode else None
+ ffmpeg.output(output_file, {"codec:a": "libmp3lame", "b:a": "192k"})
+ else:
+ print(f"[{branding}TTS] Unsupported output format: {output_format}")
+ raise ValueError(f"Unsupported output format: {output_format}")
+ try:
+ print(f"[{branding}Debug] Starting transcoding process") if debug_transcode else None
+ await ffmpeg.execute()
+ print(f"[{branding}Debug] Transcoding completed successfully") if debug_transcode else None
+ except Exception as e:
+ print(f"[{branding}TTS] Error occurred during transcoding: {str(e)}")
+ raise
+ print(f"[{branding}Debug] Deleting original input file") if debug_transcode else None
+ os.remove(input_file)
+ print(f"[{branding}Debug] Transcoding process completed") if debug_transcode else None
+ print(f"[{branding}Debug] Transcoded file: {output_file}") if debug_transcode else None
+ return output_file
+
+##############################
+# Central Transcode function #
+##############################
+async def transcode_audio_if_necessary(output_file, model_audio_format, output_audio_format):
+ if debug_transcode:
+ print(f"[{branding}Debug] model_engine.audio_format is:", model_audio_format)
+ print(f"[{branding}Debug] audio format is:", output_audio_format)
+ print(f"[{branding}Debug] Entering the transcode condition")
+ try:
+ if debug_transcode:
+ print(f"[{branding}Debug] Calling transcode_audio function")
+ output_file = await transcode_audio(output_file, output_audio_format)
+ if debug_transcode:
+ print(f"[{branding}Debug] Transcode completed successfully")
+ except Exception as e:
+ print(f"[{branding}Debug] Error occurred during transcoding:", str(e))
+ raise
+ if debug_transcode:
+ print(f"[{branding}Debug] Transcode condition completed")
+ # Update the output file path and URLs based on the transcoded file
+ if debug_tts:
+ print(f"[{branding}Debug] Updating output file paths and URLs")
+ if params["api_def"]["api_use_legacy_api"]:
+ output_file_url = f'http://{api_defaults["api_legacy_ip_address"]}:{api_defaults["api_port_number"]}/audio/{os.path.basename(output_file)}'
+ output_cache_url = f'http://{api_defaults["api_legacy_ip_address"]}:{api_defaults["api_port_number"]}/audiocache/{os.path.basename(output_file)}'
+ else:
+ output_file_url = f'/audio/{os.path.basename(output_file)}'
+ output_cache_url = f'/audiocache/{os.path.basename(output_file)}'
+ if debug_tts:
+ print(f"[{branding}Debug] Output file paths and URLs updated")
+ return output_file, output_file_url, output_cache_url
+
+##############################
+#### Streaming Generation ####
+##############################
+@app.get("/api/tts-generate-streaming", response_class=StreamingResponse)
+async def apifunction_generate_streaming(text: str, voice: str, language: str, output_file: str):
+ if model_engine.streaming_capable == False:
+ print(f"[{branding}GEN] The selected TTS Engine does not support streaming. To use streaming, please select a TTS")
+ print(f"[{branding}GEN] Engine that has streaming capability. You can find the streaming support information for")
+ print(f"[{branding}GEN] each TTS Engine in the 'Engine Information' section of the Gradio interface.")
+ try:
+ output_file_path = f'{this_dir / output_directory / output_file}.{model_engine.audio_format}'
+ stream = await generate_audio(text, voice, language, model_engine.temperature_set, model_engine.repetitionpenalty_set, 1.0, 1.0, output_file_path, streaming=True)
+ return StreamingResponse(stream, media_type="audio/wav")
+ except Exception as e:
+ print(f"An error occurred: {e}")
+ return JSONResponse(content={"error": "An error occurred"}, status_code=500)
+
+@app.post("/api/tts-generate-streaming", response_class=JSONResponse)
+async def tts_generate_streaming(request: Request, text: str = Form(...), voice: str = Form(...), language: str = Form(...), output_file: str = Form(...)):
+ if model_engine.streaming_capable == False:
+ print(f"[{branding}GEN] The selected TTS Engine does not support streaming. To use streaming, please select a TTS")
+ print(f"[{branding}GEN] Engine that has streaming capability. You can find the streaming support information for")
+ print(f"[{branding}GEN] each TTS Engine in the 'Engine Information' section of the Gradio interface.")
+ try:
+ output_file_path = f'{this_dir / output_directory / output_file}.{model_engine.audio_format}'
+ await generate_audio(text, voice, language, model_engine.temperature_set, model_engine.repetitionpenalty_set, "1.0", "1.0", output_file_path, streaming=False)
+ return JSONResponse(content={"output_file_path": str(output_file)}, status_code=200)
+ except Exception as e:
+ print(f"An error occurred: {e}")
+ return JSONResponse(content={"error": "An error occurred"}, status_code=500)
+
+###################################
+# Central generate_audio function #
+###################################
+async def generate_audio(text, voice, language, temperature, repetition_penalty, speed, pitch, output_file, streaming=False):
+ if model_engine.streaming_capable == False and streaming==True:
+ print(f"[{branding}GEN] The selected TTS Engine does not support streaming. To use streaming, please select a TTS")
+ print(f"[{branding}GEN] Engine that has streaming capability. You can find the streaming support information for")
+ print(f"[{branding}GEN] each TTS Engine in the 'Engine Information' section of the Gradio interface.")
+ # Get the async generator from the internal function
+ response = model_engine.generate_tts(text, voice, language, temperature, repetition_penalty, speed, pitch, output_file, streaming)
+ # If streaming, then return the generator as-is, otherwise just exhaust it and return
+ if streaming:
+ # Return an async generator that yields from the response
+ async def stream_response():
+ async for chunk in response:
+ yield chunk
+ return stream_response()
+ else:
+ # Exhaust the generator and handle any errors
+ try:
+ async for _ in response:
+ pass
+ except Exception as e:
+ print(f"[GEN] Error during audio generation: {str(e)}")
+ raise
+
+###########################
+#### PREVIEW VOICE API ####
+###########################
+@app.post("/api/previewvoice/", response_class=JSONResponse)
+async def apifunction_preview_voice(request: Request, voice: str = Form(...)):
+ try:
+ # Hardcoded settings
+ language = "en"
+ output_file_name = "api_preview_voice"
+ # Clean the voice filename for inclusion in the text
+ clean_voice_filename = re.sub(r'\.wav$', '', voice.replace(' ', '_'))
+ clean_voice_filename = re.sub(r'[^a-zA-Z0-9]', ' ', clean_voice_filename)
+ # Generate the audio
+ text = f"Hello, this is a preview of voice {clean_voice_filename}."
+ # Generate the audio
+ output_file_path = this_dir / output_directory / f'{output_file_name}.{model_engine.audio_format}'
+ await generate_audio(text, voice, language, model_engine.temperature_set, model_engine.repetitionpenalty_set, 1.0, 0, output_file_path, streaming=False)
+ # Generate the URL
+ output_file_url = f'/audio/{output_file_name}.{model_engine.audio_format}'
+ # Return the response with both local file path and URL
+ return JSONResponse(
+ content={
+ "status": "generate-success",
+ "output_file_path": str(output_file_path),
+ "output_file_url": str(output_file_url),
+ },
+ status_code=200,
+ )
+ except Exception as e:
+ print(f"An error occurred: {e}")
+ return JSONResponse(content={"error": "An error occurred"}, status_code=500)
+
+##################################################################
+# API Endpoint - OpenAI Speech API compatable endpoint Validator #
+##################################################################
+class OpenAIInput(BaseModel):
+ model: str = Field(..., description="The TTS model to use. Currently ignored.")
+ input: str = Field(..., max_length=4096, description="The text to generate audio for.")
+ voice: str = Field(..., description="The voice to use when generating the audio.")
+ response_format: str = Field(default="wav", description="The format of the audio. Currently only 'wav' is supported.")
+ speed: float = Field(default=1.0, ge=0.25, le=4.0, description="The speed of the generated audio.")
+
+ @field_validator('voice')
+ def validate_voice(cls, value):
+ supported_voices = ["alloy", "echo", "fable", "nova", "onyx", "shimmer"]
+ if value not in supported_voices:
+ raise ValueError(f'Voice must be one of {supported_voices}')
+ return value
+
+class OpenAIGenerator:
+ @staticmethod
+ def validate_input(json_data: dict) -> Union[None, str]:
+ try:
+ OpenAIInput(**json_data)
+ return None
+ except ValidationError as e:
+ errors = []
+ for err in e.errors():
+ field = err['loc'][0]
+ message = err['msg']
+ description = OpenAIInput.model_fields[field].field_info.description
+ errors.append(f"Error in field '{field}': {message}. Description: {description}")
+ return ', '.join(errors)
+
+#########################################################################
+# API Endpoint - OpenAI Speech API compatable endpoint /v1/audio/speech #
+#########################################################################
+@app.post("/v1/audio/speech", response_class=JSONResponse)
+async def openai_tts_generate(request: Request):
+ try:
+ json_data = await request.json()
+ print(f"[{branding}Debug] Received JSON data: {json_data}") if debug_openai else None
+ validation_error = OpenAIGenerator.validate_input(json_data)
+ if validation_error:
+ print(f"Validation error: {validation_error}") if debug_openai else None
+ return JSONResponse(content={"error": validation_error}, status_code=400)
+ model = json_data["model"] # Currently ignored
+ input_text = json_data["input"]
+ voice = json_data["voice"]
+ response_format = json_data.get("response_format", "wav").lower()
+ speed = json_data.get("speed", 1.0)
+ print(f"[{branding}Debug] Input text: {input_text}") if debug_openai else None
+ print(f"[{branding}Debug] Voice: {voice}") if debug_openai else None
+ print(f"[{branding}Debug] Speed: {speed}") if debug_openai else None
+ cleaned_string = html.unescape(standard_filtering(input_text))
+ # Map the OpenAI voice to the corresponding internal voice
+ voice_mapping = {
+ "alloy": model_engine.openai_alloy,
+ "echo": model_engine.openai_echo,
+ "fable": model_engine.openai_fable,
+ "nova": model_engine.openai_nova,
+ "onyx": model_engine.openai_onyx,
+ "shimmer": model_engine.openai_shimmer
+ }
+ mapped_voice = voice_mapping.get(voice)
+ if not mapped_voice:
+ print(f"Unsupported voice: {voice}")
+ return JSONResponse(content={"error": "Unsupported voice"}, status_code=400)
+ print(f"[{branding}Debug]Mapped voice: {mapped_voice}") if debug_openai else None
+ # Generate the audio
+ # Generate a unique filename
+ unique_id = uuid.uuid4()
+ timestamp = int(time.time())
+ output_file_path = f'{this_dir / output_directory / f"openai_output_{unique_id}_{timestamp}.{model_engine.audio_format}"}'
+ await generate_audio(cleaned_string, mapped_voice, "en", model_engine.temperature_set,
+ model_engine.repetitionpenalty_set, speed, model_engine.pitch_set,
+ output_file_path, streaming=False)
+ print(f"[{branding}Debug] Audio generated at: {output_file_path}") if debug_openai else None
+ if rvc_settings["rvc_enabled"]:
+ if rvc_settings["rvc_char_model_file"].lower() in ["disabled", "disable"]:
+ print(f"[{branding}Debug] Pass rvccharacter_voice_gen") if debug_openai else None
+ pass # Skip RVC processing for character part
+ else:
+ print(f"[{branding}Debug] send to rvc") if debug_openai else None
+ pth_path = this_dir / "models" / "rvc_voices" / rvc_settings["rvc_char_model_file"]
+ run_rvc(output_file_path, pth_path, infer_pipeline)
+ # Transcode the audio to the requested format
+ transcoded_file_path = await transcode_for_openai(output_file_path, response_format)
+ print(f"[{branding}Debug] Audio transcoded to: {transcoded_file_path}") if debug_openai else None
+ # Return the audio file
+ return FileResponse(transcoded_file_path, media_type=f"audio/{response_format}", filename=f"output.{response_format}")
+ except Exception as e:
+ print(f"[{branding}Debug] An error occurred: {str(e)}") if debug_openai else None
+ return JSONResponse(content={"error": "An error occurred"}, status_code=500)
+
+###########################################################################
+# API Endpoint - OpenAI Speech API compatable endpoint Transcode Function #
+###########################################################################
+async def transcode_for_openai(input_file, output_format):
+ print(f"[{branding}Debug] ************************************") if debug_openai else None
+ print(f"[{branding}Debug] transcode_for_openai function called") if debug_openai else None
+ print(f"[{branding}Debug] ************************************") if debug_openai else None
+ print(f"[{branding}Debug] Input file : {input_file}") if debug_openai else None
+ print(f"[{branding}Debug] Output format : {output_format}") if debug_openai else None
+ if not ffmpeg_installed:
+ print(f"[{branding}TTS] FFmpeg is not installed. Format conversion is not possible.")
+ raise Exception("FFmpeg is not installed. Format conversion is not possible.")
+ # Get the file extension of the input file
+ input_extension = os.path.splitext(input_file)[1][1:].lower()
+ print(f"[{branding}Debug] Input file extension: {input_extension}") if debug_openai else None
+ # Check if the input extension matches the requested output format
+ if input_extension == output_format.lower():
+ print(f"[{branding}Debug] Input file is already in the requested format: {output_format}") if debug_openai else None
+ return input_file
+ output_file = os.path.splitext(input_file)[0] + f".{output_format}"
+ print(f"[{branding}Debug] Output file: {output_file}") if debug_openai else None
+ ffmpeg_path = "ffmpeg" # Default path for Linux and macOS
+ if sys.platform == "win32":
+ ffmpeg_path = os.path.join(this_dir, "system", "win_ffmpeg", "ffmpeg.exe")
+ ffmpeg = (FFmpeg(ffmpeg_path).option("y").input(input_file).output(output_file))
+ print(f"[{branding}Debug] Transcoding to {output_format}") if debug_openai else None
+ if output_format == "opus":
+ print(f"[{branding}Debug] Configuring Opus options") if debug_openai else None
+ ffmpeg.output(output_file, {"codec:a": "libopus", "b:a": "128k", "vbr": "on", "compression_level": 10, "frame_duration": 60, "application": "voip"})
+ elif output_format == "aac":
+ print(f"[{branding}Debug] Configuring AAC options") if debug_openai else None
+ ffmpeg.output(output_file, {"codec:a": "aac", "b:a": "192k"})
+ elif output_format == "flac":
+ print(f"[{branding}Debug] Configuring FLAC options") if debug_openai else None
+ ffmpeg.output(output_file, {"codec:a": "flac", "compression_level": 8})
+ elif output_format == "wav":
+ print(f"[{branding}Debug] Configuring WAV options") if debug_openai else None
+ ffmpeg.output(output_file, {"codec:a": "pcm_s16le"})
+ elif output_format == "mp3":
+ print(f"[{branding}Debug] Configuring MP3 options") if debug_openai else None
+ ffmpeg.output(output_file, {"codec:a": "libmp3lame", "b:a": "192k"})
+ elif output_format in ["ogg", "m4a"]:
+ if output_format == "ogg":
+ print(f"[{branding}Debug] Configuring OGG options") if debug_openai else None
+ ffmpeg.output(output_file, {"codec:a": "libvorbis"})
+ elif output_format == "m4a":
+ print(f"[{branding}Debug] Configuring M4A options") if debug_openai else None
+ ffmpeg.output(output_file, {"codec:a": "aac", "b:a": "192k"})
+ else:
+ print(f"[{branding}TTS] Unsupported output format: {output_format}")
+ raise ValueError(f"Unsupported output format: {output_format}")
+ try:
+ print(f"[{branding}Debug] Starting transcoding process") if debug_openai else None
+ await ffmpeg.execute()
+ print(f"[{branding}Debug] Transcoding completed successfully") if debug_openai else None
+ except Exception as e:
+ print(f"[{branding}TTS] Error occurred during transcoding: {str(e)}")
+ raise
+ print(f"[{branding}Debug] Transcoding process completed") if debug_openai else None
+ print(f"[{branding}Debug] Transcoded file: {output_file}") if debug_openai else None
+ return output_file
+
+######################################################################################
+# API Endpoint - OpenAI Speech API compatable endpoint change engine voices Function #
+######################################################################################
+class VoiceMappings(BaseModel):
+ alloy: str
+ echo: str
+ fable: str
+ nova: str
+ onyx: str
+ shimmer: str
+
+@app.put("/api/openai-voicemap")
+async def update_openai_voice_mappings(mappings: VoiceMappings):
+ # Update in-memory versions (wont display in Gradio without a restart as gradio caches)
+ model_engine.openai_alloy = mappings.alloy
+ model_engine.openai_echo = mappings.echo
+ model_engine.openai_fable = mappings.fable
+ model_engine.openai_nova = mappings.nova
+ model_engine.openai_onyx = mappings.onyx
+ model_engine.openai_shimmer = mappings.shimmer
+ # Update model_settings.json file for the currently loaded TTS engine
+ try:
+ settings_file = this_dir / "system" / "tts_engines" / model_engine.engine_loaded / "model_settings.json"
+ with open(settings_file, "r+") as f:
+ settings = json.load(f)
+ settings["openai_voices"] = mappings.dict()
+ f.seek(0)
+ json.dump(settings, f, indent=4)
+ f.truncate()
+ except Exception as e:
+ raise HTTPException(status_code=500, detail=f"Failed to update model settings file: {str(e)}")
+ return {"message": "OpenAI voice mappings updated successfully"}
+
+#######################
+# Play at the console #
+#######################
+def play_audio(file_path, volume):
+ print(f"[{branding}GEN] \033[94m File path is: {file_path}") if debug_tts else None
+ print(f"[{branding}GEN] \033[94m Volume is: {volume}") if debug_tts else None
+ normalized_file_path = os.path.normpath(file_path)
+ print(f"[{branding}GEN] \033[94m Normalized file path is: {normalized_file_path}") if debug_tts else None
+ directory = os.path.dirname(normalized_file_path)
+ if os.path.isdir(directory):
+ print(f"[{branding}GEN] \033[94m Directory contents: {os.listdir(directory)}") if debug_tts else None
+ else:
+ print(f"[{branding}GEN] \033[94mError: Directory does not exist: {directory}") if debug_tts else None
+ if not os.path.isfile(normalized_file_path):
+ print(f"[{branding}GEN] \033[94mError: File does not exist: {normalized_file_path}\033[0m") if debug_tts else None
+ return
+ # Check for AAC format
+ if normalized_file_path.lower().endswith('.aac'):
+ print(f"[{branding}GEN] \033[94mPlay Audio : \033[0mAAC format files cannot be played at the console. Please choose another format")
+ return
+ try:
+ print(f"[{branding}GEN] \033[94mPlay Audio : \033[0mPlaying audio at console")
+ data, fs = sf.read(normalized_file_path)
+ sd.play(volume * data, fs)
+ sd.wait()
+ except Exception as e:
+ print(f"[{branding}GEN] \033[94mError playing audio file: {e}\033[0m")
+
+class Request(BaseModel):
+ # Define the structure of the 'Request' class if needed
+ pass
+
+
+class TTSGenerator:
+ @staticmethod
+ def validate_json_input(json_data: Union[Dict, str]) -> Union[None, str]:
+ try:
+ if isinstance(json_data, str):
+ json_data = json.loads(json_data)
+ JSONInput(**json_data)
+ return None # JSON is valid
+ except ValidationError as e:
+ return str(e)
+
+#################################################################
+# /api/tts-generate Generation API Endpoint Narration Filtering #
+#################################################################
+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
+
+def standard_filtering(text_input):
+ text_output = (text_input
+ .replace("***", "")
+ .replace("**", "")
+ .replace("*", "")
+ .replace("\n\n", "\n")
+ .replace("'", "'")
+ )
+ return text_output
+
+#################################################################
+# /api/tts-generate Generation API Endpoint Narration Combining #
+#################################################################
+def combine(output_file_timestamp, output_file_name, audio_files, target_sample_rate=44100):
+ audio = np.array([])
+ try:
+ for audio_file in audio_files:
+ normalized_audio_file = os.path.normpath(audio_file)
+ print(f"[{branding}Debug] Processing file: {normalized_audio_file}") if debug_concat else None
+ # Check if the file exists
+ if not os.path.isfile(normalized_audio_file):
+ print(f"[{branding}Debug] Error: File does not exist: {normalized_audio_file}") if debug_concat else None
+ return None, None
+ # Read the audio file
+ audio_data, current_sample_rate = sf.read(normalized_audio_file)
+ print(f"[{branding}Debug] Read file: {normalized_audio_file}, Sample rate: {current_sample_rate}, Data shape: {audio_data.shape}") if debug_concat else None
+ # Resample if necessary
+ if current_sample_rate != target_sample_rate:
+ print(f"[{branding}Debug] Resampling file from {current_sample_rate} to {target_sample_rate} Hz") if debug_concat else None
+ audio_data = librosa.resample(audio_data, orig_sr=current_sample_rate, target_sr=target_sample_rate)
+ # Concatenate audio data
+ if audio.size == 0:
+ audio = audio_data
+ else:
+ audio = np.concatenate((audio, audio_data))
+ if output_file_timestamp:
+ timestamp = int(time.time())
+ output_file_path = os.path.join(this_dir / "outputs" / f'{output_file_name}_{timestamp}_combined.wav')
+ sf.write(output_file_path, audio, target_sample_rate)
+ # Legacy API or New API return
+ if params["api_def"]["api_use_legacy_api"]:
+ output_file_url = f"http://{api_defaults['api_legacy_ip_address']}:{params['api_def']['api_port_number']}/audio/{output_file_name}_{timestamp}_combined.wav"
+ output_cache_url = f"http://{api_defaults['api_legacy_ip_address']}:{params['api_def']['api_port_number']}/audiocache/{output_file_name}_{timestamp}_combined.wav"
+ else:
+ output_file_url = f'/audio/{output_file_name}_{timestamp}_combined.wav'
+ output_cache_url = f'/audiocache/{output_file_name}_{timestamp}_combined.wav'
+ else:
+ output_file_path = os.path.join(this_dir / "outputs" / f'{output_file_name}_combined.wav')
+ sf.write(output_file_path, audio, target_sample_rate)
+ # Legacy API or New API return
+ if params["api_def"]["api_use_legacy_api"]:
+ output_file_url = f"http://{api_defaults['api_legacy_ip_address']}:{api_defaults['api_port_number']}/audio/{output_file_name}_combined.wav"
+ output_cache_url = f"http://{api_defaults['api_legacy_ip_address']}:{api_defaults['api_port_number']}/audiocache/{output_file_name}_combined.wav"
+ else:
+ output_file_url = f'/audio/{output_file_name}_combined.wav'
+ output_cache_url = f'/audiocache/{output_file_name}_combined.wav'
+ print(f"[{branding}Debug] Output file changed to:", output_file_path) if debug_concat else None
+ print(f"[{branding}Debug] Output file changed to:", output_file_url) if debug_concat else None
+ print(f"[{branding}Debug] Output file changed to:", output_cache_url) if debug_concat else None
+ except Exception as e:
+ print(f"Error occurred: {e}")
+ return output_file_path, output_file_url, output_cache_url
+
+##################################
+# Central RVC Generation request #
+##################################
+def run_rvc(input_tts_path, pth_path, pitch, infer_pipeline):
+ generate_start_time = time.time()
+ f0up_key = pitch
+ filter_radius = rvc_settings["filter_radius"]
+ index_rate = rvc_settings["index_rate"]
+ rms_mix_rate = rvc_settings["rms_mix_rate"]
+ protect = rvc_settings["protect"]
+ hop_length = rvc_settings["hop_length"]
+ f0method = rvc_settings["f0method"]
+ split_audio = rvc_settings["split_audio"]
+ f0autotune = rvc_settings["autotune"]
+ embedder_model = rvc_settings["embedder_model"]
+ training_data_size = rvc_settings["training_data_size"]
+ # Convert path variables to strings
+ input_tts_path = str(input_tts_path)
+ pth_path = str(pth_path)
+ # Check if the model file exists
+ if not os.path.isfile(pth_path):
+ print(f"Model file {pth_path} does not exist. Exiting.")
+ return
+ # Get the directory of the model file
+ model_dir = os.path.dirname(pth_path)
+ # Get the filename of pth_path
+ pth_filename = os.path.basename(pth_path)
+ # Find all .index files in the model directory
+ index_files = [file for file in os.listdir(model_dir) if file.endswith(".index")]
+ if len(index_files) == 1:
+ index_path = str(os.path.join(model_dir, index_files[0]))
+ # Get the filename of index_path
+ index_filename = os.path.basename(index_path)
+ index_filename_print = index_filename
+ index_size_print = training_data_size
+ elif len(index_files) > 1:
+ print(f"[{branding}GEN] \033[94mRVC Convert :\033[0m Multiple RVC index files found in the models folder where \033[93m{pth_filename}\033[0m is")
+ print(f"[{branding}GEN] \033[94mRVC Convert :\033[0m located. Unable to determine which index to use. Continuing without an index file.")
+ index_path = ""
+ index_filename = None
+ index_filename_print = "None used"
+ index_size_print = "N/A"
+ else:
+ index_path = ""
+ index_filename = None
+ index_filename_print = "None used"
+ index_size_print = "N/A"
+ # Set the output path to be the same as the input path
+ output_rvc_path = input_tts_path
+ if debug_rvc:
+ debug_lines = [
+ f"[{branding}Debug] ************************************",
+ f"[{branding}Debug] run_rvc function called (debug_rvc)",
+ f"[{branding}Debug] ***********************************",
+ f"[{branding}Debug] f0up_key : {f0up_key}",
+ f"[{branding}Debug] filter_radius : {filter_radius}",
+ f"[{branding}Debug] index_rate : {index_rate}",
+ f"[{branding}Debug] rms_mix_rate : {rms_mix_rate}",
+ f"[{branding}Debug] protect : {protect}",
+ f"[{branding}Debug] hop_length : {hop_length}",
+ f"[{branding}Debug] f0method : {f0method}",
+ f"[{branding}Debug] input_tts_path : {input_tts_path}",
+ f"[{branding}Debug] output_rvc_path : {output_rvc_path}",
+ f"[{branding}Debug] pth_path : {pth_path}",
+ f"[{branding}Debug] index_path : {index_path}",
+ f"[{branding}Debug] split_audio : {split_audio}",
+ f"[{branding}Debug] f0autotune : {f0autotune}",
+ f"[{branding}Debug] embedder_model : {embedder_model}",
+ f"[{branding}Debug] training_data_size: {training_data_size}"
+ ]
+ debug_info = "\n".join(debug_lines)
+ print(debug_info)
+ # Call the infer_pipeline function
+ infer_pipeline(f0up_key, filter_radius, index_rate, rms_mix_rate, protect, hop_length, f0method,
+ input_tts_path, output_rvc_path, pth_path, index_path, split_audio, f0autotune, embedder_model, training_data_size, debug_rvc)
+ generate_end_time = time.time()
+ generate_elapsed_time = generate_end_time - generate_start_time
+ print(f"[{branding}GEN] \033[94mRVC Convert : \033[94mModel: \033[93m{pth_filename} \033[94mIndex: \033[93m{index_filename_print}\033[0m")
+ print(f"[{branding}GEN] \033[94mRVC Convert : \033[93m{generate_elapsed_time:.2f} seconds. \033[94mMethod: \033[93m{f0method} \033[94mIndex size used \033[93m{index_size_print}\033[0m")
+ return
+
+####################################################################
+# /api/tts-generate Generation API Endpoint Narration RVC handling #
+####################################################################
+def process_rvc_narrator(part_type, voice_gen, pitch, default_model_file, output_file, infer_pipeline):
+ if voice_gen.lower() in ["disabled", "disable"]:
+ return
+ else:
+ voice_gen_path = this_dir / "models" / "rvc_voices" / voice_gen
+ pth_path = voice_gen_path if voice_gen else default_model_file
+ run_rvc(output_file, pth_path, pitch, infer_pipeline)
+
+#############################################################
+# /api/tts-generate Generation API Endpoint JSON validation #
+#############################################################
+class JSONInput(BaseModel):
+ text_input: str = Field(..., max_length=int(api_defaults["api_max_characters"]), description=f"text_input needs to be {api_defaults['api_max_characters']} characters or less.")
+ text_filtering: str = Field(..., pattern="^(none|standard|html)$", description="text_filtering needs to be 'none', 'standard' or 'html'.")
+ #character_voice_gen: str = Field(..., pattern=r'^[\(\)a-zA-Z0-9\_\-./\s]+$', description="character_voice_gen needs to be the name of a valid voice for the loaded TTS engine.")
+ rvccharacter_voice_gen: str = Field(..., description="rvccharacter_voice_gen needs to be the name of a valid pth file in the 'folder\\file.pth' format or the word 'Disabled'.")
+ rvccharacter_pitch: float = Field(..., description="RVC Character pitch needs to be a number between -24 and 24")
+ narrator_enabled: str = Field(..., pattern="^(true|false|silent)$", description="narrator_enabled needs to be 'true', 'false' or 'silent'.")
+ narrator_voice_gen: str = Field(..., pattern=r'^[\(\)a-zA-Z0-9\_\-./\s]+$', description="character_voice_gen needs to be the name of a valid voice for the loaded TTS engine.")
+ rvcnarrator_voice_gen: str = Field(..., description="rvcnarrator_voice_gen needs to be the name of a valid pth file in the 'folder\\file.pth' format or the word 'Disabled'.")
+ rvcnarrator_pitch: float = Field(..., description="RVC Narrator pitch needs to be a number between -24 and 24")
+ text_not_inside: str = Field(..., pattern="^(character|narrator|silent)$", description="text_not_inside needs to be 'character', 'narrator' or 'silent'.")
+ language: str = Field(..., pattern="^(ar|zh-cn|zh|cs|nl|en|fr|de|hu|hi|it|ja|ko|pl|pt|ru|es|tr)$", description="language needs to be one of the following: ar, zh-cn, zh, cs, nl, en, fr, de, hu, hi, it, ja, ko, pl, pt, ru, es, tr.")
+ output_file_name: str = Field(..., pattern="^[a-zA-Z0-9_]+$", description="output_file_name needs to be the name without any special characters or file extension, e.g., 'filename'.")
+ output_file_timestamp: bool = Field(..., description="output_file_timestamp needs to be true or false.")
+ autoplay: bool = Field(..., description="autoplay needs to be a true or false value.")
+ autoplay_volume: float = Field(..., ge=0.1, le=1.0, description="autoplay_volume needs to be from 0.1 to 1.0.")
+ speed: float = Field(..., ge=0.25, le=2.0, description="speed needs to be between 0.25 and 2.0.")
+ pitch: float = Field(..., ge=-10, le=10, description="pitch needs to be between -10 and 10.")
+ temperature: float = Field(..., ge=0.1, le=1.0, description="temperature needs to be between 0.1 and 1.0.")
+ repetition_penalty: float = Field(..., ge=1.0, le=20.0, description="repetition_penalty needs to be between 1.0 and 20.0.")
+
+ @classmethod
+ def validate_autoplay_volume(cls, value):
+ if not (0.1 <= value <= 1.0):
+ raise ValueError("Autoplay volume must be between 0.1 and 1.0")
+ return value
+
+ def validate_rvccharacter_voice_gen(cls, v):
+ if v.lower() == "disabled":
+ return v
+ pattern = re.compile(r'^.*\.(pth)$')
+ if not pattern.match(v):
+ raise ValueError("rvccharacter_voice_gen needs to be the name of a valid pth file in the 'folder\\file.pth' format or 'Disabled'.")
+ return v
+
+ def validate_rvnarrator_voice_gen(cls, v):
+ if v.lower() == "disabled":
+ return v
+ pattern = re.compile(r'^.*\.(pth)$')
+ if not pattern.match(v):
+ raise ValueError("rvcnarrator_voice_gen needs to be the name of a valid pth file in the 'folder\\file.pth' format or 'Disabled'.")
+ return v
+
+ def validate_pitches(cls, value):
+ try:
+ num_value = float(value)
+ except ValueError:
+ raise ValueError("Pitch must be a number or a string representing a number.")
+ if not -24 <= num_value <= 24:
+ raise ValueError("Pitch needs to be a number between -24 and 24.")
+ return value
+
+def validate_json_input(json_input_data):
+ try:
+ JSONInput(**json_input_data)
+ except ValidationError as e:
+ error_messages = []
+ for error in e.errors():
+ field = error["loc"][0]
+ description = JSONInput.__fields__[field].description
+ error_messages.append(f"{field}: {description}")
+ error_message = "\n".join(error_messages)
+ print(f"[{branding}API] \033[91mError with API request:\033[0m", error_message)
+ return error_message
+ return None
+
+###############################################################
+# /api/tts-generate Generation API Endpoint pull fresh values #
+###############################################################
+def get_api_text_filtering():
+ return api_defaults["api_text_filtering"]
+def get_api_narrator_enabled():
+ return api_defaults["api_narrator_enabled"]
+def get_api_text_not_inside():
+ return api_defaults["api_text_not_inside"]
+def get_api_language():
+ return api_defaults["api_language"]
+def get_api_output_file_name():
+ return api_defaults["api_output_file_name"]
+def get_api_output_file_timestamp():
+ return api_defaults["api_output_file_timestamp"]
+def get_api_autoplay():
+ return api_defaults["api_autoplay"]
+def get_api_autoplay_volume():
+ return api_defaults["api_autoplay_volume"]
+def get_params_speed():
+ return model_engine.generationspeed_set
+def get_params_temperature():
+ return model_engine.temperature_set
+def get_params_repetition():
+ value = model_engine.repetitionpenalty_set
+ return float(str(value).replace(',', '.'))
+def get_params_pitch():
+ return model_engine.pitch_set
+def get_character_voice_gen():
+ return model_engine.def_character_voice
+def get_rvccharacter_voice_gen():
+ return rvc_settings["rvc_char_model_file"]
+def get_rvccharacter_pitch():
+ return rvc_settings["pitch"]
+def get_narrator_voice_gen():
+ return model_engine.def_narrator_voice
+def get_rvcnarrator_voice_gen():
+ return rvc_settings["rvc_narr_model_file"]
+def get_rvcnarrator_pitch():
+ return rvc_settings["pitch"]
+
+#############################################
+# /api/tts-generate Generation API Endpoint #
+#############################################
+@app.post("/api/tts-generate", response_class=JSONResponse)
+async def apifunction_generate_tts_standard(
+ text_input: str = Form(...),
+ text_filtering: str = Form(None),
+ character_voice_gen: str = Form(None),
+ rvccharacter_voice_gen: str = Form(None),
+ rvccharacter_pitch: float = Form(None),
+ narrator_enabled: str = Form(None),
+ narrator_voice_gen: str = Form(None),
+ rvcnarrator_voice_gen: str = Form(None),
+ rvcnarrator_pitch: float = Form(None),
+ text_not_inside: str = Form(None),
+ language: str = Form(None),
+ output_file_name: str = Form(None),
+ output_file_timestamp: bool = Form(None),
+ autoplay: bool = Form(None),
+ autoplay_volume: float = Form(None),
+ streaming: bool = Form(False),
+ speed: float = Form(None),
+ temperature: float = Form(None),
+ repetition_penalty: float = Form(None),
+ pitch: float = Form(None),
+ _text_filtering: str = Depends(get_api_text_filtering),
+ _character_voice_gen: str = Depends(get_character_voice_gen),
+ _rvccharacter_voice_gen: str = Depends(get_rvccharacter_voice_gen),
+ _rvccharacter_pitch: str = Depends(get_rvccharacter_pitch),
+ _narrator_enabled: str = Depends(get_api_narrator_enabled),
+ _narrator_voice_gen: str = Depends(get_narrator_voice_gen),
+ _rvcnarrator_voice_gen: str = Depends(get_rvcnarrator_voice_gen),
+ _rvcnarrator_pitch: str = Depends(get_rvcnarrator_pitch),
+ _text_not_inside: str = Depends(get_api_text_not_inside),
+ _language: str = Depends(get_api_language),
+ _output_file_name: str = Depends(get_api_output_file_name),
+ _output_file_timestamp: bool = Depends(get_api_output_file_timestamp),
+ _autoplay: bool = Depends(get_api_autoplay),
+ _autoplay_volume: float = Depends(get_api_autoplay_volume),
+ _speed: float = Depends(get_params_speed),
+ _temperature: float = Depends(get_params_temperature),
+ _repetition_penalty: float = Depends(get_params_repetition),
+ _pitch: float = Depends(get_params_pitch),
+):
+ if debug_tts or debug_tts_variables:
+ debug_lines = [
+ f"[{branding}Debug] *******************************************************",
+ f"[{branding}Debug] /api/tts_generate function called (debug_tts_variables)",
+ f"[{branding}Debug] **** PRE validation checks ****",
+ f"[{branding}Debug] *******************************************************",
+ f"[{branding}Debug] Defaults will be pulled from Settings if not specified in API Request!",
+ f"[{branding}Debug] max_characters : {api_defaults['api_max_characters']}",
+ f"[{branding}Debug] length_stripping : {api_defaults['api_length_stripping']}",
+ f"[{branding}Debug] legacy_api : {api_defaults['api_use_legacy_api']}",
+ f"[{branding}Debug] legacy_api IPadd : {api_defaults['api_legacy_ip_address']}",
+ f"[{branding}Debug] allowed_filter : {api_defaults['api_allowed_filter']}",
+ f"[{branding}Debug] text_filtering : {text_filtering}",
+ f"[{branding}Debug] character_voice_gen : {character_voice_gen}",
+ f"[{branding}Debug] rvccharacter_voice_gen : {rvccharacter_voice_gen}",
+ f"[{branding}Debug] rvccharacter_pitch : {rvccharacter_pitch}",
+ f"[{branding}Debug] narrator_enabled : {narrator_enabled}",
+ f"[{branding}Debug] narrator_voice_gen : {narrator_voice_gen}",
+ f"[{branding}Debug] rvcnarrator_voice_gen : {rvcnarrator_voice_gen}",
+ f"[{branding}Debug] rvcnarrator_pitch : {rvcnarrator_pitch}",
+ f"[{branding}Debug] text_not_inside : {text_not_inside}",
+ f"[{branding}Debug] language : {language}",
+ f"[{branding}Debug] output_file_name : {output_file_name}",
+ f"[{branding}Debug] output_file_timestamp : {output_file_timestamp}",
+ f"[{branding}Debug] autoplay : {autoplay}",
+ f"[{branding}Debug] autoplay_volume : {autoplay_volume}",
+ f"[{branding}Debug] streaming : {streaming}",
+ f"[{branding}Debug] speed : {speed}",
+ f"[{branding}Debug] temperature : {temperature}",
+ f"[{branding}Debug] repetition_penalty : {repetition_penalty}",
+ f"[{branding}Debug] pitch : {pitch}"
+ ]
+ debug_info = "\n".join(debug_lines)
+ print(debug_info)
+ text_filtering = text_filtering or _text_filtering
+ character_voice_gen = character_voice_gen or _character_voice_gen
+ rvccharacter_voice_gen = rvccharacter_voice_gen or _rvccharacter_voice_gen
+ rvccharacter_pitch = rvccharacter_pitch if rvccharacter_pitch is not None else _rvccharacter_pitch
+ narrator_enabled = narrator_enabled or _narrator_enabled # NEED TO COME BACK AND LOOK AT THIS!!!
+ narrator_voice_gen = narrator_voice_gen or _narrator_voice_gen
+ rvcnarrator_voice_gen = rvcnarrator_voice_gen or _rvcnarrator_voice_gen
+ rvcnarrator_pitch = rvcnarrator_pitch if rvcnarrator_pitch is not None else _rvcnarrator_pitch
+ text_not_inside = text_not_inside or _text_not_inside
+ language = language or _language
+ output_file_name = output_file_name or _output_file_name
+ output_file_timestamp = output_file_timestamp if output_file_timestamp is not None else _output_file_timestamp
+ autoplay = autoplay if autoplay is not None else _autoplay
+ autoplay_volume = autoplay_volume or _autoplay_volume
+ speed = speed or _speed
+ temperature = temperature or _temperature
+ repetition_penalty = repetition_penalty or _repetition_penalty
+ pitch = pitch or _pitch
+ try:
+ json_input_data = {
+ "text_input": text_input,
+ "text_filtering": text_filtering,
+ "character_voice_gen": character_voice_gen,
+ "rvccharacter_voice_gen": rvccharacter_voice_gen,
+ "rvccharacter_pitch": rvccharacter_pitch,
+ "narrator_enabled": narrator_enabled,
+ "narrator_voice_gen": narrator_voice_gen,
+ "rvcnarrator_voice_gen": rvcnarrator_voice_gen,
+ "rvcnarrator_pitch": rvcnarrator_pitch,
+ "text_not_inside": text_not_inside,
+ "language": language,
+ "output_file_name": output_file_name,
+ "output_file_timestamp": output_file_timestamp,
+ "autoplay": autoplay,
+ "autoplay_volume": autoplay_volume,
+ "streaming": streaming,
+ "speed": speed,
+ "temperature": temperature,
+ "repetition_penalty": repetition_penalty,
+ "pitch": pitch,
+ }
+ JSONresult = validate_json_input(json_input_data)
+ if JSONresult is None:
+ pass
+ else:
+ return JSONResponse(content={"error": JSONresult}, status_code=400)
+
+ if debug_tts or debug_tts_variables:
+ debug_lines = [
+ f"[{branding}Debug] *******************************************************",
+ f"[{branding}Debug] /api/tts_generate function called (debug_tts_variables)",
+ f"[{branding}Debug] **** POST validation checks ****",
+ f"[{branding}Debug] *******************************************************",
+ f"[{branding}Debug] Defaults will be pulled from Settings if not specified in API Request!",
+ f"[{branding}Debug] max_characters : {api_defaults['api_max_characters']}",
+ f"[{branding}Debug] length_stripping : {api_defaults['api_length_stripping']}",
+ f"[{branding}Debug] legacy_api : {api_defaults['api_use_legacy_api']}",
+ f"[{branding}Debug] legacy_api IPadd : {api_defaults['api_legacy_ip_address']}",
+ f"[{branding}Debug] allowed_filter : {api_defaults['api_allowed_filter']}",
+ f"[{branding}Debug] text_filtering : {text_filtering}",
+ f"[{branding}Debug] character_voice_gen : {character_voice_gen}",
+ f"[{branding}Debug] rvccharacter_voice_gen : {rvccharacter_voice_gen}",
+ f"[{branding}Debug] rvccharacter_pitch : {rvccharacter_pitch}",
+ f"[{branding}Debug] narrator_enabled : {narrator_enabled}",
+ f"[{branding}Debug] narrator_voice_gen : {narrator_voice_gen}",
+ f"[{branding}Debug] rvcnarrator_voice_gen : {rvcnarrator_voice_gen}",
+ f"[{branding}Debug] rvcnarrator_pitch : {rvcnarrator_pitch}",
+ f"[{branding}Debug] text_not_inside : {text_not_inside}",
+ f"[{branding}Debug] language : {language}",
+ f"[{branding}Debug] output_file_name : {output_file_name}",
+ f"[{branding}Debug] output_file_timestamp : {output_file_timestamp}",
+ f"[{branding}Debug] autoplay : {autoplay}",
+ f"[{branding}Debug] autoplay_volume : {autoplay_volume}",
+ f"[{branding}Debug] streaming : {streaming}",
+ f"[{branding}Debug] speed : {speed}",
+ f"[{branding}Debug] temperature : {temperature}",
+ f"[{branding}Debug] repetition_penalty : {repetition_penalty}",
+ f"[{branding}Debug] pitch : {pitch}"
+ ]
+ debug_info = "\n".join(debug_lines)
+ print(debug_info)
+ if narrator_enabled.lower() == "silent" and text_not_inside.lower() == "silent":
+ print(f"[{branding}GEN] \033[92mBoth Narrator & Text-not-inside are set to \033[91msilent\033[92m. If you get no TTS, this is why.\033[0m")
+ if narrator_enabled.lower() in ["true", "silent"]:
+ if model_engine.lowvram_enabled and model_engine.device == "cpu":
+ await model_engine.handle_lowvram_change()
+ model_engine.tts_narrator_generatingtts = True
+ print(f"[{branding}Debug] Moved into Narrator") if debug_tts else None
+ print(f"[{branding}Debug] original text : {text_input}") if debug_tts else None
+ processed_parts = process_text(text_input)
+ audio_files_all_paragraphs = []
+ for part_type, part in processed_parts:
+ # Skip parts that are too short
+ if len(part.strip()) <= int(params["api_def"]["api_length_stripping"]):
+ continue
+ # Determine the voice to use based on the part type
+ if part_type == 'narrator':
+ if narrator_enabled.lower() == "silent":
+ print(f"[{branding}GEN] \033[95mNarrator Silent:\033[0m", part) # Green
+ continue # Skip generating audio for narrator parts if set to "silent"
+ voice_to_use = narrator_voice_gen
+ print(f"[{branding}GEN] \033[92mNarrator:\033[0m", part) # Purple
+ elif part_type == 'character':
+ voice_to_use = character_voice_gen
+ print(f"[{branding}GEN] \033[36mCharacter:\033[0m", part) # Yellow
+ else:
+ # Handle ambiguous parts based on user preference
+ if text_not_inside == "silent":
+ print(f"[{branding}GEN] \033[95mText-not-inside Silent:\033[0m", part) # Purple
+ continue # Skip generating audio for ambiguous parts if set to "silent"
+ voice_to_use = character_voice_gen if text_not_inside == "character" else narrator_voice_gen
+ voice_description = "\033[36mCharacter (Text-not-inside)\033[0m" if text_not_inside == "character" else "\033[92mNarrator (Text-not-inside)\033[0m"
+ print(f"[{branding}GEN] {voice_description}:", part)
+ # 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(rf'{api_defaults["api_allowed_filter"]}', '', cleaned_part)
+ print(f"[{branding}Debug] text after api_allowed: {text_input}") if debug_tts else None
+ # Remove all newline characters (single or multiple)
+ cleaned_part = re.sub(r'\n+', ' ', cleaned_part)
+ output_file = this_dir / output_directory / f'{output_file_name}_{uuid.uuid4()}_{int(time.time())}.{model_engine.audio_format}'
+ output_file_str = output_file.as_posix()
+ response = await generate_audio(cleaned_part, voice_to_use, language,temperature, repetition_penalty, speed, pitch, output_file_str, streaming)
+ audio_path = output_file_str
+ if rvc_settings["rvc_enabled"]:
+ if part_type == 'character':
+ process_rvc_narrator(part_type, rvccharacter_voice_gen, rvccharacter_pitch, rvc_settings["rvc_char_model_file"], output_file, infer_pipeline)
+ elif part_type == 'narrator':
+ process_rvc_narrator(part_type, rvcnarrator_voice_gen, rvcnarrator_pitch, rvc_settings["rvc_narr_model_file"], output_file, infer_pipeline)
+ else:
+ if text_not_inside == 'character':
+ process_rvc_narrator('character', rvccharacter_voice_gen, rvccharacter_pitch, rvc_settings["rvc_char_model_file"], output_file, infer_pipeline)
+ elif text_not_inside == 'narrator':
+ process_rvc_narrator('narrator', rvcnarrator_voice_gen, rvcnarrator_pitch, rvc_settings["rvc_narr_model_file"], output_file, infer_pipeline)
+ print(f"[{branding}Debug] Appending audio path to list") if debug_tts else None
+ audio_files_all_paragraphs.append(audio_path)
+ # Combine audio files across paragraphs
+ model_engine.tts_narrator_generatingtts = False
+ print(f"[{branding}Debug] Narrator sending to combine") if debug_tts else None
+ output_file_path, output_file_url, output_cache_url = combine(output_file_timestamp, output_file_name, audio_files_all_paragraphs)
+ # Transcode audio if necessary
+ model_audio_format = str(model_engine.audio_format).lower()
+ output_audio_format = str(params["transcode_audio_format"]).lower()
+ if output_audio_format == "disabled":
+ pass
+ else:
+ if model_audio_format != output_audio_format and not model_engine.tts_narrator_generatingtts:
+ print(f"[{branding}Debug] Pre Transcode call - Output file: {output_file_path}") if debug_tts else None
+ output_file_path, output_file_url, output_cache_url = await transcode_audio_if_necessary(output_file_path, model_audio_format, output_audio_format)
+ print(f"[{branding}Debug] Post Transcode call - Output file: {output_file_path}") if debug_tts else None
+ if sounddevice_installed == False:
+ autoplay = False
+ if autoplay:
+ play_audio(output_file_path, autoplay_volume)
+ # Move model back to cpu system ram if needed.
+ if model_engine.lowvram_enabled and model_engine.device == "cuda" and model_engine.lowvram_capable:
+ await model_engine.handle_lowvram_change()
+ return JSONResponse(content={"status": "generate-success", "output_file_path": str(output_file_path), "output_file_url": str(output_file_url), "output_cache_url": str(output_cache_url)}, status_code=200)
+ else:
+ print(f"[{branding}Debug] Moved into Standard generation") if debug_tts else None
+ print(f"[{branding}Debug] original text : {text_input}") if debug_tts else None
+ if output_file_timestamp:
+ timestamp = int(time.time())
+ # Generate a standard UUID
+ original_uuid = uuid.uuid4()
+ # Hash the UUID using SHA-256
+ hash_object = hashlib.sha256(str(original_uuid).encode())
+ hashed_uuid = hash_object.hexdigest()
+ # Truncate to the desired length, for example, 16 characters
+ short_uuid = hashed_uuid[:5]
+ output_file_path = this_dir / output_directory / f'{output_file_name}_{timestamp}{short_uuid}.{model_engine.audio_format}'
+ #Legacy API or New API return
+ if params["api_def"]["api_use_legacy_api"]:
+ output_file_url = f'http://{api_defaults["api_legacy_ip_address"]}:{api_defaults["api_port_number"]}/audio/{output_file_name}_{timestamp}{short_uuid}.{model_engine.audio_format}'
+ output_cache_url = f'http://{api_defaults["api_legacy_ip_address"]}:{api_defaults["api_port_number"]}/audiocache/{output_file_name}_{timestamp}{short_uuid}.{model_engine.audio_format}'
+ else:
+ output_file_url = f'/audio/{output_file_name}_{timestamp}{short_uuid}.{model_engine.audio_format}'
+ output_cache_url = f'/audiocache/{output_file_name}_{timestamp}{short_uuid}.{model_engine.audio_format}'
+ else:
+ output_file_path = this_dir / output_directory / f"{output_file_name}.{model_engine.audio_format}"
+ # Legacy API or New API return
+ if params["api_def"]["api_use_legacy_api"]:
+ output_file_url = f'http://{api_defaults["api_legacy_ip_address"]}:{api_defaults["api_port_number"]}/audio/{output_file_name}.{model_engine.audio_format}'
+ output_cache_url = f'http://{api_defaults["api_legacy_ip_address"]}:{api_defaults["api_port_number"]}/audiocache/{output_file_name}.{model_engine.audio_format}'
+ else:
+ output_file_url = f'/audio/{output_file_name}.{model_engine.audio_format}'
+ output_cache_url = f'/audiocache/{output_file_name}.{model_engine.audio_format}'
+ if text_filtering == "html":
+ cleaned_string = html.unescape(standard_filtering(text_input))
+ cleaned_string = re.sub(r'([!?.])\1+', r'\1', cleaned_string)
+ # Further clean to remove any other unwanted characters
+ cleaned_string = re.sub(rf'{api_defaults["api_allowed_filter"]}', '', cleaned_string)
+ print(f"[{branding}Debug] HTML Filtering - text after api_allowed: {cleaned_string}") if debug_tts else None
+ # Remove all newline characters (single or multiple)
+ cleaned_string = re.sub(r'\n+', ' ', cleaned_string)
+ elif text_filtering == "standard":
+ cleaned_string = re.sub(r'([!?.])\1+', r'\1', text_input)
+ # Further clean to remove any other unwanted characters
+ cleaned_string = re.sub(rf'{api_defaults["api_allowed_filter"]}', '', cleaned_string)
+ print(f"[{branding}Debug] Standard Filtering - text after api_allowed: {cleaned_string}") if debug_tts else None
+ # Remove all newline characters (single or multiple)
+ cleaned_string = re.sub(r'\n+', ' ', cleaned_string)
+ else:
+ print(f"[{branding}Debug] No filtering text : {text_input}") if debug_tts else None
+ cleaned_string = text_input
+ print(f"[{branding}GEN]", cleaned_string)
+ print(f"[{branding}Debug] Sending request to generate_audio in tts_server.py") if debug_tts else None
+ try:
+ if streaming:
+ response = await generate_audio(cleaned_string, character_voice_gen, language, temperature, repetition_penalty, speed, pitch, output_file_path, streaming)
+ return StreamingResponse(response, media_type="audio/wav")
+ else:
+ response = await generate_audio(cleaned_string, character_voice_gen, language, temperature, repetition_penalty, speed, pitch, output_file_path, streaming)
+ if rvc_settings["rvc_enabled"]:
+ if rvccharacter_voice_gen.lower() in ["disabled", "disable"]:
+ print(f"[{branding}Debug] Pass rvccharacter_voice_gen") if debug_tts else None
+ pass # Skip RVC processing for character part
+ else:
+ print(f"[{branding}Debug] send to rvc") if debug_tts else None
+ rvccharacter_voice_gen = this_dir / "models" / "rvc_voices" / rvccharacter_voice_gen
+ pth_path = rvccharacter_voice_gen if rvccharacter_voice_gen else rvc_settings["rvc_char_model_file"]
+ run_rvc(output_file_path, pth_path, rvccharacter_pitch, infer_pipeline)
+ # Transcode audio if necessary
+ model_audio_format = str(model_engine.audio_format).lower()
+ output_audio_format = str(params["transcode_audio_format"]).lower()
+ print(f"[{branding}Debug] At Model audio format") if debug_tts else None
+ if output_audio_format == "disabled":
+ pass
+ else:
+ if model_audio_format != output_audio_format and not model_engine.tts_narrator_generatingtts:
+ print(f"[{branding}Debug] Pre Transcode call - Output file: {output_file_path}") if debug_tts else None
+ output_file_path, output_file_url, output_cache_url = await transcode_audio_if_necessary(output_file_path, model_audio_format, output_audio_format)
+ print(f"[{branding}Debug] Post Transcode call - Output file: {output_file_path}") if debug_tts else None
+ if sounddevice_installed == False:
+ autoplay = False
+ if autoplay:
+ play_audio(output_file_path, autoplay_volume)
+ return JSONResponse(content={"status": "generate-success", "output_file_path": str(output_file_path), "output_file_url": str(output_file_url), "output_cache_url": str(output_cache_url)}, status_code=200)
+ except Exception as e:
+ return JSONResponse(content={"status": "generate-failure", "error": "An error occurred"}, status_code=500)
+ except Exception as e:
+ return JSONResponse(content={"status": "generate-failure", "error": "An error occurred"}, status_code=500)
+
+#############################
+#### Word Add-in Sharing ####
+#############################
+# Mount the static files from the 'word_addin' directory
+app.mount("/api/word_addin", StaticFiles(directory=os.path.join(this_dir / 'system' / 'word_addin')), name="word_addin")
+
+#############################################
+#### TTS Generator Comparision Endpoints ####
+#############################################
+import aiofiles
+class TTSItem(BaseModel):
+ id: int
+ fileUrl: str
+ text: str
+ characterVoice: str
+ language: str
+
+class TTSData(BaseModel):
+ ttsList: List[TTSItem]
+
+@app.post("/api/save-tts-data")
+async def apifunction_save_tts_data(tts_data: List[TTSItem]):
+ # Convert the list of Pydantic models to a list of dictionaries
+ tts_data_list = [item.dict() for item in tts_data]
+ # Serialize the list of dictionaries to a JSON string
+ tts_data_json = json.dumps(tts_data_list, indent=4)
+ async with aiofiles.open(this_dir / output_directory / "ttsList.json", 'w') as f:
+ await f.write(tts_data_json)
+ return {"message": "Data saved successfully"}
+
+########################################
+# Trigger TTS Gen Text/Speech Analysis #
+########################################
+@app.get("/api/trigger-analysis")
+async def apifunction_trigger_analysis(threshold: int = Query(default=98)):
+ venv_path = sys.prefix
+ env = os.environ.copy()
+ env["PATH"] = os.path.join(venv_path, "bin") + ":" + env["PATH"]
+ ttslist_path = this_dir / output_directory / "ttsList.json"
+ wavfile_path = this_dir / output_directory
+ subprocess.run(["python", "tts_diff.py", f"--threshold={threshold}", f"--ttslistpath={ttslist_path}", f"--wavfilespath={wavfile_path}"], cwd=this_dir / "system" / "tts_diff", env=env)
+ # Read the analysis summary
+ try:
+ with open(this_dir / output_directory / "analysis_summary.json", "r") as summary_file:
+ summary_data = json.load(summary_file)
+ except FileNotFoundError:
+ summary_data = {"error": "Analysis summary file not found."}
+ return {"message": "Analysis Completed", "summary": summary_data}
+
+###########################################
+# TTS Generator SRT Subtitiles generation #
+###########################################
+@app.get("/api/srt-generation")
+async def apifunction_srt_generation():
+ venv_path = sys.prefix
+ env = os.environ.copy()
+ env["PATH"] = os.path.join(venv_path, "bin") + ":" + env["PATH"]
+ ttslist_path = this_dir / output_directory / "ttsList.json"
+ wavfile_path = this_dir / output_directory
+ subprocess.run(["python", "tts_srt.py", f"--ttslistpath={ttslist_path}", f"--wavfilespath={wavfile_path}"], cwd=this_dir / "system" / "tts_srt", env=env)
+ srt_file_path = this_dir / output_directory / "subtitles.srt"
+ if not srt_file_path.exists():
+ raise HTTPException(status_code=404, detail="Subtitle file not found.")
+ return FileResponse(path=srt_file_path, filename="subtitles.srt", media_type='application/octet-stream')
+
+#################################
+# Static Mount for file serving #
+#################################
+app.mount("/static", StaticFiles(directory=str(this_dir / "system")), name="static")
+
+########################################
+# Legacy JSON update settings function #
+########################################
+# Setup logging
+logging.basicConfig(level=logging.DEBUG)
+def get_json_data():
+ with open(this_dir / "confignew.json", "r") as json_file:
+ data = json.load(json_file)
+ return data
+
+@app.get("/settings")
+async def get_settings(request: Request):
+ data = get_json_data()
+ return templates.TemplateResponse("admin.html", {"request": request, "data": data})
+
+@app.get("/settings-json")
+async def get_settings_json():
+ return get_json_data()
+
+@app.post("/update-settings")
+async def update_settings(
+ request: Request,
+ delete_output_wavs: str = Form(...),
+ gradio_interface: str = Form(...),
+ gradio_port_number: int = Form(...),
+ api_port_number: int = Form(...),
+):
+ data = get_json_data()
+ # Update the settings based on the form values
+ data["delete_output_wavs"] = delete_output_wavs
+ data["gradio_interface"] = gradio_interface.lower() == 'true'
+ data["gradio_port_number"] = gradio_port_number
+ data["api_def"]["api_port_number"] = api_port_number
+ # Save the updated settings back to the JSON file
+ with open(this_dir / "confignew.json", "w") as json_file:
+ json.dump(data, json_file, indent=4)
+ # Redirect to the settings page to display the updated settings
+ return templates.TemplateResponse("admin.html", {"request": request, "data": data})
+
+# Create an instance of Jinja2Templates for rendering HTML templates
+templates = Jinja2Templates(directory=this_dir / "system")
+# Get the admin interface template
+template = templates.get_template("admin.html")
+# Render the template with the dynamic values
+rendered_html = template.render(params=params)
+
+###################################################
+#### Webserver Startup & Initial model Loading ####
+###################################################
+@app.get("/")
+async def read_root():
+ return HTMLResponse(content=rendered_html, status_code=200)
+
+# Start Uvicorn Webserver
+port_parameter = int(params["api_def"]["api_port_number"])
+
+if __name__ == "__main__":
+ import uvicorn
+
+ # Start Uvicorn Webserver
+ uvicorn_server = uvicorn.run(app, host="0.0.0.0", port=port_parameter, log_level="debug")
+
From 24e6076b40f1d9b87b9f605b042df6b0271ed91c Mon Sep 17 00:00:00 2001
From: GenericMale
Date: Fri, 13 Sep 2024 19:10:52 +0200
Subject: [PATCH 30/38] fix: use sys.executable for subprocess calls
---
script.py | 6 +++---
tts_server.py | 4 ++--
2 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/script.py b/script.py
index 704e7ba..8b3ce4a 100644
--- a/script.py
+++ b/script.py
@@ -214,7 +214,7 @@ def run_firsttime_script():
else:
script_path = os.path.join(this_dir, 'system', 'config', 'firstrun.py') # Default to Standalone if nothing matched.
- subprocess.run(['python', script_path], check=True)
+ subprocess.run([sys.executable, script_path], check=True)
except subprocess.CalledProcessError as e:
print(f"Error occurred while running the script: {e}")
except Exception as e:
@@ -356,7 +356,7 @@ if os.path.isfile("/.dockerenv") and 'google.colab' not in sys.modules:
print(f"[{branding}TTS] Internal Gradio address: http://localhost:{params['gradio_port_number']}")
# Start the subprocess (now unified for both Docker and non-Docker environments)
-process = subprocess.Popen(["python", script_path])
+process = subprocess.Popen([sys.executable, script_path])
# Check if the subprocess has started successfully
if process.poll() is None:
@@ -468,7 +468,7 @@ check_espeak_ng()
def start_subprocess():
global process
if process is None or process.poll() is not None:
- process = subprocess.Popen(["python", script_path])
+ process = subprocess.Popen([sys.executable, script_path])
return "Subprocess started."
else:
return "Subprocess is already running."
diff --git a/tts_server.py b/tts_server.py
index 4f3d0ac..fe28966 100644
--- a/tts_server.py
+++ b/tts_server.py
@@ -1594,7 +1594,7 @@ async def apifunction_trigger_analysis(threshold: int = Query(default=98)):
env["PATH"] = os.path.join(venv_path, "bin") + ":" + env["PATH"]
ttslist_path = this_dir / output_directory / "ttsList.json"
wavfile_path = this_dir / output_directory
- subprocess.run(["python", "tts_diff.py", f"--threshold={threshold}", f"--ttslistpath={ttslist_path}", f"--wavfilespath={wavfile_path}"], cwd=this_dir / "system" / "tts_diff", env=env)
+ subprocess.run([sys.executable, "tts_diff.py", f"--threshold={threshold}", f"--ttslistpath={ttslist_path}", f"--wavfilespath={wavfile_path}"], cwd=this_dir / "system" / "tts_diff", env=env)
# Read the analysis summary
try:
with open(this_dir / output_directory / "analysis_summary.json", "r") as summary_file:
@@ -1613,7 +1613,7 @@ async def apifunction_srt_generation():
env["PATH"] = os.path.join(venv_path, "bin") + ":" + env["PATH"]
ttslist_path = this_dir / output_directory / "ttsList.json"
wavfile_path = this_dir / output_directory
- subprocess.run(["python", "tts_srt.py", f"--ttslistpath={ttslist_path}", f"--wavfilespath={wavfile_path}"], cwd=this_dir / "system" / "tts_srt", env=env)
+ subprocess.run([sys.executable, "tts_srt.py", f"--ttslistpath={ttslist_path}", f"--wavfilespath={wavfile_path}"], cwd=this_dir / "system" / "tts_srt", env=env)
srt_file_path = this_dir / output_directory / "subtitles.srt"
if not srt_file_path.exists():
raise HTTPException(status_code=404, detail="Subtitle file not found.")
From 6f75fe1c281a70cf88a9434cb817564d70bfecc0 Mon Sep 17 00:00:00 2001
From: erew123 <35898566+erew123@users.noreply.github.com>
Date: Sun, 15 Sep 2024 13:29:21 +0100
Subject: [PATCH 31/38] Updated messages within the install routines.
---
atsetup.bat | 240 ++++++++++++++++++++++++++++++++++++++++++++--------
1 file changed, 205 insertions(+), 35 deletions(-)
diff --git a/atsetup.bat b/atsetup.bat
index 2a303e3..e1e552c 100644
--- a/atsetup.bat
+++ b/atsetup.bat
@@ -399,26 +399,202 @@ if not exist "%INSTALL_ENV_DIR%\python.exe" ( echo. && echo Conda environment is
@rem activate installer env
call "%CONDA_ROOT_PREFIX%\condabin\conda.bat" activate "%INSTALL_ENV_DIR%" || ( echo. && echo Miniconda hook not found. && goto end )
rem Install required packages
-echo ** PyTorch 2.2.1 **
+
+:install_pytorch
+echo ** Installing PyTorch 2.2.1 **
call "%CONDA_ROOT_PREFIX%\Scripts\conda.exe" install -y pytorch==2.2.1 torchvision==0.17.1 torchaudio==2.2.1 pytorch-cuda=12.1 -c pytorch -c nvidia
-echo ** Faiss **
+echo.
+if errorlevel 1 (
+ echo PyTorch installation failed, errorlevel was %errorlevel%. This could be caused by:
+ echo.
+ echo 1. Internet connection issues/unable to download PyTorch from Conda's website.
+ echo 2. Disk space related issues - Check your disk space on this drive.
+ echo 3. Incorrect or missing Conda environment - Restart installation.
+ echo 4. Permissions issues - Check you have enough rights on this system.
+ echo 5. Firewall or proxy settings blocking access to Conda's servers.
+ echo 6. Antivirus or security software interference.
+ echo 7. Other issues not mentioned above.
+ echo.
+ choice /C YN /M "Do you want to retry the Pytorch installation?"
+ if errorlevel 1 goto install_pytorch
+ if errorlevel 2 goto End
+) else (
+ echo PyTorch installation was successful.
+ echo.
+)
+
+:install_faiss
+echo ** Installing Faiss **
call "%CONDA_ROOT_PREFIX%\Scripts\conda.exe" install -y pytorch::faiss-cpu
-echo ** FFmpeg **
+echo.
+if errorlevel 1 (
+ echo Faiss installation failed, errorlevel was %errorlevel%. This could be caused by:
+ echo.
+ echo 1. Internet connection issues/unable to download Faiss from Conda's website.
+ echo 2. Disk space related issues - Check your disk space on this drive.
+ echo 3. Incorrect or missing Conda environment - Restart installation.
+ echo 4. Permissions issues - Check you have enough rights on this system.
+ echo 5. Firewall or proxy settings blocking access to Conda's servers.
+ echo 6. Antivirus or security software interference.
+ echo 7. Other issues not mentioned above.
+ echo.
+ choice /C YN /M "Do you want to retry the Faiss installation?"
+ if errorlevel 1 goto install_faiss
+ if errorlevel 2 goto End
+) else (
+ echo Faiss installation was successful.
+ echo.
+)
+
+:install_ffmpeg
+echo ** Installing FFmpeg **
call "%CONDA_ROOT_PREFIX%\Scripts\conda.exe" install -y conda-forge::ffmpeg
+echo.
+if errorlevel 1 (
+ echo FFmpeg installation failed, errorlevel was %errorlevel%. This could be caused by:
+ echo.
+ echo 1. Internet connection issues/unable to download FFmpeg from Conda's website.
+ echo 2. Disk space related issues - Check your disk space on this drive.
+ echo 3. Incorrect or missing Conda environment - Restart installation.
+ echo 4. Permissions issues - Check you have enough rights on this system.
+ echo 5. Firewall or proxy settings blocking access to Conda's servers.
+ echo 6. Antivirus or security software interference.
+ echo 7. Other issues not mentioned above.
+ echo.
+ choice /C YN /M "Do you want to retry the FFmpeg installation?"
+ if errorlevel 1 goto install_ffmpeg
+ if errorlevel 2 goto End
+) else (
+ echo FFmpeg installation was successful.
+ echo.
+)
+
echo ** Requirements file **
pip install -r system\requirements\requirements_standalone.txt
-echo ** Gradio Update **
-pip install --upgrade gradio==4.32.2
-echo ** DeepSpeed **
-curl -LO https://github.com/erew123/alltalk_tts/releases/download/DeepSpeed-14.0/deepspeed-0.14.0+ce78a63-cp311-cp311-win_amd64.whl
-echo Installing DeepSpeed...
-pip install deepspeed-0.14.0+ce78a63-cp311-cp311-win_amd64.whl
-del deepspeed-0.14.0+ce78a63-cp311-cp311-win_amd64.whl
-echo Installing Parler...
-pip install -r system\requirements\requirements_parler.txt
-echo ** Clean Environment **
-call "%CONDA_ROOT_PREFIX%\Scripts\conda.exe" clean --all --force-pkgs-dirs -y
+echo.
+:update_gradio
+echo ** Updating Gradio **
+pip install --upgrade gradio==4.32.2
+echo.
+if errorlevel 1 (
+ echo Gradio update failed, errorlevel was %errorlevel%. This could be caused by:
+ echo.
+ echo 1. Internet connection issues/unable to download Gradio from PyPI.
+ echo 2. Disk space related issues - Check your disk space on this drive.
+ echo 3. Incorrect or missing Python environment - Restart installation.
+ echo 4. Permissions issues - Check you have enough rights on this system.
+ echo 5. Firewall or proxy settings blocking access to PyPI servers.
+ echo 6. Antivirus or security software interference.
+ echo 7. Other issues not mentioned above.
+ echo.
+ choice /C YN /M "Do you want to retry the Gradio update?"
+ if errorlevel 1 goto update_gradio
+ if errorlevel 2 goto End
+) else (
+ echo Gradio update was successful.
+ echo.
+)
+
+:install_deepspeed
+echo ** Downloading DeepSpeed **
+curl -LO https://github.com/erew123/alltalk_tts/releases/download/DeepSpeed-14.0/deepspeed-0.14.0+ce78a63-cp311-cp311-win_amd64.whl
+echo.
+if errorlevel 1 (
+ echo DeepSpeed download failed, errorlevel was %errorlevel%. This could be caused by:
+ echo.
+ echo 1. Internet connection issues/unable to reach GitHub.
+ echo 2. Disk space related issues - Check your disk space on this drive.
+ echo 3. Permissions issues - Check you have enough rights on this system.
+ echo 4. Firewall or proxy settings blocking access to GitHub.
+ echo 5. Antivirus or security software interference.
+ echo 6. The file may have been moved or deleted from the GitHub repository.
+ echo 7. Other issues not mentioned above.
+ echo.
+ choice /C YN /M "Do you want to retry the DeepSpeed download?"
+ if errorlevel 1 goto install_deepspeed
+ if errorlevel 2 goto End
+) else (
+ echo DeepSpeed download was successful.
+ echo.
+)
+
+echo ** Installing DeepSpeed **
+pip install deepspeed-0.14.0+ce78a63-cp311-cp311-win_amd64.whl
+echo.
+if errorlevel 1 (
+ echo DeepSpeed installation failed, errorlevel was %errorlevel%. This could be caused by:
+ echo.
+ echo 1. The DeepSpeed wheel file was not downloaded successfully in the previous step.
+ echo 2. Microsoft C++ development tools for Python are not installed correctly.
+ echo 3. Disk space related issues - Check your disk space on this drive.
+ echo 4. Incorrect or missing Python environment - Restart installation.
+ echo 5. Permissions issues - Check you have enough rights on this system.
+ echo 6. Antivirus or security software interference.
+ echo 7. Other issues not mentioned above.
+ echo.
+ echo Please ensure you have followed the instructions to install the Microsoft C++ development
+ echo tools for Python, which is detailed on the AllTalk GitHub page.
+ echo.
+ choice /C YN /M "Do you want to retry the DeepSpeed installation?"
+ if errorlevel 1 goto install_deepspeed
+ if errorlevel 2 goto End
+) else (
+ echo DeepSpeed installation was successful.
+ del deepspeed-0.14.0+ce78a63-cp311-cp311-win_amd64.whl
+ echo.
+)
+
+echo ** Installing Parler **
+pip install -r system\requirements\requirements_parler.txt
+echo Clean Environment
+call "%CONDA_ROOT_PREFIX%\Scripts\conda.exe" clean --all --force-pkgs-dirs -y
+echo.
+
+:transformers_choice
+echo.
+echo ======================================== IMPORTANT NOTICE ===========================================
+echo Currently, Coqui-TTS and the XTTS engine requires transformers version 4.40.2 to generate streaming
+echo audio. XTTS can still generate standard non-streaming audio on any version of transformers. It is
+echo ONLY streaming audio that is affected and ONLY with the Coqui XTTS engine. So this would affect
+echo things like Kobold, or anywhere you use XTTS with streaming audio.
+echo.
+echo Parler-TTS states that it wants transformers version 4.43.3 but it appears to work fine with
+echo transformers 4.40.2, however using transformers 4.40.2 generates a dependcy error message as
+echo Parler-TTS is untested on 4.40.2 of transformers.
+echo.
+echo Until the Coqui-TTS engine is updated to perform streaming with newer versions of transformers, if
+echo you want to use streaming with XTTS, you will need to downgrade transformers to 4.40.2.
+echo.
+echo AllTalk is NOT responsible for the maintainance or upkeep of the Coqui-TTS engine.
+echo.
+echo If you change your mind in future you can re-run atsetup and re-apply the requirements.
+echo ====================================================================================================
+echo.
+choice /C YN /M " Do you want to downgrade transformers to version 4.40.2 for XTTS streaming support?"
+if errorlevel 2 (
+ echo.
+ echo Keeping transformers at version 4.43.3.
+ echo Note: XTTS streaming may not work with this version.
+ goto end_transformers_choice
+)
+if errorlevel 1 (
+ echo.
+ echo Downgrading transformers to version 4.40.2...
+ echo.
+ pip install transformers==4.40.2
+ if errorlevel 1 (
+ echo.
+ echo Failed to downgrade transformers. Please check your internet connection and try manually later with:
+ echo pip install transformers==4.40.2
+ ) else (
+ echo.
+ echo XTTS streaming should now work correctly.
+ )
+)
+:end_transformers_choice
+echo.
+echo Installation process completed.
@rem Create start_environment.bat to run AllTalk environment
echo @echo off > start_environment.bat
@@ -451,6 +627,9 @@ Echo Run %L_YELLOW%start_alltalk.bat%RESET% to start AllTalk.
Echo Run %L_YELLOW%start_diagnostics.bat%RESET% to start the diagnostics.
Echo Run %L_YELLOW%start_finetune.bat%RESET% to start Finetuning.
Echo Run %L_YELLOW%start_environment.bat%RESET% to start the AllTalk Python environment.
+Echo.
+Echo Documentation is built into the Gradio interface. Please explore the documentation for
+Echo tips, troubleshooing and explanations as most common questions are answered there.
Echo.
if "%1"=="-silent" goto End
pause
@@ -556,29 +735,14 @@ if errorlevel 1 (
)
@rem Run Reapply requirements
echo.
+echo Re-installing requirements. If you have problems after this, it may indicate
+echo that the custom Python environment is damaged in some way. As such it may be
+echo best to delete the custom Python environment and re-run the entire setup
+echo routine again to rebuild AllTalk's Python environment. Please press a key
+echo to continue re-applying the Requirements for a standalone installation.
echo.
-echo Installing requirements. If you have problems after this, you should
-echo delete the alltalk_environment and run the setup routine again.
-echo.
-pip install -r system\requirements\requirements_standalone.txt
-pip install -r system\requirements\requirements_parler.txt
-echo Installing DeepSpeed...
-pip install deepspeed-0.14.0+ce78a63-cp311-cp311-win_amd64.whl
-del deepspeed-0.14.0+ce78a63-cp311-cp311-win_amd64.whl
-if %ERRORLEVEL% neq 0 (
- echo.
- echo There was an error.
- echo Press any key to return to the menu.
- echo.
- pause
- goto StandaloneMenu
-)
-Echo.
-Echo.
-Echo Requirements have been re-applied/updated.
-Echo.
pause
-goto StandaloneMenu
+goto install_faiss
:STPurgepipcache
cd /D "%~dp0"
@@ -670,13 +834,19 @@ goto StandaloneMenu
echo Exiting AllTalk Setup Utility...
echo.
Echo Remember, after installation you can....
+Echo.
Echo Run %L_YELLOW%start_alltalk.bat%RESET% to start AllTalk.
Echo Run %L_YELLOW%start_finetune.bat%RESET% to start Finetuning.
Echo Run %L_YELLOW%start_environment.bat%RESET% to start the AllTalk Python environment.
Echo Run %L_YELLOW%start_diagnostics.bat%RESET% to start generate a diagnostics file.
Echo.
+Echo Documentation is built into the Gradio interface. Please explore the documentation for
+Echo tips, troubleshooing and explanations as most common questions are answered there.
exit /b
:End
echo Exiting AllTalk Setup Utility...
+Echo.
+Echo Documentation is built into the Gradio interface. Please explore the documentation for
+Echo tips, troubleshooing and explanations as most common questions are answered there.
exit /b
From 8dbb64c683601b5dbbe4c213c826b4837367e5b6 Mon Sep 17 00:00:00 2001
From: erew123 <35898566+erew123@users.noreply.github.com>
Date: Mon, 16 Sep 2024 18:43:56 +0100
Subject: [PATCH 32/38] Added TTS_mem - Multi Engine Manager
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
AllTalk MEM (Multi Engine Manager) is a research tool designed to manage and test multiple instances of different Text-to-Speech (TTS) engines being loaded simultaneously, with a view to a centralised engine being able to handle multiple requests simultaneously. AllTalk MEM is currently in a demonstration, testing, and experimental phase.
โ ๏ธ MEM is not intended for production use at this time and there is NO support being offered on MEM. โ ๏ธ
---
tts_mem.py | 485 +++++++
tts_server.py | 3383 +++++++++++++++++++++++++------------------------
2 files changed, 2180 insertions(+), 1688 deletions(-)
create mode 100644 tts_mem.py
diff --git a/tts_mem.py b/tts_mem.py
new file mode 100644
index 0000000..20a6abd
--- /dev/null
+++ b/tts_mem.py
@@ -0,0 +1,485 @@
+import subprocess
+import sys
+import os
+import gradio as gr
+import time
+import requests
+from requests.exceptions import RequestException
+import socket
+import json
+
+# Dictionary to store all processes
+processes = {}
+max_instances = 16
+script_path = "tts_server.py"
+current_base_port = 7001
+server_port = 7500
+
+############################################
+# START-UP # Display initial splash screen #
+############################################
+print(f"[AllTalk MEM]\033[94m _ _ _ \033[1;35m_____ _ _ \033[0m _____ _____ ____ ")
+print(f"[AllTalk MEM]\033[94m / \ | | |\033[1;35m_ _|_ _| | | __ \033[0m |_ _|_ _/ ___| ")
+print(f"[AllTalk MEM]\033[94m / _ \ | | |\033[1;35m | |/ _` | | |/ / \033[0m | | | | \___ \ ")
+print(f"[AllTalk MEM]\033[94m / ___ \| | |\033[1;35m | | (_| | | < \033[0m | | | | ___) |")
+print(f"[AllTalk MEM]\033[94m /_/ \_\_|_|\033[1;35m |_|\__,_|_|_|\_\ \033[0m |_| |_| |____/ ")
+print(f"[AllTalk MEM]")
+print(f"[AllTalk MEM] \033[94m AllTalk Multi Engine Manager\033[00m")
+print(f"[AllTalk MEM]")
+print(f"[AllTalk MEM] \033[93m MEM is not intended for production use and\033[00m")
+print(f"[AllTalk MEM] \033[93m there is NO support being offered on MEM\033[00m")
+print(f"[AllTalk MEM]")
+print(f"[AllTalk MEM] \033[94mGradio Light:\033[00m \033[92mhttp://127.0.0.1:{server_port}\033[00m")
+print(f"[AllTalk MEM] \033[94mGradio Dark :\033[00m \033[92mhttp://127.0.0.1:{server_port}?__theme=dark\033[00m")
+print(f"[AllTalk MEM]")
+
+def is_port_in_use(port):
+ with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
+ return s.connect_ex(('localhost', port)) == 0
+
+def find_available_port(start_port, num_instances):
+ available_ports = []
+ current_port = start_port
+ while len(available_ports) < num_instances:
+ if not is_port_in_use(current_port):
+ available_ports.append(current_port)
+ current_port += 1
+ return available_ports[0], available_ports
+
+def start_subprocess(instance_id, port):
+ if instance_id in processes and processes[instance_id].poll() is None:
+ return f"โ๏ธ Running on port {port}"
+
+ print(f"[AllTalk MEM] \033[94m********************************\033[00m")
+ print(f"[AllTalk MEM] \033[94mStarting TTS Engine on port {port}\033[00m")
+ print(f"[AllTalk MEM] \033[94m********************************\033[00m")
+ print(f"[AllTalk MEM] \033[92mEngine number :\033[93m {instance_id}\033[00m")
+ print(f"[AllTalk MEM] \033[92mPort number :\033[93m {port}\033[00m")
+ processes[instance_id] = subprocess.Popen([sys.executable, script_path, "--port", str(port)])
+
+ # Wait for the server to be ready
+ if is_server_ready(port):
+ print(f"[AllTalk MEM]")
+ print(f"[AllTalk MEM] Engine {instance_id} ready on port {port}")
+ return f"โ๏ธ Running on port {port}"
+ else:
+ print(f"[AllTalk MEM] Warning: Engine {instance_id} may not be fully ready on port {port}")
+ return f"Warning: Engine {instance_id} may not be fully ready on port {port}."
+
+def stop_subprocess(instance_id):
+ if instance_id in processes:
+ print(f"[AllTalk MEM] Terminating Engine {instance_id}")
+ processes[instance_id].terminate()
+ processes[instance_id].wait()
+ del processes[instance_id]
+ return f"Engine {instance_id} stopped."
+ return f"Engine {instance_id} is not running."
+
+def restart_subprocess(instance_id, port):
+ print(f"[AllTalk MEM] Re-starting Engine {instance_id}")
+ stop_result = stop_subprocess(instance_id)
+ if "not running" in stop_result.lower():
+ start_result = start_subprocess(instance_id, port)
+ print(f"[AllTalk MEM] Engine {instance_id} was not running so starting an instance")
+ return f"Engine {instance_id} was not running. {start_result}"
+ else:
+ return start_subprocess(instance_id, port)
+
+def check_subprocess_status(instance_id, port):
+ if instance_id in processes and processes[instance_id].poll() is None:
+ return f"โ๏ธ Running on port {port}"
+ return "โ Not running"
+
+# This code block tests if one of the engines has started in a timely fashion. You can increase the max retries and/or backoff factor
+# to allow for longer startup times of the TTS engine.
+def retry_with_backoff(func, max_retries=8, initial_wait=3, backoff_factor=1.2):
+ retries = 0
+ wait_time = initial_wait
+ while retries < max_retries:
+ try:
+ result = func()
+ # print(f"[AllTalk MEM] Operation successful on attempt {retries + 1}")
+ return result
+ except RequestException as e:
+ retries += 1
+ # print(f"[AllTalk MEM] Attempt {retries}")
+ if retries == max_retries:
+ # print(f"[AllTalk MEM] All {max_retries} attempts failed waiting for this instance of the Engine to load.")
+ return False
+ # print(f"[AllTalk MEM] Retrying in {wait_time} seconds...")
+ time.sleep(wait_time)
+ wait_time = round(wait_time * backoff_factor, 1) # Round to 1 decimal place
+ return False
+
+def is_server_ready(port, timeout=5):
+ def check_ready():
+ response = requests.get(f"http://127.0.0.1:{port}/api/ready", timeout=timeout)
+ if response.text.strip() == "Ready":
+ return True
+ raise RequestException("Server not ready")
+
+ return retry_with_backoff(check_ready)
+
+def update_engine_selector():
+ running_engines = []
+ debug_info = []
+ for i, p in processes.items():
+ if p.poll() is None:
+ port = current_base_port + i - 1
+ ready = is_server_ready(port)
+ status = 'Ready' if ready else 'Not Ready'
+ debug_info.append(f"Engine {i} (Port {port}): {status}")
+ if ready:
+ running_engines.append(f"Engine {i} (Port {port})")
+
+ debug_str = "\n".join(debug_info)
+ print(f"Debug info: {debug_str}") # Add this line for console debugging
+ return gr.update(choices=running_engines, value=running_engines[0] if running_engines else None), debug_str
+
+def stop_all_instances():
+ instance_ids = list(processes.keys())
+ results = ["โ Not running"] * max_instances
+ print(f"[AllTalk MEM] Terminating All TTS Engines")
+ for instance_id in instance_ids:
+ stop_subprocess(instance_id)
+ results[instance_id-1] = "โ Not running"
+ return results
+
+def update_all_statuses(base_port):
+ return [check_subprocess_status(i, base_port + i - 1) for i in range(1, max_instances + 1)]
+
+def count_running_instances():
+ return sum(1 for p in processes.values() if p.poll() is None)
+
+def get_available_voices(port, max_retries=3, delay=2):
+ for _ in range(max_retries):
+ try:
+ response = requests.get(f"http://127.0.0.1:{port}/api/voices", timeout=5)
+ data = response.json()
+ if data["status"] == "success":
+ return data["voices"]
+ except requests.RequestException as e:
+ print(f"Error fetching voices from port {port}: {str(e)}")
+ except json.JSONDecodeError:
+ print(f"Error decoding JSON from port {port}")
+ time.sleep(delay)
+ return []
+
+def generate_tts(port, engine_num, text, voice):
+ try:
+ response = requests.post(f"http://127.0.0.1:{port}/api/tts-generate", data={
+ "text_input": text,
+ "text_filtering": "standard",
+ "character_voice_gen": voice,
+ "narrator_enabled": "false",
+ "language": "en",
+ "output_file_name": "ttsmanager",
+ "output_file_timestamp": "true",
+ "autoplay": "false",
+ "autoplay_volume": "0.8"
+ }, timeout=30) # Increased timeout for TTS generation
+ data = response.json()
+ if data["status"] == "generate-success":
+ return f"http://127.0.0.1:{port}{data['output_file_url']}", f"TTS generated successfully on Engine {engine_num} and Port {port}"
+ except requests.Timeout:
+ return None, f"Timeout while generating TTS on port {port}"
+ except requests.ConnectionError:
+ return None, f"Connection error while generating TTS on port {port}"
+ except Exception as e:
+ return None, f"Unexpected error while generating TTS on port {port}: {str(e)}"
+ return None, "Failed to generate TTS"
+
+def test_tts(engine, voice, text):
+ if not engine or not voice:
+ return None, "Please select both an engine and a voice"
+ try:
+ engine_num = int(engine.split()[1])
+ port = current_base_port + engine_num - 1
+ formatted_text = text.format(engine=engine_num, port=port, voice=voice)
+
+ audio_url, debug_msg = generate_tts(port, engine_num, formatted_text, voice)
+ if audio_url:
+ return audio_url, debug_msg
+ else:
+ return None, debug_msg
+ except Exception as e:
+ return None, f"Error in test_tts: {str(e)}"
+
+def create_gradio_interface():
+ global current_base_port
+ with gr.Blocks(title="AllTalk Multi Engine Manager", theme=gr.themes.Base()) as interface:
+ with gr.Row():
+ gr.Markdown("# AllTalk Multi Engine Manager")
+ gr.Markdown("")
+ gr.Markdown("")
+ dark_mode_btn = gr.Button("Light/Dark Mode", variant="primary", size="sm")
+ dark_mode_btn.click(None, None, None,
+ js="""() => {
+ if (document.querySelectorAll('.dark').length) {
+ document.querySelectorAll('.dark').forEach(el => el.classList.remove('dark'));
+ // localStorage.setItem('darkMode', 'disabled');
+ } else {
+ document.querySelector('body').classList.add('dark');
+ // localStorage.setItem('darkMode', 'enabled');
+ }
+ }""", show_api=False)
+ with gr.Tab("Engine Management"):
+ with gr.Row():
+ num_instances_input = gr.Slider(minimum=1, maximum=max_instances, step=1, value=1, label="Number of Instances to Start")
+ start_MEMple_button = gr.Button("Start Instances")
+ stop_all_button = gr.Button("Stop All Instances")
+ refresh_button = gr.Button("Refresh Status")
+ error_box = gr.Textbox(label="Error Messages", visible=False)
+
+ instance_statuses = []
+ for i in range(0, max_instances, 8): # Create rows with 8 instances each
+ with gr.Row():
+ for j in range(8):
+ if i + j < max_instances:
+ with gr.Column(scale=1, min_width=160):
+ instance_id = i + j + 1
+ gr.Markdown(f"### Engine Instance {instance_id}")
+ status = gr.Markdown("โ Not running")
+ start_btn = gr.Button("Start")
+ stop_btn = gr.Button("Stop")
+ restart_btn = gr.Button("Restart")
+
+ start_btn.click(lambda id=instance_id: start_and_update(id, current_base_port + id - 1), outputs=status)
+ stop_btn.click(lambda id=instance_id: stop_and_update(id), outputs=status)
+ restart_btn.click(lambda id=instance_id: restart_and_update(id, current_base_port + id - 1), outputs=status)
+
+ instance_statuses.append(status)
+
+ def refresh_and_update_slider():
+ running_count = count_running_instances()
+ statuses = update_all_statuses(current_base_port)
+ return [gr.update(value=running_count)] + statuses
+
+ refresh_button.click(
+ refresh_and_update_slider,
+ outputs=[num_instances_input] + instance_statuses
+ )
+
+ def validate_and_start(num_instances):
+ if num_instances < 1:
+ return [gr.update(value=1)] + ["โ Not running"] * max_instances + ["Invalid number of instances"]
+
+ results = start_MEMple_instances(num_instances, current_base_port)
+ running_count = count_running_instances()
+ return [gr.update(value=running_count)] + results + [""]
+
+ start_MEMple_button.click(
+ validate_and_start,
+ inputs=[num_instances_input],
+ outputs=[num_instances_input] + instance_statuses + [error_box]
+ )
+
+ def stop_all_and_update():
+ results = stop_all_instances()
+ return [gr.update(value=1)] + results
+
+ stop_all_button.click(stop_all_and_update, outputs=[num_instances_input] + instance_statuses)
+
+ def start_MEMple_instances(num_instances, base_port):
+ currently_running = count_running_instances()
+
+ if currently_running >= num_instances:
+ return update_all_statuses(base_port) # All requested instances are already running
+
+ instances_to_start = num_instances - currently_running
+ start_port, available_ports = find_available_port(base_port + currently_running, instances_to_start)
+
+ if len(available_ports) < instances_to_start:
+ return ["Not enough available ports"] * max_instances
+
+ results = update_all_statuses(base_port)
+ for i, port in enumerate(available_ports[:instances_to_start], start=currently_running+1):
+ result = start_subprocess(i, port)
+ results[i-1] = result
+ time.sleep(10) # Increase wait time to 10 seconds
+
+ return results
+
+ gr.Markdown("## Test TTS Engines")
+ with gr.Row():
+ engine_selector = gr.Dropdown(label="Select TTS Engine", choices=[], scale=1)
+ voice_selector = gr.Dropdown(label="Select Voice", choices=[], scale=1)
+ test_text = gr.Textbox(label="Enter text to synthesize", value="This is a test of TTS engine number {engine} on port {port}, using the {voice} voice.", scale=2)
+
+ with gr.Row():
+ refresh_engine_button = gr.Button("Refresh Engine List")
+ test_button = gr.Button("Generate TTS")
+
+ with gr.Row():
+ audio_output = gr.Audio(label="Generated Audio")
+ debug_output = gr.Textbox(label="Debug Output", lines=5)
+
+ def update_engine_selector():
+ running_engines = []
+ debug_info = []
+ # Create a list of keys to iterate over
+ process_keys = list(processes.keys())
+ for i in process_keys:
+ p = processes.get(i)
+ if p and p.poll() is None:
+ port = current_base_port + i - 1
+ ready = is_server_ready(port)
+ status = 'Ready' if ready else 'Not Ready'
+ debug_info.append(f"Engine {i} (Port {port}): {status}")
+ if ready:
+ running_engines.append(f"Engine {i} (Port {port})")
+
+ debug_str = "\n".join(debug_info)
+ #print(f"Debug info: {debug_str}") # un hash # line for console debugging
+ return gr.update(choices=running_engines, value=running_engines[0] if running_engines else None), debug_str
+
+ def update_voice_selector(engine):
+ if engine:
+ engine_num = int(engine.split()[1])
+ port = current_base_port + engine_num - 1
+ voices = get_available_voices(port)
+ debug_str = f"Fetched voices for Engine {engine_num} (Port {port}): {voices}"
+ return gr.update(choices=voices, value=voices[0] if voices else None), debug_str
+ return gr.update(choices=[], value=None), "No engine selected"
+
+ # Update engine selector when instances are started/stopped
+ start_MEMple_button.click(update_engine_selector, outputs=[engine_selector, debug_output])
+ stop_all_button.click(update_engine_selector, outputs=[engine_selector, debug_output])
+
+ # Add a manual refresh button for the engine selector
+ refresh_engine_button.click(update_engine_selector, outputs=[engine_selector, debug_output])
+
+ # Update voice selector when engine is selected
+ engine_selector.change(update_voice_selector, inputs=[engine_selector], outputs=[voice_selector, debug_output])
+
+ # Generate TTS when test button is clicked
+ test_button.click(test_tts, inputs=[engine_selector, voice_selector, test_text], outputs=[audio_output, debug_output])
+
+ with gr.Tab("MEM Settings"):
+ with gr.Row():
+ base_port_input = gr.Number(
+ value=current_base_port,
+ label=f"Starting Port Number (Current: {current_base_port})",
+ step=1
+ )
+
+ set_port_button = gr.Button("Set Start Port Number")
+
+ def set_base_port(new_port):
+ global current_base_port
+ if new_port < 1024:
+ current_base_port = 7001
+ return (
+ f"Invalid port. Set to default: {current_base_port}",
+ gr.update(value=current_base_port, label=f"Starting Port Number (Current: {current_base_port})")
+ )
+ current_base_port = int(new_port)
+ return (
+ f"Start port set to: {current_base_port}",
+ gr.update(value=current_base_port, label=f"Starting Port Number (Current: {current_base_port})")
+ )
+
+ set_port_button.click(
+ set_base_port,
+ inputs=[base_port_input],
+ outputs=[error_box, base_port_input]
+ )
+
+ with gr.Tab("MEM FAQ/Help"):
+ with gr.Row():
+ with gr.Column():
+ gr.Markdown("""
+ ## What is MEM?
+ AllTalk MEM (Multi Engine Manager) is a research tool designed to manage and test multiple instances of different Text-to-Speech (TTS) engines being loaded simultaneously, with a view to a centralised engine being able to handle multiple requests simultaneously.
+
+ ## Current Status & Support
+ AllTalk MEM is currently in a demonstration, testing, and experimental phase.
+
+ โ ๏ธ MEM is not intended for production use at this time and there is **NO support being offered on MEM**. โ ๏ธ
+
+ ## System Capacity
+ **Q: How many engines can my system (GPU/CPU) handle at once?**
+ A: The exact number is unknown and will vary based on your specific hardware configuration and the TTS engines being loaded. Experimentation is required to determine the optimal number for your system. You would have to monitor your system resources and see what works. Dont forget you need to leave RAM/VRAM overhead for actual generation/inference.
+
+ ## Queue System
+ **Q: Is there an in-built queue system to handle different requests to different loaded engines?**
+ A: Not currently. AllTalk MEM is presently a demonstration and research tool without an integrated queue system. You are welcome to build your own and test it out.
+
+ ## Handling Multiple Requests
+ **Q: How will different TTS engines from different manufacturers OR how will CUDA/Python handle multiple requests coming in at the same time for TTS generation?**
+ A: The behavior in such scenarios is currently unknown and is part of the research aspect of this tool. Its highly possible some TTS engines may respond in a timely fashion and its also possible they may not. I cannot say how things like CUDA/Python will handle multiple requests to the same hadware.
+
+ CUDA has additional code and tools to deal with multiplexing requests and none of these have been implimented. So for example, if you are using a TTS engine that uses a GPU, and 2x different loaded TTS engines make a request of that hardware, you may get a 50/50 spit use of the hardware and you may not. 3x requests, you may get a 33/33/33 hardware load sharing sceanario nad you may not. I cannot say at the moment and havnt tested.
+
+ Python - As each instance of each TTS engine will be loaded into a seperate Python instance, all memory management and requests should remain segregated from one another, so there should be no bleed over of tensors in CUDA requests etc, however, I have not tested.
+
+ ## API Requests to each loaded engine
+ **Q: Can I use the AllTalk API to each engine instance?**
+ A: Yes, each engine loaded will respond fully as if it was a standalone AllTalk instance. So you can use the standard API requests to the port number of each TTS engine loaded in.
+
+ """)
+ with gr.Column():
+ gr.Markdown("""
+ ## What settings are used for the TTS engines loaded
+ **Q: What exact settings are being used by the TTS engines being loaded in?**
+ A: Whatever you have set in the main AllTalk Gradio interface when you load AllTalk as a standalone, under the `TTS Engine Settings` and the specific TTS engine, those are the settings loaded/used.
+
+ ## How can I change which TTS engine is loaded
+ **Q: How can you change the TTS engines being loaded?**
+ A: Whatever you have set in the main AllTalk Gradio interface when you load AllTalk as a standalone, that will be the engine loaded in.
+
+ ## What happens if it tries to load on a port in use
+ **Q: When a engine instance starts up, what happens if a port is already used by something else?**
+ A: A test is performed of the port before starting each instance, so the engine trying to load on that port number wouldnt load in.
+
+ ## Future Updates
+ **Q: Will you be updating this in the future?**
+ A: Updates may be made in the future, but this depends largely on available time and resources.
+
+ ## Contributions
+ **Q: Can I write some code for it and send a PR?**
+ A: Yes, contributions are welcome! If you have improvements or features you'd like to add, feel free to submit a Pull Request.
+
+ ## Future Plans
+ If time permits, potential future developments include:
+
+ - Potential control over each TTS engines's settings and manipulating the JSON files that control AllTalk, so as to handle which engine loads and its settings from the MEM interface.
+ - Storing settings in a configuration JSON file (e.g., start port number, number of engines to start, automatic start of engines on script start)
+ - Implementing an incoming queue management system to handle multiple requests and distribute them among loaded engines
+ - Developing a basic API management suite for MEM
+ - Refining and optimizing the existing logi for model loading etc
+
+ Please note that these are potential plans and not guaranteed features.
+
+ ## โ ๏ธ Support for MEM โ ๏ธ
+
+ โ ๏ธ MEM is not intended for production use at this time and there is **NO support being offered on MEM**. โ ๏ธ
+
+ โ ๏ธ Yes, I know there will be bugs. Yes I know there will be issues. This is not designed for Production Environments. โ ๏ธ
+ """)
+ return interface
+
+def start_and_update(instance_id, port):
+ if is_port_in_use(port):
+ return f"Port {port} is already in use"
+ result = start_subprocess(instance_id, port)
+ return check_subprocess_status(instance_id, port)
+
+def stop_and_update(instance_id):
+ result = stop_subprocess(instance_id)
+ return "โ Not running"
+
+def restart_and_update(instance_id, port):
+ if is_port_in_use(port) and instance_id not in processes:
+ return f"Port {port} is already in use"
+ result = restart_subprocess(instance_id, port)
+ return check_subprocess_status(instance_id, port)
+
+print(f"[AllTalk MEM] Please use \033[91mCtrl+C\033[0m when exiting otherwise Python")
+print(f"[AllTalk MEM] subprocess's will continue running in the background.")
+print(f"[AllTalk MEM] ")
+print(f"[AllTalk MEM] MEM Server Ready")
+
+if __name__ == "__main__":
+ interface = create_gradio_interface()
+ interface.launch(quiet=True, server_port=server_port)
\ No newline at end of file
diff --git a/tts_server.py b/tts_server.py
index fe28966..a74ba0a 100644
--- a/tts_server.py
+++ b/tts_server.py
@@ -1,1688 +1,1695 @@
-import os
-import sys
-import json
-import time
-import librosa
-import logging
-import importlib
-import subprocess
-from pathlib import Path
-logging.disable(logging.WARNING)
-#####################
-# Webserver Imports #
-#####################
-from contextlib import asynccontextmanager
-from fastapi.staticfiles import StaticFiles
-from fastapi.templating import Jinja2Templates
-from fastapi.middleware.cors import CORSMiddleware
-from fastapi import FastAPI, Form, Request, Response, Depends, HTTPException, Query
-from fastapi.responses import JSONResponse, HTMLResponse, RedirectResponse, FileResponse, StreamingResponse
-##########################
-# Generation API Imports #
-##########################
-import re
-import html
-import uuid
-import hashlib
-import numpy as np
-import soundfile as sf
-from typing import Union, Dict, List
-from pydantic import BaseModel, ValidationError, Field, field_validator
-########################################################################################
-# START-UP # Silence RVC warning about torch.nn.utils.weight_norm even though not used #
-########################################################################################
-import warnings
-warnings.filterwarnings("ignore", category=UserWarning, module="torch.nn.utils.weight_norm")
-warnings.filterwarnings("ignore", category=UserWarning, module="torch.nn.functional", lineno=5476)
-####################
-# Setup local path #
-####################
-this_dir = Path(__file__).parent.resolve() # Set this_dir as the current alltalk_tts folder
-
-####################################################
-# Load params and api_defailts from confignew.json #
-####################################################
-def load_config():
- global branding, output_directory, debug_transcode, debug_tts, debug_tts_variables, engines_available, engine_loaded, debug_rvc, debug_concat, debug_openai
- # Define the path to the confignew.json file & load in api_defaults & params
- configfile_path = this_dir / "confignew.json"
- with open(configfile_path, "r") as configfile:
- configfile_data = json.load(configfile)
- api_defaults = {
- "api_text_filtering": configfile_data["api_def"]["api_text_filtering"],
- "api_narrator_enabled": configfile_data["api_def"]["api_narrator_enabled"],
- "api_text_not_inside": configfile_data["api_def"]["api_text_not_inside"],
- "api_language": configfile_data["api_def"]["api_language"],
- "api_output_file_name": configfile_data["api_def"]["api_output_file_name"],
- "api_output_file_timestamp": configfile_data["api_def"]["api_output_file_timestamp"],
- "api_autoplay": configfile_data["api_def"]["api_autoplay"],
- "api_autoplay_volume": configfile_data["api_def"]["api_autoplay_volume"],
- "api_port_number": configfile_data["api_def"]["api_port_number"],
- "api_allowed_filter": configfile_data["api_def"]["api_allowed_filter"],
- "api_length_stripping": configfile_data["api_def"]["api_length_stripping"],
- "api_max_characters": configfile_data["api_def"]["api_max_characters"],
- "api_use_legacy_api": configfile_data["api_def"]["api_use_legacy_api"],
- "api_legacy_ip_address": configfile_data["api_def"]["api_legacy_ip_address"],
- }
- rvc_settings = {
- "rvc_enabled": configfile_data["rvc_settings"]["rvc_enabled"],
- "rvc_char_model_file": configfile_data["rvc_settings"]["rvc_char_model_file"],
- "rvc_narr_model_file": configfile_data["rvc_settings"]["rvc_narr_model_file"],
- "split_audio": configfile_data["rvc_settings"]["split_audio"],
- "autotune": configfile_data["rvc_settings"]["autotune"],
- "pitch": configfile_data["rvc_settings"]["pitch"],
- "filter_radius": configfile_data["rvc_settings"]["filter_radius"],
- "index_rate": configfile_data["rvc_settings"]["index_rate"],
- "rms_mix_rate": configfile_data["rvc_settings"]["rms_mix_rate"],
- "protect": configfile_data["rvc_settings"]["protect"],
- "hop_length": configfile_data["rvc_settings"]["hop_length"],
- "f0method": configfile_data["rvc_settings"]["f0method"],
- "embedder_model": configfile_data["rvc_settings"]["embedder_model"],
- "training_data_size": configfile_data["rvc_settings"]["training_data_size"],
- }
- branding = configfile_data.get("branding", "")
- debug_transcode = configfile_data.get("debugging").get("debug_transcode")
- debug_tts = configfile_data.get("debugging").get("debug_tts")
- debug_tts_variables = configfile_data.get("debugging").get("debug_tts_variables")
- debug_rvc = configfile_data.get("debugging").get("debug_rvc")
- debug_concat = configfile_data.get("debugging").get("debug_concat")
- debug_openai = configfile_data.get("debugging").get("debug_openai")
- output_directory = this_dir / configfile_data.get("output_folder", "")
- output_directory.mkdir(parents=True, exist_ok=True)
- tts_engines_file = os.path.join(this_dir, "system", "tts_engines", "tts_engines.json")
- with open(tts_engines_file, "r") as f:
- tts_engines_data = json.load(f)
- engines_available = [engine["name"] for engine in tts_engines_data["engines_available"]] # List of the available TTS engines from tts_engines.json
- engine_loaded = tts_engines_data["engine_loaded"] # The currently set TTS engine from tts_engines.json
- selected_model = tts_engines_data["selected_model"]
- # Conditionally import infer_pipeline if rvc_enabled is true
- if rvc_settings["rvc_enabled"]:
- from system.tts_engines.rvc.infer.infer import infer_pipeline
- else:
- infer_pipeline = None
- return configfile_data, api_defaults, rvc_settings, infer_pipeline
-
-# Call to load in the params and api_defaults
-params, api_defaults, rvc_settings, infer_pipeline = load_config()
-
-####################
-# Check for FFMPEG #
-####################
-def check_ffmpeg(this_dir):
- if sys.platform == "win32":
- # Check for ffmpeg.exe in the specified directory on Windows
- ffmpeg_path = os.path.join(this_dir, "system", "win_ffmpeg", "ffmpeg.exe")
- if os.path.exists(ffmpeg_path):
- return True
- else:
- # Check for FFmpeg on Linux and macOS using the command line
- try:
- subprocess.run(['ffmpeg', '-version'], stdout=subprocess.PIPE, stderr=subprocess.PIPE, check=True)
- return True
- except (subprocess.CalledProcessError, FileNotFoundError):
- pass
- return False
-
-# Check if FFmpeg is installed
-ffmpeg_installed = check_ffmpeg(this_dir)
-
-if not ffmpeg_installed:
- print(f"[{branding}ENG] \033[92mTranscoding :\033[94m ffmpeg not found\033[0m")
- print(f"[{branding}ENG] FFmpeg is not installed. Transcoding will be disabled.")
- print(f"[{branding}ENG] Please install FFmpeg on your system.")
-
- if sys.platform == "win32":
- print(f"[{branding}ENG] \033[92mTranscoding :\033[94m ffmpeg not found\033[0m")
- print(f"[{branding}ENG] Installation instructions for Windows:")
- print(f"[{branding}ENG] Copy the 'ffmpeg.exe' file to '{os.path.join(this_dir, 'system', 'win_ffmpeg')}'")
- else:
- print(f"[{branding}ENG] \033[92mTranscoding :\033[94m ffmpeg not found\033[0m")
- print(f"[{branding}ENG] Installation instructions:")
- print(f"[{branding}ENG] Linux (Debian-based systems): Run 'sudo apt-get install ffmpeg' in the terminal.")
- print(f"[{branding}ENG] macOS: Run 'brew install ffmpeg' in the terminal (requires Homebrew).")
- # You can choose to exit the script or continue without FFmpeg
- # exit(1)
-
-if ffmpeg_installed:
- from ffmpeg.asyncio import FFmpeg
- print(f"[{branding}ENG] \033[92mTranscoding :\033[93m ffmpeg found\033[0m")
-
-################################
-# Check for portaudio on Linux #
-################################
-try:
- import sounddevice as sd
- sounddevice_installed=True
-except OSError:
- print(f"[{branding}Startup] \033[91mInfo\033[0m PortAudio library not found. If you wish to play TTS in standalone mode through the API suite")
- print(f"[{branding}Startup] \033[91mInfo\033[0m please install PortAudio. This will not affect any other features or use of Alltalk.")
- print(f"[{branding}Startup] \033[91mInfo\033[0m If you don't know what the API suite is, then this message is nothing to worry about.")
- sounddevice_installed=False
- if sys.platform.startswith('linux'):
- print(f"[{branding}Startup] \033[91mInfo\033[0m On Linux, you can use the following command to install PortAudio:")
- print(f"[{branding}Startup] \033[91mInfo\033[0m sudo apt-get install portaudio19-dev")
-
-#######################################################################
-# Attempt to import the ModelLoader class for the selected TTS engine #
-#######################################################################
-if engine_loaded in engines_available:
- loader_module = importlib.import_module(f"system.tts_engines.{engine_loaded}.model_engine")
- tts_class = getattr(loader_module, "tts_class")
- # Setup model_engine as the way to call the functions within the Class.
- model_engine = tts_class()
-else:
- raise ValueError(f"Invalid TTS engine: {engine_loaded}")
-
-##########################################
-# Run setup function in the model_engine #
-##########################################
-@asynccontextmanager
-async def startup_shutdown(no_actual_value_it_demanded_something_be_here):
- try:
- await model_engine.setup()
- except FileNotFoundError as e:
- print(f"Error during setup: {e}. Continuing without the TTS model.")
- yield
- # Shutdown logic
-
-###############################
-# Setup FastAPI with Lifespan #
-###############################
-app = FastAPI(lifespan=startup_shutdown)
-# Allow all origins, and set other CORS options
-app.add_middleware(
- CORSMiddleware,
- allow_origins=["*"], # Set this to the specific origins you want to allow
- allow_credentials=True,
- allow_methods=["*"],
- allow_headers=["*"],
-)
-
-##############################
-# API Endpoint - /api/reload #
-##############################
-model_currently_changing = False
-@app.route("/api/reload", methods=["POST"])
-async def apifunction_reload(request: Request):
- global model_currently_changing
- if model_currently_changing == True:
- return Response(content=json.dumps({"status": "model-is currently changing"}), media_type="application/json")
- model_currently_changing == True
- requested_model = request.query_params.get("tts_method")
- if requested_model not in model_engine.available_models:
- model_currently_changing == False
- return {"status": "error", "message": "Invalid TTS method specified"}
- # Call the handle_tts_method_change method from the imported class
- success = await model_engine.handle_tts_method_change(requested_model)
- if success:
- model_engine.current_model_loaded = requested_model
- model_currently_changing == False
- return Response(content=json.dumps({"status": "model-success"}), media_type="application/json")
- else:
- model_currently_changing == False
- return Response(content=json.dumps({"status": "model-failure"}), media_type="application/json")
-
-####################################
-# API Endpoint - /api/enginereload #
-####################################
-import asyncio
-uvicorn_server = None
-def restart_self():
- """Restart the current script."""
- #print(f"[{branding}ENG] Restarting subprocess...")
- os.execv(sys.executable, ['python'] + sys.argv)
-
-async def handle_restart():
- global uvicorn_server
- if uvicorn_server:
- print(f"[{branding}ENG] Stopping uvicorn server...")
- uvicorn_server.should_exit = True
- uvicorn_server.force_exit = True
- # Check if the server has stopped
- while not uvicorn_server.is_stopped:
- await asyncio.sleep(0.1) # Check every 100ms if server is stopped
- restart_self()
-
-@app.post("/api/enginereload")
-async def apifunction_reload(request: Request):
- requested_engine = request.query_params.get("engine")
- if requested_engine not in engines_available:
- return {"status": "error", "message": "Invalid TTS engine specified"}
- print(f"[{branding}ENG]")
- print(f"[{branding}ENG] \033[94mChanging model loaded. Please wait.\033[00m")
- print(f"[{branding}ENG]")
- # Update the tts_engines.json file
- tts_engines_file = os.path.join(this_dir, "system", "tts_engines", "tts_engines.json")
- with open(tts_engines_file, "r") as f:
- tts_engines_data = json.load(f)
- for engine in tts_engines_data["engines_available"]:
- if engine["name"] == requested_engine:
- tts_engines_data["engine_loaded"] = requested_engine
- tts_engines_data["selected_model"] = engine["selected_model"]
- break
- with open(tts_engines_file, "w") as f:
- json.dump(tts_engines_data, f)
- #print(f"[{branding}ENG] Reloading the engine...")
- # Start the restart process in the background
- asyncio.create_task(handle_restart())
- # Never goes past the above asyncio as the script is killed at that point
- return Response(content=json.dumps({"status": "engine-success"}), media_type="application/json")
-
-#######################################
-# API Endpoint - /api/stop-generation #
-#######################################
-# When this endpoint it called it will set tts_stop_generation in the model_engine to True, which can be used to interrupt the current TTS generation.
-@app.put("/api/stop-generation")
-async def apifunction_stop_generation():
- if model_engine.tts_generating_lock and not model_engine.tts_stop_generation:
- model_engine.tts_stop_generation = True
- return {"message": "Cancelling current TTS generation"}
-
-#############################
-# API Endpoint - /api/audio #
-#############################
-# Gives web access to the output files as downloads
-@app.get("/audio/{filename}")
-async def apifunction_get_audio(filename: str):
- audio_path = this_dir / output_directory / filename
- return FileResponse(audio_path)
-
-##################################
-# API Endpoint - /api/audiocache #
-##################################
-@app.get("/audiocache/{filename}")
-async def apifunction_get_audio(filename: str):
- audio_path = Path({output_directory}) / filename
- if not audio_path.is_file():
- raise HTTPException(status_code=404, detail="File not found")
- response = FileResponse(path=audio_path, media_type='audio/wav', filename=filename)
- # Set caching headers
- response.headers["Cache-Control"] = "public, max-age=604800" # Cache for one week
- response.headers["ETag"] = str(audio_path.stat().st_mtime) # Use the file's last modified time as a simple ETag
- return response
-
-##############################
-# API Endpoint - /api/voices #
-##############################
-@app.get("/api/voices")
-async def apifunction_get_voices():
- if not model_engine.multivoice_capable:
- return {"status": "error", "message": f"The currently loaded TTS engine '{model_engine.engine_loaded}' does not support multiple voices."}
-
- available_voices = model_engine.voices_file_list()
- return {"status": "success", "voices": available_voices}
-
-#################################
-# API Endpoint - /api/rvcvoices #
-#################################
-@app.get("/api/rvcvoices")
-async def apifunction_get_voices():
- global params, api_defaults, rvc_settings, infer_pipeline
- params, api_defaults, rvc_settings, infer_pipeline = load_config()
- if not rvc_settings["rvc_enabled"]:
- return {"status": "success", "rvcvoices": ["Disabled"]}
- # Define the directory path
- directory = os.path.join(this_dir, "models", "rvc_voices")
- # Check if the directory exists
- if not os.path.exists(directory):
- return {"status": "success", "rvcvoices": ["Disabled"]}
- # List all .pth files recursively in the directory
- pth_files = []
- for root, dirs, files in os.walk(directory):
- for file in files:
- if file.endswith(".pth"):
- pth_files.append(os.path.relpath(os.path.join(root, file), start=directory))
- # If no .pth files found, return "Disabled"
- if not pth_files:
- return {"status": "success", "rvcvoices": ["Disabled"]}
- # Sort the list alphabetically
- pth_files.sort()
- # Add "Disabled" at the beginning of the list
- pth_files.insert(0, "Disabled")
- return {"status": "success", "rvcvoices": pth_files}
-
-#####################################
-# API Endpoint - /api/reload_config #
-#####################################
-@app.get("/api/reload_config")
-async def apifunction_reload_config():
- global params, api_defaults, rvc_settings, infer_pipeline
- model_engine.available_models = model_engine.scan_models_folder()
- params, api_defaults, rvc_settings, infer_pipeline = load_config()
- return Response("Config file reloaded successfully")
-
-#############################
-# API Endpoint - /api/ready #
-#############################
-@app.get("/api/ready")
-async def apifunction_ready():
- if model_engine.setup_has_run:
- return Response("Ready")
- else:
- return Response("Unloaded")
-
-#######################################
-# API Endpoint - /api/currentsettings #
-#######################################
-@app.get('/api/currentsettings')
-def apifunction_get_current_settings():
- settings = {
- "engines_available": engines_available, # Lists the currently available TTS engines
- "current_engine_loaded": model_engine.engine_loaded, # Lists the currently loaded TTS engine
- "models_available": [{"name": name} for name in model_engine.available_models.keys()], # Lists the currently available models that can be loaded on this TTS engine.
- "current_model_loaded": model_engine.current_model_loaded, # Lists the currently loaded model
- "manufacturer_name": model_engine.manufacturer_name, # The name of the person/company/group who made the current TTS engine
- "audio_format": model_engine.audio_format, # The default audio format the the currently loaded TTS engine generates in
- "deepspeed_capable": model_engine.deepspeed_capable, # Is the TTS engine capable of supporting DeepSpeed
- "deepspeed_available": model_engine.deepspeed_available, # Was DeepSpeed detected as being available for use
- "deepspeed_enabled": model_engine.deepspeed_enabled, # Is DeepSpeed current enabled or disabled
- "generationspeed_capable": model_engine.generationspeed_capable, # Does the TTS engine support changing speed of generated TTS
- "generationspeed_set": model_engine.generationspeed_set, # What is the currently set TTS speed
- "lowvram_capable": model_engine.lowvram_capable, # Does the TTS engine support moving the model between VRAM and System Ram
- "lowvram_enabled": model_engine.lowvram_enabled, # What is the currently enabled/disabled state of low vram
- "pitch_capable": model_engine.pitch_capable, # Does the TTS engine support pitch on tts generation
- "pitch_set": model_engine.pitch_set, # What is the currently set value for pitch
- "repetitionpenalty_capable": model_engine.repetitionpenalty_capable, # Does the TTS engine support differnt repitition penalties
- "repetitionpenalty_set": model_engine.repetitionpenalty_set, # What is the currently set repetition penalty
- "streaming_capable": model_engine.streaming_capable, # Does the model support streaming TTS generation
- "temperature_capable": model_engine.temperature_capable, # Does the TTS engine support model temperature settings
- "temperature_set": model_engine.temperature_set, # What is the currently set temperature
- "ttsengines_installed": model_engine.engine_installed, #
- "languages_capable": model_engine.languages_capable, # Does the TTS engine support multi languages
- "multivoice_capable": model_engine.multivoice_capable, # Does the TTS engine support multi voice models
- "multimodel_capable": model_engine.multimodel_capable, # Does the engine support loading many different models
- }
- return settings
-
-######################################
-# API Endpoint - /api/lowvramsetting #
-######################################
-@app.post("/api/lowvramsetting")
-async def apifunction_low_vram(request: Request, new_low_vram_value: bool):
- if not model_engine.lowvram_capable:
- return {"status": "error", "message": f"The currently loaded TTS engine '{model_engine.engine_loaded}' does not support lowvram."}
- try:
- if new_low_vram_value is None:
- raise ValueError("Missing 'low_vram' parameter")
- if model_engine.lowvram_enabled == new_low_vram_value:
- return Response(content=json.dumps({"status": "success", "message": f"[{branding}Model] LowVRAM is already {'enabled' if new_low_vram_value else 'disabled'}.",}))
- model_engine.lowvram_enabled = new_low_vram_value
- await model_engine.unload_model()
-
- if model_engine.cuda_is_available:
- if model_engine.lowvram_enabled:
- model_engine.device = "cpu"
- print(f"[{branding}Engine] \033[94mLowVRAM Enabled.\033[0m Model will move between \033[93mVRAM(cuda) <> System RAM(cpu)\033[0m")
- else:
- model_engine.device = "cuda"
- print(f"[{branding}Engine] \033[94mLowVRAM Disabled.\033[0m Model will stay in \033[93mVRAM(cuda)\033[0m")
- await model_engine.setup()
- else:
- # Handle the case where CUDA is not available
- print(f"[{branding}Engine] \033[91mError:\033[0m Nvidia CUDA is not available on this system. Unable to use LowVRAM mode.")
- model_engine.lowvram_enabled = False
-
- return Response(content=json.dumps({"status": "lowvram-success"}))
- except Exception as e:
- return Response(content=json.dumps({"status": "error", "message": str(e)}))
-
-#################################
-# API Endpoint - /api/deepspeed #
-#################################
-@app.post("/api/deepspeed")
-async def deepspeed(request: Request, new_deepspeed_value: bool):
- if not model_engine.deepspeed_capable or not model_engine.deepspeed_available:
- return {"status": "error", "message": f"The currently loaded TTS engine '{model_engine.engine_loaded}' does not support DeepSpeed or DeepSpeed is not available on this system."}
- try:
- if new_deepspeed_value is None:
- raise ValueError("Missing 'deepspeed' parameter")
- if model_engine.deepspeed_enabled == new_deepspeed_value:
- return Response(
- content=json.dumps({"status": "success", "message": f"DeepSpeed is already {'enabled' if new_deepspeed_value else 'disabled'}.",}))
- model_engine.deepspeed_enabled = new_deepspeed_value
- await model_engine.handle_deepspeed_change(new_deepspeed_value)
- return Response(content=json.dumps({"status": "deepspeed-success"}))
- except Exception as e:
- return Response(content=json.dumps({"status": "error", "message": str(e)}))
-
-#################################
-# API Endpoint - /api/voice2rvc #
-#################################
-@app.post("/api/voice2rvc")
-async def voice2rvc(input_tts_path: str = Form(...), output_rvc_path: str = Form(...), pth_name: str = Form(...), pitch: str = Form(...), method: str = Form(...)):
- try:
- # Handle "Disabled" case
- if pth_name.lower() in ["disabled", "disable"]:
- print(f"[{branding}TTS] \033[94mVoice2RVC Convert: No voice was specified or the name was Disabled\033[0m")
- return {"status": "error", "message": "No voice was specified or the name was Disabled"}
- input_tts_path = Path(input_tts_path)
- output_rvc_path = Path(output_rvc_path)
- # Check if input file exists
- if not input_tts_path.is_file():
- raise HTTPException(status_code=400, detail=f"Input file {input_tts_path} does not exist.")
- # Define the model path based on the selected model name
- pth_path = this_dir / "models" / "rvc_voices" / pth_name
- # Check if model file exists
- if not pth_path.is_file():
- raise HTTPException(status_code=400, detail=f"Model file {pth_path} does not exist.")
- # Run the RVC conversion
- result_path = run_voice2rvc(input_tts_path, output_rvc_path, pth_path, pitch, method)
- if result_path:
- return {"status": "success", "output_path": str(result_path)}
- else:
- raise HTTPException(status_code=500, detail="RVC conversion failed.")
- except Exception as e:
- print(f"Error during Voice2RVC conversion: {e}")
- raise HTTPException(status_code=500, detail=str(e))
-
-# Define the run_rvc function
-def run_voice2rvc(input_tts_path, output_rvc_path, pth_path, pitch, method):
- print(f"[{branding}GEN] \033[94mVoice2RVC Convert: Started\033[0m")
- generate_start_time = time.time()
- f0up_key = pitch
- filter_radius = rvc_settings["filter_radius"]
- index_rate = rvc_settings["index_rate"]
- rms_mix_rate = rvc_settings["rms_mix_rate"]
- protect = rvc_settings["protect"]
- hop_length = rvc_settings["hop_length"]
- f0method = method
- split_audio = rvc_settings["split_audio"]
- f0autotune = rvc_settings["autotune"]
- embedder_model = rvc_settings["embedder_model"]
- training_data_size = rvc_settings["training_data_size"]
- # Convert path variables to strings
- input_tts_path = str(input_tts_path)
- pth_path = str(pth_path)
- # Check if the model file exists
- if not os.path.isfile(pth_path):
- print(f"Model file {pth_path} does not exist. Exiting.")
- return
- # Get the directory of the model file
- model_dir = os.path.dirname(pth_path)
- # Find all .index files in the model directory
- index_files = [file for file in os.listdir(model_dir) if file.endswith(".index")]
- if len(index_files) == 1:
- index_path = str(os.path.join(model_dir, index_files[0]))
- else:
- index_path = ""
- # Call the infer_pipeline function
- infer_pipeline(f0up_key, filter_radius, index_rate, rms_mix_rate, protect, hop_length, f0method,
- input_tts_path, output_rvc_path, pth_path, index_path, split_audio, f0autotune, embedder_model, training_data_size, debug_rvc)
- generate_end_time = time.time()
- generate_elapsed_time = generate_end_time - generate_start_time
- print(f"[{branding}GEN] \033[94mVoice2RVC Convert: \033[91m{generate_elapsed_time:.2f} seconds.\033[0m")
- return output_rvc_path
-
-##################################
-# Transcode between file formats #
-##################################
-async def transcode_audio(input_file, output_format):
- print(f"[{branding}Debug] *************************************************",) if debug_transcode else None
- print(f"[{branding}Debug] transcode_audio function called (debug_transcode)",) if debug_transcode else None
- print(f"[{branding}Debug] *************************************************",) if debug_transcode else None
- print(f"[{branding}Debug] Input file : {input_file}") if debug_transcode else None
- print(f"[{branding}Debug] Output format : {output_format}") if debug_transcode else None
- if output_format == "Disabled":
- print(f"[{branding}Debug] Transcode format is set to Disabled so skipping transcode.") if debug_transcode else None
- return input_file
- if not ffmpeg_installed:
- print(f"[{branding}TTS] FFmpeg is not installed. Format conversion is not possible.")
- raise Exception("FFmpeg is not installed. Format conversion is not possible.")
- # Get the file extension of the input file
- input_extension = os.path.splitext(input_file)[1][1:].lower()
- print(f"[{branding}Debug] Input file extension: {input_extension}") if debug_transcode else None
- # Check if the input extension matches the requested output format
- if input_extension == output_format.lower():
- print(f"[{branding}Debug] Input file is already in the requested format: {output_format}") if debug_transcode else None
- return input_file
- output_file = os.path.splitext(input_file)[0] + f".{output_format}"
- print(f"[{branding}Debug] Output file: {output_file}") if debug_transcode else None
- ffmpeg_path = "ffmpeg" # Default path for Linux and macOS
- if sys.platform == "win32":
- ffmpeg_path = os.path.join(this_dir, "system", "win_ffmpeg", "ffmpeg.exe")
- ffmpeg = (FFmpeg(ffmpeg_path).option("y").input(input_file).output(output_file))
- print(f"[{branding}Debug] Transcoding to {output_format}") if debug_transcode else None
- if output_format == "opus":
- print(f"[{branding}Debug] Configuring Opus options") if debug_transcode else None
- ffmpeg.output(output_file, {"codec:a": "libopus", "b:a": "128k", "vbr": "on", "compression_level": 10, "frame_duration": 60, "application": "voip"})
- elif output_format == "aac":
- print(f"[{branding}Debug] Configuring AAC options") if debug_transcode else None
- ffmpeg.output(output_file, {"codec:a": "aac", "b:a": "192k"})
- elif output_format == "flac":
- print(f"[{branding}Debug] Configuring FLAC options") if debug_transcode else None
- ffmpeg.output(output_file, {"codec:a": "flac", "compression_level": 8})
- elif output_format == "wav":
- print(f"[{branding}Debug] Configuring WAV options") if debug_transcode else None
- ffmpeg.output(output_file, {"codec:a": "pcm_s16le"})
- elif output_format == "mp3":
- print(f"[{branding}Debug] Configuring MP3 options") if debug_transcode else None
- ffmpeg.output(output_file, {"codec:a": "libmp3lame", "b:a": "192k"})
- else:
- print(f"[{branding}TTS] Unsupported output format: {output_format}")
- raise ValueError(f"Unsupported output format: {output_format}")
- try:
- print(f"[{branding}Debug] Starting transcoding process") if debug_transcode else None
- await ffmpeg.execute()
- print(f"[{branding}Debug] Transcoding completed successfully") if debug_transcode else None
- except Exception as e:
- print(f"[{branding}TTS] Error occurred during transcoding: {str(e)}")
- raise
- print(f"[{branding}Debug] Deleting original input file") if debug_transcode else None
- os.remove(input_file)
- print(f"[{branding}Debug] Transcoding process completed") if debug_transcode else None
- print(f"[{branding}Debug] Transcoded file: {output_file}") if debug_transcode else None
- return output_file
-
-##############################
-# Central Transcode function #
-##############################
-async def transcode_audio_if_necessary(output_file, model_audio_format, output_audio_format):
- if debug_transcode:
- print(f"[{branding}Debug] model_engine.audio_format is:", model_audio_format)
- print(f"[{branding}Debug] audio format is:", output_audio_format)
- print(f"[{branding}Debug] Entering the transcode condition")
- try:
- if debug_transcode:
- print(f"[{branding}Debug] Calling transcode_audio function")
- output_file = await transcode_audio(output_file, output_audio_format)
- if debug_transcode:
- print(f"[{branding}Debug] Transcode completed successfully")
- except Exception as e:
- print(f"[{branding}Debug] Error occurred during transcoding:", str(e))
- raise
- if debug_transcode:
- print(f"[{branding}Debug] Transcode condition completed")
- # Update the output file path and URLs based on the transcoded file
- if debug_tts:
- print(f"[{branding}Debug] Updating output file paths and URLs")
- if params["api_def"]["api_use_legacy_api"]:
- output_file_url = f'http://{api_defaults["api_legacy_ip_address"]}:{api_defaults["api_port_number"]}/audio/{os.path.basename(output_file)}'
- output_cache_url = f'http://{api_defaults["api_legacy_ip_address"]}:{api_defaults["api_port_number"]}/audiocache/{os.path.basename(output_file)}'
- else:
- output_file_url = f'/audio/{os.path.basename(output_file)}'
- output_cache_url = f'/audiocache/{os.path.basename(output_file)}'
- if debug_tts:
- print(f"[{branding}Debug] Output file paths and URLs updated")
- return output_file, output_file_url, output_cache_url
-
-##############################
-#### Streaming Generation ####
-##############################
-@app.get("/api/tts-generate-streaming", response_class=StreamingResponse)
-async def apifunction_generate_streaming(text: str, voice: str, language: str, output_file: str):
- if model_engine.streaming_capable == False:
- print(f"[{branding}GEN] The selected TTS Engine does not support streaming. To use streaming, please select a TTS")
- print(f"[{branding}GEN] Engine that has streaming capability. You can find the streaming support information for")
- print(f"[{branding}GEN] each TTS Engine in the 'Engine Information' section of the Gradio interface.")
- try:
- output_file_path = f'{this_dir / output_directory / output_file}.{model_engine.audio_format}'
- stream = await generate_audio(text, voice, language, model_engine.temperature_set, model_engine.repetitionpenalty_set, 1.0, 1.0, output_file_path, streaming=True)
- return StreamingResponse(stream, media_type="audio/wav")
- except Exception as e:
- print(f"An error occurred: {e}")
- return JSONResponse(content={"error": "An error occurred"}, status_code=500)
-
-@app.post("/api/tts-generate-streaming", response_class=JSONResponse)
-async def tts_generate_streaming(request: Request, text: str = Form(...), voice: str = Form(...), language: str = Form(...), output_file: str = Form(...)):
- if model_engine.streaming_capable == False:
- print(f"[{branding}GEN] The selected TTS Engine does not support streaming. To use streaming, please select a TTS")
- print(f"[{branding}GEN] Engine that has streaming capability. You can find the streaming support information for")
- print(f"[{branding}GEN] each TTS Engine in the 'Engine Information' section of the Gradio interface.")
- try:
- output_file_path = f'{this_dir / output_directory / output_file}.{model_engine.audio_format}'
- await generate_audio(text, voice, language, model_engine.temperature_set, model_engine.repetitionpenalty_set, "1.0", "1.0", output_file_path, streaming=False)
- return JSONResponse(content={"output_file_path": str(output_file)}, status_code=200)
- except Exception as e:
- print(f"An error occurred: {e}")
- return JSONResponse(content={"error": "An error occurred"}, status_code=500)
-
-###################################
-# Central generate_audio function #
-###################################
-async def generate_audio(text, voice, language, temperature, repetition_penalty, speed, pitch, output_file, streaming=False):
- if model_engine.streaming_capable == False and streaming==True:
- print(f"[{branding}GEN] The selected TTS Engine does not support streaming. To use streaming, please select a TTS")
- print(f"[{branding}GEN] Engine that has streaming capability. You can find the streaming support information for")
- print(f"[{branding}GEN] each TTS Engine in the 'Engine Information' section of the Gradio interface.")
- # Get the async generator from the internal function
- response = model_engine.generate_tts(text, voice, language, temperature, repetition_penalty, speed, pitch, output_file, streaming)
- # If streaming, then return the generator as-is, otherwise just exhaust it and return
- if streaming:
- # Return an async generator that yields from the response
- async def stream_response():
- async for chunk in response:
- yield chunk
- return stream_response()
- else:
- # Exhaust the generator and handle any errors
- try:
- async for _ in response:
- pass
- except Exception as e:
- print(f"[GEN] Error during audio generation: {str(e)}")
- raise
-
-###########################
-#### PREVIEW VOICE API ####
-###########################
-@app.post("/api/previewvoice/", response_class=JSONResponse)
-async def apifunction_preview_voice(request: Request, voice: str = Form(...)):
- try:
- # Hardcoded settings
- language = "en"
- output_file_name = "api_preview_voice"
- # Clean the voice filename for inclusion in the text
- clean_voice_filename = re.sub(r'\.wav$', '', voice.replace(' ', '_'))
- clean_voice_filename = re.sub(r'[^a-zA-Z0-9]', ' ', clean_voice_filename)
- # Generate the audio
- text = f"Hello, this is a preview of voice {clean_voice_filename}."
- # Generate the audio
- output_file_path = this_dir / output_directory / f'{output_file_name}.{model_engine.audio_format}'
- await generate_audio(text, voice, language, model_engine.temperature_set, model_engine.repetitionpenalty_set, 1.0, 0, output_file_path, streaming=False)
- # Generate the URL
- output_file_url = f'/audio/{output_file_name}.{model_engine.audio_format}'
- # Return the response with both local file path and URL
- return JSONResponse(
- content={
- "status": "generate-success",
- "output_file_path": str(output_file_path),
- "output_file_url": str(output_file_url),
- },
- status_code=200,
- )
- except Exception as e:
- print(f"An error occurred: {e}")
- return JSONResponse(content={"error": "An error occurred"}, status_code=500)
-
-##################################################################
-# API Endpoint - OpenAI Speech API compatable endpoint Validator #
-##################################################################
-class OpenAIInput(BaseModel):
- model: str = Field(..., description="The TTS model to use. Currently ignored.")
- input: str = Field(..., max_length=4096, description="The text to generate audio for.")
- voice: str = Field(..., description="The voice to use when generating the audio.")
- response_format: str = Field(default="wav", description="The format of the audio. Currently only 'wav' is supported.")
- speed: float = Field(default=1.0, ge=0.25, le=4.0, description="The speed of the generated audio.")
-
- @field_validator('voice')
- def validate_voice(cls, value):
- supported_voices = ["alloy", "echo", "fable", "nova", "onyx", "shimmer"]
- if value not in supported_voices:
- raise ValueError(f'Voice must be one of {supported_voices}')
- return value
-
-class OpenAIGenerator:
- @staticmethod
- def validate_input(json_data: dict) -> Union[None, str]:
- try:
- OpenAIInput(**json_data)
- return None
- except ValidationError as e:
- errors = []
- for err in e.errors():
- field = err['loc'][0]
- message = err['msg']
- description = OpenAIInput.model_fields[field].field_info.description
- errors.append(f"Error in field '{field}': {message}. Description: {description}")
- return ', '.join(errors)
-
-#########################################################################
-# API Endpoint - OpenAI Speech API compatable endpoint /v1/audio/speech #
-#########################################################################
-@app.post("/v1/audio/speech", response_class=JSONResponse)
-async def openai_tts_generate(request: Request):
- try:
- json_data = await request.json()
- print(f"[{branding}Debug] Received JSON data: {json_data}") if debug_openai else None
- validation_error = OpenAIGenerator.validate_input(json_data)
- if validation_error:
- print(f"Validation error: {validation_error}") if debug_openai else None
- return JSONResponse(content={"error": validation_error}, status_code=400)
- model = json_data["model"] # Currently ignored
- input_text = json_data["input"]
- voice = json_data["voice"]
- response_format = json_data.get("response_format", "wav").lower()
- speed = json_data.get("speed", 1.0)
- print(f"[{branding}Debug] Input text: {input_text}") if debug_openai else None
- print(f"[{branding}Debug] Voice: {voice}") if debug_openai else None
- print(f"[{branding}Debug] Speed: {speed}") if debug_openai else None
- cleaned_string = html.unescape(standard_filtering(input_text))
- # Map the OpenAI voice to the corresponding internal voice
- voice_mapping = {
- "alloy": model_engine.openai_alloy,
- "echo": model_engine.openai_echo,
- "fable": model_engine.openai_fable,
- "nova": model_engine.openai_nova,
- "onyx": model_engine.openai_onyx,
- "shimmer": model_engine.openai_shimmer
- }
- mapped_voice = voice_mapping.get(voice)
- if not mapped_voice:
- print(f"Unsupported voice: {voice}")
- return JSONResponse(content={"error": "Unsupported voice"}, status_code=400)
- print(f"[{branding}Debug]Mapped voice: {mapped_voice}") if debug_openai else None
- # Generate the audio
- # Generate a unique filename
- unique_id = uuid.uuid4()
- timestamp = int(time.time())
- output_file_path = f'{this_dir / output_directory / f"openai_output_{unique_id}_{timestamp}.{model_engine.audio_format}"}'
- await generate_audio(cleaned_string, mapped_voice, "en", model_engine.temperature_set,
- model_engine.repetitionpenalty_set, speed, model_engine.pitch_set,
- output_file_path, streaming=False)
- print(f"[{branding}Debug] Audio generated at: {output_file_path}") if debug_openai else None
- if rvc_settings["rvc_enabled"]:
- if rvc_settings["rvc_char_model_file"].lower() in ["disabled", "disable"]:
- print(f"[{branding}Debug] Pass rvccharacter_voice_gen") if debug_openai else None
- pass # Skip RVC processing for character part
- else:
- print(f"[{branding}Debug] send to rvc") if debug_openai else None
- pth_path = this_dir / "models" / "rvc_voices" / rvc_settings["rvc_char_model_file"]
- run_rvc(output_file_path, pth_path, infer_pipeline)
- # Transcode the audio to the requested format
- transcoded_file_path = await transcode_for_openai(output_file_path, response_format)
- print(f"[{branding}Debug] Audio transcoded to: {transcoded_file_path}") if debug_openai else None
- # Return the audio file
- return FileResponse(transcoded_file_path, media_type=f"audio/{response_format}", filename=f"output.{response_format}")
- except Exception as e:
- print(f"[{branding}Debug] An error occurred: {str(e)}") if debug_openai else None
- return JSONResponse(content={"error": "An error occurred"}, status_code=500)
-
-###########################################################################
-# API Endpoint - OpenAI Speech API compatable endpoint Transcode Function #
-###########################################################################
-async def transcode_for_openai(input_file, output_format):
- print(f"[{branding}Debug] ************************************") if debug_openai else None
- print(f"[{branding}Debug] transcode_for_openai function called") if debug_openai else None
- print(f"[{branding}Debug] ************************************") if debug_openai else None
- print(f"[{branding}Debug] Input file : {input_file}") if debug_openai else None
- print(f"[{branding}Debug] Output format : {output_format}") if debug_openai else None
- if not ffmpeg_installed:
- print(f"[{branding}TTS] FFmpeg is not installed. Format conversion is not possible.")
- raise Exception("FFmpeg is not installed. Format conversion is not possible.")
- # Get the file extension of the input file
- input_extension = os.path.splitext(input_file)[1][1:].lower()
- print(f"[{branding}Debug] Input file extension: {input_extension}") if debug_openai else None
- # Check if the input extension matches the requested output format
- if input_extension == output_format.lower():
- print(f"[{branding}Debug] Input file is already in the requested format: {output_format}") if debug_openai else None
- return input_file
- output_file = os.path.splitext(input_file)[0] + f".{output_format}"
- print(f"[{branding}Debug] Output file: {output_file}") if debug_openai else None
- ffmpeg_path = "ffmpeg" # Default path for Linux and macOS
- if sys.platform == "win32":
- ffmpeg_path = os.path.join(this_dir, "system", "win_ffmpeg", "ffmpeg.exe")
- ffmpeg = (FFmpeg(ffmpeg_path).option("y").input(input_file).output(output_file))
- print(f"[{branding}Debug] Transcoding to {output_format}") if debug_openai else None
- if output_format == "opus":
- print(f"[{branding}Debug] Configuring Opus options") if debug_openai else None
- ffmpeg.output(output_file, {"codec:a": "libopus", "b:a": "128k", "vbr": "on", "compression_level": 10, "frame_duration": 60, "application": "voip"})
- elif output_format == "aac":
- print(f"[{branding}Debug] Configuring AAC options") if debug_openai else None
- ffmpeg.output(output_file, {"codec:a": "aac", "b:a": "192k"})
- elif output_format == "flac":
- print(f"[{branding}Debug] Configuring FLAC options") if debug_openai else None
- ffmpeg.output(output_file, {"codec:a": "flac", "compression_level": 8})
- elif output_format == "wav":
- print(f"[{branding}Debug] Configuring WAV options") if debug_openai else None
- ffmpeg.output(output_file, {"codec:a": "pcm_s16le"})
- elif output_format == "mp3":
- print(f"[{branding}Debug] Configuring MP3 options") if debug_openai else None
- ffmpeg.output(output_file, {"codec:a": "libmp3lame", "b:a": "192k"})
- elif output_format in ["ogg", "m4a"]:
- if output_format == "ogg":
- print(f"[{branding}Debug] Configuring OGG options") if debug_openai else None
- ffmpeg.output(output_file, {"codec:a": "libvorbis"})
- elif output_format == "m4a":
- print(f"[{branding}Debug] Configuring M4A options") if debug_openai else None
- ffmpeg.output(output_file, {"codec:a": "aac", "b:a": "192k"})
- else:
- print(f"[{branding}TTS] Unsupported output format: {output_format}")
- raise ValueError(f"Unsupported output format: {output_format}")
- try:
- print(f"[{branding}Debug] Starting transcoding process") if debug_openai else None
- await ffmpeg.execute()
- print(f"[{branding}Debug] Transcoding completed successfully") if debug_openai else None
- except Exception as e:
- print(f"[{branding}TTS] Error occurred during transcoding: {str(e)}")
- raise
- print(f"[{branding}Debug] Transcoding process completed") if debug_openai else None
- print(f"[{branding}Debug] Transcoded file: {output_file}") if debug_openai else None
- return output_file
-
-######################################################################################
-# API Endpoint - OpenAI Speech API compatable endpoint change engine voices Function #
-######################################################################################
-class VoiceMappings(BaseModel):
- alloy: str
- echo: str
- fable: str
- nova: str
- onyx: str
- shimmer: str
-
-@app.put("/api/openai-voicemap")
-async def update_openai_voice_mappings(mappings: VoiceMappings):
- # Update in-memory versions (wont display in Gradio without a restart as gradio caches)
- model_engine.openai_alloy = mappings.alloy
- model_engine.openai_echo = mappings.echo
- model_engine.openai_fable = mappings.fable
- model_engine.openai_nova = mappings.nova
- model_engine.openai_onyx = mappings.onyx
- model_engine.openai_shimmer = mappings.shimmer
- # Update model_settings.json file for the currently loaded TTS engine
- try:
- settings_file = this_dir / "system" / "tts_engines" / model_engine.engine_loaded / "model_settings.json"
- with open(settings_file, "r+") as f:
- settings = json.load(f)
- settings["openai_voices"] = mappings.dict()
- f.seek(0)
- json.dump(settings, f, indent=4)
- f.truncate()
- except Exception as e:
- raise HTTPException(status_code=500, detail=f"Failed to update model settings file: {str(e)}")
- return {"message": "OpenAI voice mappings updated successfully"}
-
-#######################
-# Play at the console #
-#######################
-def play_audio(file_path, volume):
- print(f"[{branding}GEN] \033[94m File path is: {file_path}") if debug_tts else None
- print(f"[{branding}GEN] \033[94m Volume is: {volume}") if debug_tts else None
- normalized_file_path = os.path.normpath(file_path)
- print(f"[{branding}GEN] \033[94m Normalized file path is: {normalized_file_path}") if debug_tts else None
- directory = os.path.dirname(normalized_file_path)
- if os.path.isdir(directory):
- print(f"[{branding}GEN] \033[94m Directory contents: {os.listdir(directory)}") if debug_tts else None
- else:
- print(f"[{branding}GEN] \033[94mError: Directory does not exist: {directory}") if debug_tts else None
- if not os.path.isfile(normalized_file_path):
- print(f"[{branding}GEN] \033[94mError: File does not exist: {normalized_file_path}\033[0m") if debug_tts else None
- return
- # Check for AAC format
- if normalized_file_path.lower().endswith('.aac'):
- print(f"[{branding}GEN] \033[94mPlay Audio : \033[0mAAC format files cannot be played at the console. Please choose another format")
- return
- try:
- print(f"[{branding}GEN] \033[94mPlay Audio : \033[0mPlaying audio at console")
- data, fs = sf.read(normalized_file_path)
- sd.play(volume * data, fs)
- sd.wait()
- except Exception as e:
- print(f"[{branding}GEN] \033[94mError playing audio file: {e}\033[0m")
-
-class Request(BaseModel):
- # Define the structure of the 'Request' class if needed
- pass
-
-
-class TTSGenerator:
- @staticmethod
- def validate_json_input(json_data: Union[Dict, str]) -> Union[None, str]:
- try:
- if isinstance(json_data, str):
- json_data = json.loads(json_data)
- JSONInput(**json_data)
- return None # JSON is valid
- except ValidationError as e:
- return str(e)
-
-#################################################################
-# /api/tts-generate Generation API Endpoint Narration Filtering #
-#################################################################
-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
-
-def standard_filtering(text_input):
- text_output = (text_input
- .replace("***", "")
- .replace("**", "")
- .replace("*", "")
- .replace("\n\n", "\n")
- .replace("'", "'")
- )
- return text_output
-
-#################################################################
-# /api/tts-generate Generation API Endpoint Narration Combining #
-#################################################################
-def combine(output_file_timestamp, output_file_name, audio_files, target_sample_rate=44100):
- audio = np.array([])
- try:
- for audio_file in audio_files:
- normalized_audio_file = os.path.normpath(audio_file)
- print(f"[{branding}Debug] Processing file: {normalized_audio_file}") if debug_concat else None
- # Check if the file exists
- if not os.path.isfile(normalized_audio_file):
- print(f"[{branding}Debug] Error: File does not exist: {normalized_audio_file}") if debug_concat else None
- return None, None
- # Read the audio file
- audio_data, current_sample_rate = sf.read(normalized_audio_file)
- print(f"[{branding}Debug] Read file: {normalized_audio_file}, Sample rate: {current_sample_rate}, Data shape: {audio_data.shape}") if debug_concat else None
- # Resample if necessary
- if current_sample_rate != target_sample_rate:
- print(f"[{branding}Debug] Resampling file from {current_sample_rate} to {target_sample_rate} Hz") if debug_concat else None
- audio_data = librosa.resample(audio_data, orig_sr=current_sample_rate, target_sr=target_sample_rate)
- # Concatenate audio data
- if audio.size == 0:
- audio = audio_data
- else:
- audio = np.concatenate((audio, audio_data))
- if output_file_timestamp:
- timestamp = int(time.time())
- output_file_path = os.path.join(this_dir / "outputs" / f'{output_file_name}_{timestamp}_combined.wav')
- sf.write(output_file_path, audio, target_sample_rate)
- # Legacy API or New API return
- if params["api_def"]["api_use_legacy_api"]:
- output_file_url = f"http://{api_defaults['api_legacy_ip_address']}:{params['api_def']['api_port_number']}/audio/{output_file_name}_{timestamp}_combined.wav"
- output_cache_url = f"http://{api_defaults['api_legacy_ip_address']}:{params['api_def']['api_port_number']}/audiocache/{output_file_name}_{timestamp}_combined.wav"
- else:
- output_file_url = f'/audio/{output_file_name}_{timestamp}_combined.wav'
- output_cache_url = f'/audiocache/{output_file_name}_{timestamp}_combined.wav'
- else:
- output_file_path = os.path.join(this_dir / "outputs" / f'{output_file_name}_combined.wav')
- sf.write(output_file_path, audio, target_sample_rate)
- # Legacy API or New API return
- if params["api_def"]["api_use_legacy_api"]:
- output_file_url = f"http://{api_defaults['api_legacy_ip_address']}:{api_defaults['api_port_number']}/audio/{output_file_name}_combined.wav"
- output_cache_url = f"http://{api_defaults['api_legacy_ip_address']}:{api_defaults['api_port_number']}/audiocache/{output_file_name}_combined.wav"
- else:
- output_file_url = f'/audio/{output_file_name}_combined.wav'
- output_cache_url = f'/audiocache/{output_file_name}_combined.wav'
- print(f"[{branding}Debug] Output file changed to:", output_file_path) if debug_concat else None
- print(f"[{branding}Debug] Output file changed to:", output_file_url) if debug_concat else None
- print(f"[{branding}Debug] Output file changed to:", output_cache_url) if debug_concat else None
- except Exception as e:
- print(f"Error occurred: {e}")
- return output_file_path, output_file_url, output_cache_url
-
-##################################
-# Central RVC Generation request #
-##################################
-def run_rvc(input_tts_path, pth_path, pitch, infer_pipeline):
- generate_start_time = time.time()
- f0up_key = pitch
- filter_radius = rvc_settings["filter_radius"]
- index_rate = rvc_settings["index_rate"]
- rms_mix_rate = rvc_settings["rms_mix_rate"]
- protect = rvc_settings["protect"]
- hop_length = rvc_settings["hop_length"]
- f0method = rvc_settings["f0method"]
- split_audio = rvc_settings["split_audio"]
- f0autotune = rvc_settings["autotune"]
- embedder_model = rvc_settings["embedder_model"]
- training_data_size = rvc_settings["training_data_size"]
- # Convert path variables to strings
- input_tts_path = str(input_tts_path)
- pth_path = str(pth_path)
- # Check if the model file exists
- if not os.path.isfile(pth_path):
- print(f"Model file {pth_path} does not exist. Exiting.")
- return
- # Get the directory of the model file
- model_dir = os.path.dirname(pth_path)
- # Get the filename of pth_path
- pth_filename = os.path.basename(pth_path)
- # Find all .index files in the model directory
- index_files = [file for file in os.listdir(model_dir) if file.endswith(".index")]
- if len(index_files) == 1:
- index_path = str(os.path.join(model_dir, index_files[0]))
- # Get the filename of index_path
- index_filename = os.path.basename(index_path)
- index_filename_print = index_filename
- index_size_print = training_data_size
- elif len(index_files) > 1:
- print(f"[{branding}GEN] \033[94mRVC Convert :\033[0m Multiple RVC index files found in the models folder where \033[93m{pth_filename}\033[0m is")
- print(f"[{branding}GEN] \033[94mRVC Convert :\033[0m located. Unable to determine which index to use. Continuing without an index file.")
- index_path = ""
- index_filename = None
- index_filename_print = "None used"
- index_size_print = "N/A"
- else:
- index_path = ""
- index_filename = None
- index_filename_print = "None used"
- index_size_print = "N/A"
- # Set the output path to be the same as the input path
- output_rvc_path = input_tts_path
- if debug_rvc:
- debug_lines = [
- f"[{branding}Debug] ************************************",
- f"[{branding}Debug] run_rvc function called (debug_rvc)",
- f"[{branding}Debug] ***********************************",
- f"[{branding}Debug] f0up_key : {f0up_key}",
- f"[{branding}Debug] filter_radius : {filter_radius}",
- f"[{branding}Debug] index_rate : {index_rate}",
- f"[{branding}Debug] rms_mix_rate : {rms_mix_rate}",
- f"[{branding}Debug] protect : {protect}",
- f"[{branding}Debug] hop_length : {hop_length}",
- f"[{branding}Debug] f0method : {f0method}",
- f"[{branding}Debug] input_tts_path : {input_tts_path}",
- f"[{branding}Debug] output_rvc_path : {output_rvc_path}",
- f"[{branding}Debug] pth_path : {pth_path}",
- f"[{branding}Debug] index_path : {index_path}",
- f"[{branding}Debug] split_audio : {split_audio}",
- f"[{branding}Debug] f0autotune : {f0autotune}",
- f"[{branding}Debug] embedder_model : {embedder_model}",
- f"[{branding}Debug] training_data_size: {training_data_size}"
- ]
- debug_info = "\n".join(debug_lines)
- print(debug_info)
- # Call the infer_pipeline function
- infer_pipeline(f0up_key, filter_radius, index_rate, rms_mix_rate, protect, hop_length, f0method,
- input_tts_path, output_rvc_path, pth_path, index_path, split_audio, f0autotune, embedder_model, training_data_size, debug_rvc)
- generate_end_time = time.time()
- generate_elapsed_time = generate_end_time - generate_start_time
- print(f"[{branding}GEN] \033[94mRVC Convert : \033[94mModel: \033[93m{pth_filename} \033[94mIndex: \033[93m{index_filename_print}\033[0m")
- print(f"[{branding}GEN] \033[94mRVC Convert : \033[93m{generate_elapsed_time:.2f} seconds. \033[94mMethod: \033[93m{f0method} \033[94mIndex size used \033[93m{index_size_print}\033[0m")
- return
-
-####################################################################
-# /api/tts-generate Generation API Endpoint Narration RVC handling #
-####################################################################
-def process_rvc_narrator(part_type, voice_gen, pitch, default_model_file, output_file, infer_pipeline):
- if voice_gen.lower() in ["disabled", "disable"]:
- return
- else:
- voice_gen_path = this_dir / "models" / "rvc_voices" / voice_gen
- pth_path = voice_gen_path if voice_gen else default_model_file
- run_rvc(output_file, pth_path, pitch, infer_pipeline)
-
-#############################################################
-# /api/tts-generate Generation API Endpoint JSON validation #
-#############################################################
-class JSONInput(BaseModel):
- text_input: str = Field(..., max_length=int(api_defaults["api_max_characters"]), description=f"text_input needs to be {api_defaults['api_max_characters']} characters or less.")
- text_filtering: str = Field(..., pattern="^(none|standard|html)$", description="text_filtering needs to be 'none', 'standard' or 'html'.")
- #character_voice_gen: str = Field(..., pattern=r'^[\(\)a-zA-Z0-9\_\-./\s]+$', description="character_voice_gen needs to be the name of a valid voice for the loaded TTS engine.")
- rvccharacter_voice_gen: str = Field(..., description="rvccharacter_voice_gen needs to be the name of a valid pth file in the 'folder\\file.pth' format or the word 'Disabled'.")
- rvccharacter_pitch: float = Field(..., description="RVC Character pitch needs to be a number between -24 and 24")
- narrator_enabled: str = Field(..., pattern="^(true|false|silent)$", description="narrator_enabled needs to be 'true', 'false' or 'silent'.")
- narrator_voice_gen: str = Field(..., pattern=r'^[\(\)a-zA-Z0-9\_\-./\s]+$', description="character_voice_gen needs to be the name of a valid voice for the loaded TTS engine.")
- rvcnarrator_voice_gen: str = Field(..., description="rvcnarrator_voice_gen needs to be the name of a valid pth file in the 'folder\\file.pth' format or the word 'Disabled'.")
- rvcnarrator_pitch: float = Field(..., description="RVC Narrator pitch needs to be a number between -24 and 24")
- text_not_inside: str = Field(..., pattern="^(character|narrator|silent)$", description="text_not_inside needs to be 'character', 'narrator' or 'silent'.")
- language: str = Field(..., pattern="^(ar|zh-cn|zh|cs|nl|en|fr|de|hu|hi|it|ja|ko|pl|pt|ru|es|tr)$", description="language needs to be one of the following: ar, zh-cn, zh, cs, nl, en, fr, de, hu, hi, it, ja, ko, pl, pt, ru, es, tr.")
- output_file_name: str = Field(..., pattern="^[a-zA-Z0-9_]+$", description="output_file_name needs to be the name without any special characters or file extension, e.g., 'filename'.")
- output_file_timestamp: bool = Field(..., description="output_file_timestamp needs to be true or false.")
- autoplay: bool = Field(..., description="autoplay needs to be a true or false value.")
- autoplay_volume: float = Field(..., ge=0.1, le=1.0, description="autoplay_volume needs to be from 0.1 to 1.0.")
- speed: float = Field(..., ge=0.25, le=2.0, description="speed needs to be between 0.25 and 2.0.")
- pitch: float = Field(..., ge=-10, le=10, description="pitch needs to be between -10 and 10.")
- temperature: float = Field(..., ge=0.1, le=1.0, description="temperature needs to be between 0.1 and 1.0.")
- repetition_penalty: float = Field(..., ge=1.0, le=20.0, description="repetition_penalty needs to be between 1.0 and 20.0.")
-
- @classmethod
- def validate_autoplay_volume(cls, value):
- if not (0.1 <= value <= 1.0):
- raise ValueError("Autoplay volume must be between 0.1 and 1.0")
- return value
-
- def validate_rvccharacter_voice_gen(cls, v):
- if v.lower() == "disabled":
- return v
- pattern = re.compile(r'^.*\.(pth)$')
- if not pattern.match(v):
- raise ValueError("rvccharacter_voice_gen needs to be the name of a valid pth file in the 'folder\\file.pth' format or 'Disabled'.")
- return v
-
- def validate_rvnarrator_voice_gen(cls, v):
- if v.lower() == "disabled":
- return v
- pattern = re.compile(r'^.*\.(pth)$')
- if not pattern.match(v):
- raise ValueError("rvcnarrator_voice_gen needs to be the name of a valid pth file in the 'folder\\file.pth' format or 'Disabled'.")
- return v
-
- def validate_pitches(cls, value):
- try:
- num_value = float(value)
- except ValueError:
- raise ValueError("Pitch must be a number or a string representing a number.")
- if not -24 <= num_value <= 24:
- raise ValueError("Pitch needs to be a number between -24 and 24.")
- return value
-
-def validate_json_input(json_input_data):
- try:
- JSONInput(**json_input_data)
- except ValidationError as e:
- error_messages = []
- for error in e.errors():
- field = error["loc"][0]
- description = JSONInput.__fields__[field].description
- error_messages.append(f"{field}: {description}")
- error_message = "\n".join(error_messages)
- print(f"[{branding}API] \033[91mError with API request:\033[0m", error_message)
- return error_message
- return None
-
-###############################################################
-# /api/tts-generate Generation API Endpoint pull fresh values #
-###############################################################
-def get_api_text_filtering():
- return api_defaults["api_text_filtering"]
-def get_api_narrator_enabled():
- return api_defaults["api_narrator_enabled"]
-def get_api_text_not_inside():
- return api_defaults["api_text_not_inside"]
-def get_api_language():
- return api_defaults["api_language"]
-def get_api_output_file_name():
- return api_defaults["api_output_file_name"]
-def get_api_output_file_timestamp():
- return api_defaults["api_output_file_timestamp"]
-def get_api_autoplay():
- return api_defaults["api_autoplay"]
-def get_api_autoplay_volume():
- return api_defaults["api_autoplay_volume"]
-def get_params_speed():
- return model_engine.generationspeed_set
-def get_params_temperature():
- return model_engine.temperature_set
-def get_params_repetition():
- value = model_engine.repetitionpenalty_set
- return float(str(value).replace(',', '.'))
-def get_params_pitch():
- return model_engine.pitch_set
-def get_character_voice_gen():
- return model_engine.def_character_voice
-def get_rvccharacter_voice_gen():
- return rvc_settings["rvc_char_model_file"]
-def get_rvccharacter_pitch():
- return rvc_settings["pitch"]
-def get_narrator_voice_gen():
- return model_engine.def_narrator_voice
-def get_rvcnarrator_voice_gen():
- return rvc_settings["rvc_narr_model_file"]
-def get_rvcnarrator_pitch():
- return rvc_settings["pitch"]
-
-#############################################
-# /api/tts-generate Generation API Endpoint #
-#############################################
-@app.post("/api/tts-generate", response_class=JSONResponse)
-async def apifunction_generate_tts_standard(
- text_input: str = Form(...),
- text_filtering: str = Form(None),
- character_voice_gen: str = Form(None),
- rvccharacter_voice_gen: str = Form(None),
- rvccharacter_pitch: float = Form(None),
- narrator_enabled: str = Form(None),
- narrator_voice_gen: str = Form(None),
- rvcnarrator_voice_gen: str = Form(None),
- rvcnarrator_pitch: float = Form(None),
- text_not_inside: str = Form(None),
- language: str = Form(None),
- output_file_name: str = Form(None),
- output_file_timestamp: bool = Form(None),
- autoplay: bool = Form(None),
- autoplay_volume: float = Form(None),
- streaming: bool = Form(False),
- speed: float = Form(None),
- temperature: float = Form(None),
- repetition_penalty: float = Form(None),
- pitch: float = Form(None),
- _text_filtering: str = Depends(get_api_text_filtering),
- _character_voice_gen: str = Depends(get_character_voice_gen),
- _rvccharacter_voice_gen: str = Depends(get_rvccharacter_voice_gen),
- _rvccharacter_pitch: str = Depends(get_rvccharacter_pitch),
- _narrator_enabled: str = Depends(get_api_narrator_enabled),
- _narrator_voice_gen: str = Depends(get_narrator_voice_gen),
- _rvcnarrator_voice_gen: str = Depends(get_rvcnarrator_voice_gen),
- _rvcnarrator_pitch: str = Depends(get_rvcnarrator_pitch),
- _text_not_inside: str = Depends(get_api_text_not_inside),
- _language: str = Depends(get_api_language),
- _output_file_name: str = Depends(get_api_output_file_name),
- _output_file_timestamp: bool = Depends(get_api_output_file_timestamp),
- _autoplay: bool = Depends(get_api_autoplay),
- _autoplay_volume: float = Depends(get_api_autoplay_volume),
- _speed: float = Depends(get_params_speed),
- _temperature: float = Depends(get_params_temperature),
- _repetition_penalty: float = Depends(get_params_repetition),
- _pitch: float = Depends(get_params_pitch),
-):
- if debug_tts or debug_tts_variables:
- debug_lines = [
- f"[{branding}Debug] *******************************************************",
- f"[{branding}Debug] /api/tts_generate function called (debug_tts_variables)",
- f"[{branding}Debug] **** PRE validation checks ****",
- f"[{branding}Debug] *******************************************************",
- f"[{branding}Debug] Defaults will be pulled from Settings if not specified in API Request!",
- f"[{branding}Debug] max_characters : {api_defaults['api_max_characters']}",
- f"[{branding}Debug] length_stripping : {api_defaults['api_length_stripping']}",
- f"[{branding}Debug] legacy_api : {api_defaults['api_use_legacy_api']}",
- f"[{branding}Debug] legacy_api IPadd : {api_defaults['api_legacy_ip_address']}",
- f"[{branding}Debug] allowed_filter : {api_defaults['api_allowed_filter']}",
- f"[{branding}Debug] text_filtering : {text_filtering}",
- f"[{branding}Debug] character_voice_gen : {character_voice_gen}",
- f"[{branding}Debug] rvccharacter_voice_gen : {rvccharacter_voice_gen}",
- f"[{branding}Debug] rvccharacter_pitch : {rvccharacter_pitch}",
- f"[{branding}Debug] narrator_enabled : {narrator_enabled}",
- f"[{branding}Debug] narrator_voice_gen : {narrator_voice_gen}",
- f"[{branding}Debug] rvcnarrator_voice_gen : {rvcnarrator_voice_gen}",
- f"[{branding}Debug] rvcnarrator_pitch : {rvcnarrator_pitch}",
- f"[{branding}Debug] text_not_inside : {text_not_inside}",
- f"[{branding}Debug] language : {language}",
- f"[{branding}Debug] output_file_name : {output_file_name}",
- f"[{branding}Debug] output_file_timestamp : {output_file_timestamp}",
- f"[{branding}Debug] autoplay : {autoplay}",
- f"[{branding}Debug] autoplay_volume : {autoplay_volume}",
- f"[{branding}Debug] streaming : {streaming}",
- f"[{branding}Debug] speed : {speed}",
- f"[{branding}Debug] temperature : {temperature}",
- f"[{branding}Debug] repetition_penalty : {repetition_penalty}",
- f"[{branding}Debug] pitch : {pitch}"
- ]
- debug_info = "\n".join(debug_lines)
- print(debug_info)
- text_filtering = text_filtering or _text_filtering
- character_voice_gen = character_voice_gen or _character_voice_gen
- rvccharacter_voice_gen = rvccharacter_voice_gen or _rvccharacter_voice_gen
- rvccharacter_pitch = rvccharacter_pitch if rvccharacter_pitch is not None else _rvccharacter_pitch
- narrator_enabled = narrator_enabled or _narrator_enabled # NEED TO COME BACK AND LOOK AT THIS!!!
- narrator_voice_gen = narrator_voice_gen or _narrator_voice_gen
- rvcnarrator_voice_gen = rvcnarrator_voice_gen or _rvcnarrator_voice_gen
- rvcnarrator_pitch = rvcnarrator_pitch if rvcnarrator_pitch is not None else _rvcnarrator_pitch
- text_not_inside = text_not_inside or _text_not_inside
- language = language or _language
- output_file_name = output_file_name or _output_file_name
- output_file_timestamp = output_file_timestamp if output_file_timestamp is not None else _output_file_timestamp
- autoplay = autoplay if autoplay is not None else _autoplay
- autoplay_volume = autoplay_volume or _autoplay_volume
- speed = speed or _speed
- temperature = temperature or _temperature
- repetition_penalty = repetition_penalty or _repetition_penalty
- pitch = pitch or _pitch
- try:
- json_input_data = {
- "text_input": text_input,
- "text_filtering": text_filtering,
- "character_voice_gen": character_voice_gen,
- "rvccharacter_voice_gen": rvccharacter_voice_gen,
- "rvccharacter_pitch": rvccharacter_pitch,
- "narrator_enabled": narrator_enabled,
- "narrator_voice_gen": narrator_voice_gen,
- "rvcnarrator_voice_gen": rvcnarrator_voice_gen,
- "rvcnarrator_pitch": rvcnarrator_pitch,
- "text_not_inside": text_not_inside,
- "language": language,
- "output_file_name": output_file_name,
- "output_file_timestamp": output_file_timestamp,
- "autoplay": autoplay,
- "autoplay_volume": autoplay_volume,
- "streaming": streaming,
- "speed": speed,
- "temperature": temperature,
- "repetition_penalty": repetition_penalty,
- "pitch": pitch,
- }
- JSONresult = validate_json_input(json_input_data)
- if JSONresult is None:
- pass
- else:
- return JSONResponse(content={"error": JSONresult}, status_code=400)
-
- if debug_tts or debug_tts_variables:
- debug_lines = [
- f"[{branding}Debug] *******************************************************",
- f"[{branding}Debug] /api/tts_generate function called (debug_tts_variables)",
- f"[{branding}Debug] **** POST validation checks ****",
- f"[{branding}Debug] *******************************************************",
- f"[{branding}Debug] Defaults will be pulled from Settings if not specified in API Request!",
- f"[{branding}Debug] max_characters : {api_defaults['api_max_characters']}",
- f"[{branding}Debug] length_stripping : {api_defaults['api_length_stripping']}",
- f"[{branding}Debug] legacy_api : {api_defaults['api_use_legacy_api']}",
- f"[{branding}Debug] legacy_api IPadd : {api_defaults['api_legacy_ip_address']}",
- f"[{branding}Debug] allowed_filter : {api_defaults['api_allowed_filter']}",
- f"[{branding}Debug] text_filtering : {text_filtering}",
- f"[{branding}Debug] character_voice_gen : {character_voice_gen}",
- f"[{branding}Debug] rvccharacter_voice_gen : {rvccharacter_voice_gen}",
- f"[{branding}Debug] rvccharacter_pitch : {rvccharacter_pitch}",
- f"[{branding}Debug] narrator_enabled : {narrator_enabled}",
- f"[{branding}Debug] narrator_voice_gen : {narrator_voice_gen}",
- f"[{branding}Debug] rvcnarrator_voice_gen : {rvcnarrator_voice_gen}",
- f"[{branding}Debug] rvcnarrator_pitch : {rvcnarrator_pitch}",
- f"[{branding}Debug] text_not_inside : {text_not_inside}",
- f"[{branding}Debug] language : {language}",
- f"[{branding}Debug] output_file_name : {output_file_name}",
- f"[{branding}Debug] output_file_timestamp : {output_file_timestamp}",
- f"[{branding}Debug] autoplay : {autoplay}",
- f"[{branding}Debug] autoplay_volume : {autoplay_volume}",
- f"[{branding}Debug] streaming : {streaming}",
- f"[{branding}Debug] speed : {speed}",
- f"[{branding}Debug] temperature : {temperature}",
- f"[{branding}Debug] repetition_penalty : {repetition_penalty}",
- f"[{branding}Debug] pitch : {pitch}"
- ]
- debug_info = "\n".join(debug_lines)
- print(debug_info)
- if narrator_enabled.lower() == "silent" and text_not_inside.lower() == "silent":
- print(f"[{branding}GEN] \033[92mBoth Narrator & Text-not-inside are set to \033[91msilent\033[92m. If you get no TTS, this is why.\033[0m")
- if narrator_enabled.lower() in ["true", "silent"]:
- if model_engine.lowvram_enabled and model_engine.device == "cpu":
- await model_engine.handle_lowvram_change()
- model_engine.tts_narrator_generatingtts = True
- print(f"[{branding}Debug] Moved into Narrator") if debug_tts else None
- print(f"[{branding}Debug] original text : {text_input}") if debug_tts else None
- processed_parts = process_text(text_input)
- audio_files_all_paragraphs = []
- for part_type, part in processed_parts:
- # Skip parts that are too short
- if len(part.strip()) <= int(params["api_def"]["api_length_stripping"]):
- continue
- # Determine the voice to use based on the part type
- if part_type == 'narrator':
- if narrator_enabled.lower() == "silent":
- print(f"[{branding}GEN] \033[95mNarrator Silent:\033[0m", part) # Green
- continue # Skip generating audio for narrator parts if set to "silent"
- voice_to_use = narrator_voice_gen
- print(f"[{branding}GEN] \033[92mNarrator:\033[0m", part) # Purple
- elif part_type == 'character':
- voice_to_use = character_voice_gen
- print(f"[{branding}GEN] \033[36mCharacter:\033[0m", part) # Yellow
- else:
- # Handle ambiguous parts based on user preference
- if text_not_inside == "silent":
- print(f"[{branding}GEN] \033[95mText-not-inside Silent:\033[0m", part) # Purple
- continue # Skip generating audio for ambiguous parts if set to "silent"
- voice_to_use = character_voice_gen if text_not_inside == "character" else narrator_voice_gen
- voice_description = "\033[36mCharacter (Text-not-inside)\033[0m" if text_not_inside == "character" else "\033[92mNarrator (Text-not-inside)\033[0m"
- print(f"[{branding}GEN] {voice_description}:", part)
- # 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(rf'{api_defaults["api_allowed_filter"]}', '', cleaned_part)
- print(f"[{branding}Debug] text after api_allowed: {text_input}") if debug_tts else None
- # Remove all newline characters (single or multiple)
- cleaned_part = re.sub(r'\n+', ' ', cleaned_part)
- output_file = this_dir / output_directory / f'{output_file_name}_{uuid.uuid4()}_{int(time.time())}.{model_engine.audio_format}'
- output_file_str = output_file.as_posix()
- response = await generate_audio(cleaned_part, voice_to_use, language,temperature, repetition_penalty, speed, pitch, output_file_str, streaming)
- audio_path = output_file_str
- if rvc_settings["rvc_enabled"]:
- if part_type == 'character':
- process_rvc_narrator(part_type, rvccharacter_voice_gen, rvccharacter_pitch, rvc_settings["rvc_char_model_file"], output_file, infer_pipeline)
- elif part_type == 'narrator':
- process_rvc_narrator(part_type, rvcnarrator_voice_gen, rvcnarrator_pitch, rvc_settings["rvc_narr_model_file"], output_file, infer_pipeline)
- else:
- if text_not_inside == 'character':
- process_rvc_narrator('character', rvccharacter_voice_gen, rvccharacter_pitch, rvc_settings["rvc_char_model_file"], output_file, infer_pipeline)
- elif text_not_inside == 'narrator':
- process_rvc_narrator('narrator', rvcnarrator_voice_gen, rvcnarrator_pitch, rvc_settings["rvc_narr_model_file"], output_file, infer_pipeline)
- print(f"[{branding}Debug] Appending audio path to list") if debug_tts else None
- audio_files_all_paragraphs.append(audio_path)
- # Combine audio files across paragraphs
- model_engine.tts_narrator_generatingtts = False
- print(f"[{branding}Debug] Narrator sending to combine") if debug_tts else None
- output_file_path, output_file_url, output_cache_url = combine(output_file_timestamp, output_file_name, audio_files_all_paragraphs)
- # Transcode audio if necessary
- model_audio_format = str(model_engine.audio_format).lower()
- output_audio_format = str(params["transcode_audio_format"]).lower()
- if output_audio_format == "disabled":
- pass
- else:
- if model_audio_format != output_audio_format and not model_engine.tts_narrator_generatingtts:
- print(f"[{branding}Debug] Pre Transcode call - Output file: {output_file_path}") if debug_tts else None
- output_file_path, output_file_url, output_cache_url = await transcode_audio_if_necessary(output_file_path, model_audio_format, output_audio_format)
- print(f"[{branding}Debug] Post Transcode call - Output file: {output_file_path}") if debug_tts else None
- if sounddevice_installed == False:
- autoplay = False
- if autoplay:
- play_audio(output_file_path, autoplay_volume)
- # Move model back to cpu system ram if needed.
- if model_engine.lowvram_enabled and model_engine.device == "cuda" and model_engine.lowvram_capable:
- await model_engine.handle_lowvram_change()
- return JSONResponse(content={"status": "generate-success", "output_file_path": str(output_file_path), "output_file_url": str(output_file_url), "output_cache_url": str(output_cache_url)}, status_code=200)
- else:
- print(f"[{branding}Debug] Moved into Standard generation") if debug_tts else None
- print(f"[{branding}Debug] original text : {text_input}") if debug_tts else None
- if output_file_timestamp:
- timestamp = int(time.time())
- # Generate a standard UUID
- original_uuid = uuid.uuid4()
- # Hash the UUID using SHA-256
- hash_object = hashlib.sha256(str(original_uuid).encode())
- hashed_uuid = hash_object.hexdigest()
- # Truncate to the desired length, for example, 16 characters
- short_uuid = hashed_uuid[:5]
- output_file_path = this_dir / output_directory / f'{output_file_name}_{timestamp}{short_uuid}.{model_engine.audio_format}'
- #Legacy API or New API return
- if params["api_def"]["api_use_legacy_api"]:
- output_file_url = f'http://{api_defaults["api_legacy_ip_address"]}:{api_defaults["api_port_number"]}/audio/{output_file_name}_{timestamp}{short_uuid}.{model_engine.audio_format}'
- output_cache_url = f'http://{api_defaults["api_legacy_ip_address"]}:{api_defaults["api_port_number"]}/audiocache/{output_file_name}_{timestamp}{short_uuid}.{model_engine.audio_format}'
- else:
- output_file_url = f'/audio/{output_file_name}_{timestamp}{short_uuid}.{model_engine.audio_format}'
- output_cache_url = f'/audiocache/{output_file_name}_{timestamp}{short_uuid}.{model_engine.audio_format}'
- else:
- output_file_path = this_dir / output_directory / f"{output_file_name}.{model_engine.audio_format}"
- # Legacy API or New API return
- if params["api_def"]["api_use_legacy_api"]:
- output_file_url = f'http://{api_defaults["api_legacy_ip_address"]}:{api_defaults["api_port_number"]}/audio/{output_file_name}.{model_engine.audio_format}'
- output_cache_url = f'http://{api_defaults["api_legacy_ip_address"]}:{api_defaults["api_port_number"]}/audiocache/{output_file_name}.{model_engine.audio_format}'
- else:
- output_file_url = f'/audio/{output_file_name}.{model_engine.audio_format}'
- output_cache_url = f'/audiocache/{output_file_name}.{model_engine.audio_format}'
- if text_filtering == "html":
- cleaned_string = html.unescape(standard_filtering(text_input))
- cleaned_string = re.sub(r'([!?.])\1+', r'\1', cleaned_string)
- # Further clean to remove any other unwanted characters
- cleaned_string = re.sub(rf'{api_defaults["api_allowed_filter"]}', '', cleaned_string)
- print(f"[{branding}Debug] HTML Filtering - text after api_allowed: {cleaned_string}") if debug_tts else None
- # Remove all newline characters (single or multiple)
- cleaned_string = re.sub(r'\n+', ' ', cleaned_string)
- elif text_filtering == "standard":
- cleaned_string = re.sub(r'([!?.])\1+', r'\1', text_input)
- # Further clean to remove any other unwanted characters
- cleaned_string = re.sub(rf'{api_defaults["api_allowed_filter"]}', '', cleaned_string)
- print(f"[{branding}Debug] Standard Filtering - text after api_allowed: {cleaned_string}") if debug_tts else None
- # Remove all newline characters (single or multiple)
- cleaned_string = re.sub(r'\n+', ' ', cleaned_string)
- else:
- print(f"[{branding}Debug] No filtering text : {text_input}") if debug_tts else None
- cleaned_string = text_input
- print(f"[{branding}GEN]", cleaned_string)
- print(f"[{branding}Debug] Sending request to generate_audio in tts_server.py") if debug_tts else None
- try:
- if streaming:
- response = await generate_audio(cleaned_string, character_voice_gen, language, temperature, repetition_penalty, speed, pitch, output_file_path, streaming)
- return StreamingResponse(response, media_type="audio/wav")
- else:
- response = await generate_audio(cleaned_string, character_voice_gen, language, temperature, repetition_penalty, speed, pitch, output_file_path, streaming)
- if rvc_settings["rvc_enabled"]:
- if rvccharacter_voice_gen.lower() in ["disabled", "disable"]:
- print(f"[{branding}Debug] Pass rvccharacter_voice_gen") if debug_tts else None
- pass # Skip RVC processing for character part
- else:
- print(f"[{branding}Debug] send to rvc") if debug_tts else None
- rvccharacter_voice_gen = this_dir / "models" / "rvc_voices" / rvccharacter_voice_gen
- pth_path = rvccharacter_voice_gen if rvccharacter_voice_gen else rvc_settings["rvc_char_model_file"]
- run_rvc(output_file_path, pth_path, rvccharacter_pitch, infer_pipeline)
- # Transcode audio if necessary
- model_audio_format = str(model_engine.audio_format).lower()
- output_audio_format = str(params["transcode_audio_format"]).lower()
- print(f"[{branding}Debug] At Model audio format") if debug_tts else None
- if output_audio_format == "disabled":
- pass
- else:
- if model_audio_format != output_audio_format and not model_engine.tts_narrator_generatingtts:
- print(f"[{branding}Debug] Pre Transcode call - Output file: {output_file_path}") if debug_tts else None
- output_file_path, output_file_url, output_cache_url = await transcode_audio_if_necessary(output_file_path, model_audio_format, output_audio_format)
- print(f"[{branding}Debug] Post Transcode call - Output file: {output_file_path}") if debug_tts else None
- if sounddevice_installed == False:
- autoplay = False
- if autoplay:
- play_audio(output_file_path, autoplay_volume)
- return JSONResponse(content={"status": "generate-success", "output_file_path": str(output_file_path), "output_file_url": str(output_file_url), "output_cache_url": str(output_cache_url)}, status_code=200)
- except Exception as e:
- return JSONResponse(content={"status": "generate-failure", "error": "An error occurred"}, status_code=500)
- except Exception as e:
- return JSONResponse(content={"status": "generate-failure", "error": "An error occurred"}, status_code=500)
-
-#############################
-#### Word Add-in Sharing ####
-#############################
-# Mount the static files from the 'word_addin' directory
-app.mount("/api/word_addin", StaticFiles(directory=os.path.join(this_dir / 'system' / 'word_addin')), name="word_addin")
-
-#############################################
-#### TTS Generator Comparision Endpoints ####
-#############################################
-import aiofiles
-class TTSItem(BaseModel):
- id: int
- fileUrl: str
- text: str
- characterVoice: str
- language: str
-
-class TTSData(BaseModel):
- ttsList: List[TTSItem]
-
-@app.post("/api/save-tts-data")
-async def apifunction_save_tts_data(tts_data: List[TTSItem]):
- # Convert the list of Pydantic models to a list of dictionaries
- tts_data_list = [item.dict() for item in tts_data]
- # Serialize the list of dictionaries to a JSON string
- tts_data_json = json.dumps(tts_data_list, indent=4)
- async with aiofiles.open(this_dir / output_directory / "ttsList.json", 'w') as f:
- await f.write(tts_data_json)
- return {"message": "Data saved successfully"}
-
-########################################
-# Trigger TTS Gen Text/Speech Analysis #
-########################################
-@app.get("/api/trigger-analysis")
-async def apifunction_trigger_analysis(threshold: int = Query(default=98)):
- venv_path = sys.prefix
- env = os.environ.copy()
- env["PATH"] = os.path.join(venv_path, "bin") + ":" + env["PATH"]
- ttslist_path = this_dir / output_directory / "ttsList.json"
- wavfile_path = this_dir / output_directory
- subprocess.run([sys.executable, "tts_diff.py", f"--threshold={threshold}", f"--ttslistpath={ttslist_path}", f"--wavfilespath={wavfile_path}"], cwd=this_dir / "system" / "tts_diff", env=env)
- # Read the analysis summary
- try:
- with open(this_dir / output_directory / "analysis_summary.json", "r") as summary_file:
- summary_data = json.load(summary_file)
- except FileNotFoundError:
- summary_data = {"error": "Analysis summary file not found."}
- return {"message": "Analysis Completed", "summary": summary_data}
-
-###########################################
-# TTS Generator SRT Subtitiles generation #
-###########################################
-@app.get("/api/srt-generation")
-async def apifunction_srt_generation():
- venv_path = sys.prefix
- env = os.environ.copy()
- env["PATH"] = os.path.join(venv_path, "bin") + ":" + env["PATH"]
- ttslist_path = this_dir / output_directory / "ttsList.json"
- wavfile_path = this_dir / output_directory
- subprocess.run([sys.executable, "tts_srt.py", f"--ttslistpath={ttslist_path}", f"--wavfilespath={wavfile_path}"], cwd=this_dir / "system" / "tts_srt", env=env)
- srt_file_path = this_dir / output_directory / "subtitles.srt"
- if not srt_file_path.exists():
- raise HTTPException(status_code=404, detail="Subtitle file not found.")
- return FileResponse(path=srt_file_path, filename="subtitles.srt", media_type='application/octet-stream')
-
-#################################
-# Static Mount for file serving #
-#################################
-app.mount("/static", StaticFiles(directory=str(this_dir / "system")), name="static")
-
-########################################
-# Legacy JSON update settings function #
-########################################
-# Setup logging
-logging.basicConfig(level=logging.DEBUG)
-def get_json_data():
- with open(this_dir / "confignew.json", "r") as json_file:
- data = json.load(json_file)
- return data
-
-@app.get("/settings")
-async def get_settings(request: Request):
- data = get_json_data()
- return templates.TemplateResponse("admin.html", {"request": request, "data": data})
-
-@app.get("/settings-json")
-async def get_settings_json():
- return get_json_data()
-
-@app.post("/update-settings")
-async def update_settings(
- request: Request,
- delete_output_wavs: str = Form(...),
- gradio_interface: str = Form(...),
- gradio_port_number: int = Form(...),
- api_port_number: int = Form(...),
-):
- data = get_json_data()
- # Update the settings based on the form values
- data["delete_output_wavs"] = delete_output_wavs
- data["gradio_interface"] = gradio_interface.lower() == 'true'
- data["gradio_port_number"] = gradio_port_number
- data["api_def"]["api_port_number"] = api_port_number
- # Save the updated settings back to the JSON file
- with open(this_dir / "confignew.json", "w") as json_file:
- json.dump(data, json_file, indent=4)
- # Redirect to the settings page to display the updated settings
- return templates.TemplateResponse("admin.html", {"request": request, "data": data})
-
-# Create an instance of Jinja2Templates for rendering HTML templates
-templates = Jinja2Templates(directory=this_dir / "system")
-# Get the admin interface template
-template = templates.get_template("admin.html")
-# Render the template with the dynamic values
-rendered_html = template.render(params=params)
-
-###################################################
-#### Webserver Startup & Initial model Loading ####
-###################################################
-@app.get("/")
-async def read_root():
- return HTMLResponse(content=rendered_html, status_code=200)
-
-# Start Uvicorn Webserver
-port_parameter = int(params["api_def"]["api_port_number"])
-
-if __name__ == "__main__":
- import uvicorn
-
- # Start Uvicorn Webserver
- uvicorn_server = uvicorn.run(app, host="0.0.0.0", port=port_parameter, log_level="debug")
-
+import os
+import sys
+import json
+import time
+import argparse
+import librosa
+import logging
+import importlib
+import subprocess
+from pathlib import Path
+logging.disable(logging.WARNING)
+#####################
+# Webserver Imports #
+#####################
+from contextlib import asynccontextmanager
+from fastapi.staticfiles import StaticFiles
+from fastapi.templating import Jinja2Templates
+from fastapi.middleware.cors import CORSMiddleware
+from fastapi import FastAPI, Form, Request, Response, Depends, HTTPException, Query
+from fastapi.responses import JSONResponse, HTMLResponse, RedirectResponse, FileResponse, StreamingResponse
+##########################
+# Generation API Imports #
+##########################
+import re
+import html
+import uuid
+import hashlib
+import numpy as np
+import soundfile as sf
+from typing import Union, Dict, List
+from pydantic import BaseModel, ValidationError, Field, field_validator
+########################################################################################
+# START-UP # Silence RVC warning about torch.nn.utils.weight_norm even though not used #
+########################################################################################
+import warnings
+warnings.filterwarnings("ignore", category=UserWarning, module="torch.nn.utils.weight_norm")
+warnings.filterwarnings("ignore", category=UserWarning, module="torch.nn.functional", lineno=5476)
+####################
+# Setup local path #
+####################
+this_dir = Path(__file__).parent.resolve() # Set this_dir as the current alltalk_tts folder
+
+####################################################
+# Load params and api_defailts from confignew.json #
+####################################################
+def load_config():
+ global branding, output_directory, debug_transcode, debug_tts, debug_tts_variables, engines_available, engine_loaded, debug_rvc, debug_concat, debug_openai
+ # Define the path to the confignew.json file & load in api_defaults & params
+ configfile_path = this_dir / "confignew.json"
+ with open(configfile_path, "r") as configfile:
+ configfile_data = json.load(configfile)
+ api_defaults = {
+ "api_text_filtering": configfile_data["api_def"]["api_text_filtering"],
+ "api_narrator_enabled": configfile_data["api_def"]["api_narrator_enabled"],
+ "api_text_not_inside": configfile_data["api_def"]["api_text_not_inside"],
+ "api_language": configfile_data["api_def"]["api_language"],
+ "api_output_file_name": configfile_data["api_def"]["api_output_file_name"],
+ "api_output_file_timestamp": configfile_data["api_def"]["api_output_file_timestamp"],
+ "api_autoplay": configfile_data["api_def"]["api_autoplay"],
+ "api_autoplay_volume": configfile_data["api_def"]["api_autoplay_volume"],
+ "api_port_number": configfile_data["api_def"]["api_port_number"],
+ "api_allowed_filter": configfile_data["api_def"]["api_allowed_filter"],
+ "api_length_stripping": configfile_data["api_def"]["api_length_stripping"],
+ "api_max_characters": configfile_data["api_def"]["api_max_characters"],
+ "api_use_legacy_api": configfile_data["api_def"]["api_use_legacy_api"],
+ "api_legacy_ip_address": configfile_data["api_def"]["api_legacy_ip_address"],
+ }
+ rvc_settings = {
+ "rvc_enabled": configfile_data["rvc_settings"]["rvc_enabled"],
+ "rvc_char_model_file": configfile_data["rvc_settings"]["rvc_char_model_file"],
+ "rvc_narr_model_file": configfile_data["rvc_settings"]["rvc_narr_model_file"],
+ "split_audio": configfile_data["rvc_settings"]["split_audio"],
+ "autotune": configfile_data["rvc_settings"]["autotune"],
+ "pitch": configfile_data["rvc_settings"]["pitch"],
+ "filter_radius": configfile_data["rvc_settings"]["filter_radius"],
+ "index_rate": configfile_data["rvc_settings"]["index_rate"],
+ "rms_mix_rate": configfile_data["rvc_settings"]["rms_mix_rate"],
+ "protect": configfile_data["rvc_settings"]["protect"],
+ "hop_length": configfile_data["rvc_settings"]["hop_length"],
+ "f0method": configfile_data["rvc_settings"]["f0method"],
+ "embedder_model": configfile_data["rvc_settings"]["embedder_model"],
+ "training_data_size": configfile_data["rvc_settings"]["training_data_size"],
+ }
+ branding = configfile_data.get("branding", "")
+ debug_transcode = configfile_data.get("debugging").get("debug_transcode")
+ debug_tts = configfile_data.get("debugging").get("debug_tts")
+ debug_tts_variables = configfile_data.get("debugging").get("debug_tts_variables")
+ debug_rvc = configfile_data.get("debugging").get("debug_rvc")
+ debug_concat = configfile_data.get("debugging").get("debug_concat")
+ debug_openai = configfile_data.get("debugging").get("debug_openai")
+ output_directory = this_dir / configfile_data.get("output_folder", "")
+ output_directory.mkdir(parents=True, exist_ok=True)
+ tts_engines_file = os.path.join(this_dir, "system", "tts_engines", "tts_engines.json")
+ with open(tts_engines_file, "r") as f:
+ tts_engines_data = json.load(f)
+ engines_available = [engine["name"] for engine in tts_engines_data["engines_available"]] # List of the available TTS engines from tts_engines.json
+ engine_loaded = tts_engines_data["engine_loaded"] # The currently set TTS engine from tts_engines.json
+ selected_model = tts_engines_data["selected_model"]
+ # Conditionally import infer_pipeline if rvc_enabled is true
+ if rvc_settings["rvc_enabled"]:
+ from system.tts_engines.rvc.infer.infer import infer_pipeline
+ else:
+ infer_pipeline = None
+ return configfile_data, api_defaults, rvc_settings, infer_pipeline
+
+# Call to load in the params and api_defaults
+params, api_defaults, rvc_settings, infer_pipeline = load_config()
+
+####################
+# Check for FFMPEG #
+####################
+def check_ffmpeg(this_dir):
+ if sys.platform == "win32":
+ # Check for ffmpeg.exe in the specified directory on Windows
+ ffmpeg_path = os.path.join(this_dir, "system", "win_ffmpeg", "ffmpeg.exe")
+ if os.path.exists(ffmpeg_path):
+ return True
+ else:
+ # Check for FFmpeg on Linux and macOS using the command line
+ try:
+ subprocess.run(['ffmpeg', '-version'], stdout=subprocess.PIPE, stderr=subprocess.PIPE, check=True)
+ return True
+ except (subprocess.CalledProcessError, FileNotFoundError):
+ pass
+ return False
+
+# Check if FFmpeg is installed
+ffmpeg_installed = check_ffmpeg(this_dir)
+
+if not ffmpeg_installed:
+ print(f"[{branding}ENG] \033[92mTranscoding :\033[94m ffmpeg not found\033[0m")
+ print(f"[{branding}ENG] FFmpeg is not installed. Transcoding will be disabled.")
+ print(f"[{branding}ENG] Please install FFmpeg on your system.")
+
+ if sys.platform == "win32":
+ print(f"[{branding}ENG] \033[92mTranscoding :\033[94m ffmpeg not found\033[0m")
+ print(f"[{branding}ENG] Installation instructions for Windows:")
+ print(f"[{branding}ENG] Copy the 'ffmpeg.exe' file to '{os.path.join(this_dir, 'system', 'win_ffmpeg')}'")
+ else:
+ print(f"[{branding}ENG] \033[92mTranscoding :\033[94m ffmpeg not found\033[0m")
+ print(f"[{branding}ENG] Installation instructions:")
+ print(f"[{branding}ENG] Linux (Debian-based systems): Run 'sudo apt-get install ffmpeg' in the terminal.")
+ print(f"[{branding}ENG] macOS: Run 'brew install ffmpeg' in the terminal (requires Homebrew).")
+ # You can choose to exit the script or continue without FFmpeg
+ # exit(1)
+
+if ffmpeg_installed:
+ from ffmpeg.asyncio import FFmpeg
+ print(f"[{branding}ENG] \033[92mTranscoding :\033[93m ffmpeg found\033[0m")
+
+################################
+# Check for portaudio on Linux #
+################################
+try:
+ import sounddevice as sd
+ sounddevice_installed=True
+except OSError:
+ print(f"[{branding}Startup] \033[91mInfo\033[0m PortAudio library not found. If you wish to play TTS in standalone mode through the API suite")
+ print(f"[{branding}Startup] \033[91mInfo\033[0m please install PortAudio. This will not affect any other features or use of Alltalk.")
+ print(f"[{branding}Startup] \033[91mInfo\033[0m If you don't know what the API suite is, then this message is nothing to worry about.")
+ sounddevice_installed=False
+ if sys.platform.startswith('linux'):
+ print(f"[{branding}Startup] \033[91mInfo\033[0m On Linux, you can use the following command to install PortAudio:")
+ print(f"[{branding}Startup] \033[91mInfo\033[0m sudo apt-get install portaudio19-dev")
+
+#######################################################################
+# Attempt to import the ModelLoader class for the selected TTS engine #
+#######################################################################
+if engine_loaded in engines_available:
+ loader_module = importlib.import_module(f"system.tts_engines.{engine_loaded}.model_engine")
+ tts_class = getattr(loader_module, "tts_class")
+ # Setup model_engine as the way to call the functions within the Class.
+ model_engine = tts_class()
+else:
+ raise ValueError(f"Invalid TTS engine: {engine_loaded}")
+
+##########################################
+# Run setup function in the model_engine #
+##########################################
+@asynccontextmanager
+async def startup_shutdown(no_actual_value_it_demanded_something_be_here):
+ try:
+ await model_engine.setup()
+ except FileNotFoundError as e:
+ print(f"Error during setup: {e}. Continuing without the TTS model.")
+ yield
+ # Shutdown logic
+
+###############################
+# Setup FastAPI with Lifespan #
+###############################
+app = FastAPI(lifespan=startup_shutdown)
+# Allow all origins, and set other CORS options
+app.add_middleware(
+ CORSMiddleware,
+ allow_origins=["*"], # Set this to the specific origins you want to allow
+ allow_credentials=True,
+ allow_methods=["*"],
+ allow_headers=["*"],
+)
+
+##############################
+# API Endpoint - /api/reload #
+##############################
+model_currently_changing = False
+@app.route("/api/reload", methods=["POST"])
+async def apifunction_reload(request: Request):
+ global model_currently_changing
+ if model_currently_changing == True:
+ return Response(content=json.dumps({"status": "model-is currently changing"}), media_type="application/json")
+ model_currently_changing == True
+ requested_model = request.query_params.get("tts_method")
+ if requested_model not in model_engine.available_models:
+ model_currently_changing == False
+ return {"status": "error", "message": "Invalid TTS method specified"}
+ # Call the handle_tts_method_change method from the imported class
+ success = await model_engine.handle_tts_method_change(requested_model)
+ if success:
+ model_engine.current_model_loaded = requested_model
+ model_currently_changing == False
+ return Response(content=json.dumps({"status": "model-success"}), media_type="application/json")
+ else:
+ model_currently_changing == False
+ return Response(content=json.dumps({"status": "model-failure"}), media_type="application/json")
+
+####################################
+# API Endpoint - /api/enginereload #
+####################################
+import asyncio
+uvicorn_server = None
+def restart_self():
+ """Restart the current script."""
+ #print(f"[{branding}ENG] Restarting subprocess...")
+ os.execv(sys.executable, ['python'] + sys.argv)
+
+async def handle_restart():
+ global uvicorn_server
+ if uvicorn_server:
+ print(f"[{branding}ENG] Stopping uvicorn server...")
+ uvicorn_server.should_exit = True
+ uvicorn_server.force_exit = True
+ # Check if the server has stopped
+ while not uvicorn_server.is_stopped:
+ await asyncio.sleep(0.1) # Check every 100ms if server is stopped
+ restart_self()
+
+@app.post("/api/enginereload")
+async def apifunction_reload(request: Request):
+ requested_engine = request.query_params.get("engine")
+ if requested_engine not in engines_available:
+ return {"status": "error", "message": "Invalid TTS engine specified"}
+ print(f"[{branding}ENG]")
+ print(f"[{branding}ENG] \033[94mChanging model loaded. Please wait.\033[00m")
+ print(f"[{branding}ENG]")
+ # Update the tts_engines.json file
+ tts_engines_file = os.path.join(this_dir, "system", "tts_engines", "tts_engines.json")
+ with open(tts_engines_file, "r") as f:
+ tts_engines_data = json.load(f)
+ for engine in tts_engines_data["engines_available"]:
+ if engine["name"] == requested_engine:
+ tts_engines_data["engine_loaded"] = requested_engine
+ tts_engines_data["selected_model"] = engine["selected_model"]
+ break
+ with open(tts_engines_file, "w") as f:
+ json.dump(tts_engines_data, f)
+ #print(f"[{branding}ENG] Reloading the engine...")
+ # Start the restart process in the background
+ asyncio.create_task(handle_restart())
+ # Never goes past the above asyncio as the script is killed at that point
+ return Response(content=json.dumps({"status": "engine-success"}), media_type="application/json")
+
+#######################################
+# API Endpoint - /api/stop-generation #
+#######################################
+# When this endpoint it called it will set tts_stop_generation in the model_engine to True, which can be used to interrupt the current TTS generation.
+@app.put("/api/stop-generation")
+async def apifunction_stop_generation():
+ if model_engine.tts_generating_lock and not model_engine.tts_stop_generation:
+ model_engine.tts_stop_generation = True
+ return {"message": "Cancelling current TTS generation"}
+
+#############################
+# API Endpoint - /api/audio #
+#############################
+# Gives web access to the output files as downloads
+@app.get("/audio/{filename}")
+async def apifunction_get_audio(filename: str):
+ audio_path = this_dir / output_directory / filename
+ return FileResponse(audio_path)
+
+##################################
+# API Endpoint - /api/audiocache #
+##################################
+@app.get("/audiocache/{filename}")
+async def apifunction_get_audio(filename: str):
+ audio_path = Path({output_directory}) / filename
+ if not audio_path.is_file():
+ raise HTTPException(status_code=404, detail="File not found")
+ response = FileResponse(path=audio_path, media_type='audio/wav', filename=filename)
+ # Set caching headers
+ response.headers["Cache-Control"] = "public, max-age=604800" # Cache for one week
+ response.headers["ETag"] = str(audio_path.stat().st_mtime) # Use the file's last modified time as a simple ETag
+ return response
+
+##############################
+# API Endpoint - /api/voices #
+##############################
+@app.get("/api/voices")
+async def apifunction_get_voices():
+ if not model_engine.multivoice_capable:
+ return {"status": "error", "message": f"The currently loaded TTS engine '{model_engine.engine_loaded}' does not support multiple voices."}
+
+ available_voices = model_engine.voices_file_list()
+ return {"status": "success", "voices": available_voices}
+
+#################################
+# API Endpoint - /api/rvcvoices #
+#################################
+@app.get("/api/rvcvoices")
+async def apifunction_get_voices():
+ global params, api_defaults, rvc_settings, infer_pipeline
+ params, api_defaults, rvc_settings, infer_pipeline = load_config()
+ if not rvc_settings["rvc_enabled"]:
+ return {"status": "success", "rvcvoices": ["Disabled"]}
+ # Define the directory path
+ directory = os.path.join(this_dir, "models", "rvc_voices")
+ # Check if the directory exists
+ if not os.path.exists(directory):
+ return {"status": "success", "rvcvoices": ["Disabled"]}
+ # List all .pth files recursively in the directory
+ pth_files = []
+ for root, dirs, files in os.walk(directory):
+ for file in files:
+ if file.endswith(".pth"):
+ pth_files.append(os.path.relpath(os.path.join(root, file), start=directory))
+ # If no .pth files found, return "Disabled"
+ if not pth_files:
+ return {"status": "success", "rvcvoices": ["Disabled"]}
+ # Sort the list alphabetically
+ pth_files.sort()
+ # Add "Disabled" at the beginning of the list
+ pth_files.insert(0, "Disabled")
+ return {"status": "success", "rvcvoices": pth_files}
+
+#####################################
+# API Endpoint - /api/reload_config #
+#####################################
+@app.get("/api/reload_config")
+async def apifunction_reload_config():
+ global params, api_defaults, rvc_settings, infer_pipeline
+ model_engine.available_models = model_engine.scan_models_folder()
+ params, api_defaults, rvc_settings, infer_pipeline = load_config()
+ return Response("Config file reloaded successfully")
+
+#############################
+# API Endpoint - /api/ready #
+#############################
+@app.get("/api/ready")
+async def apifunction_ready():
+ if model_engine.setup_has_run:
+ return Response("Ready")
+ else:
+ return Response("Unloaded")
+
+#######################################
+# API Endpoint - /api/currentsettings #
+#######################################
+@app.get('/api/currentsettings')
+def apifunction_get_current_settings():
+ settings = {
+ "engines_available": engines_available, # Lists the currently available TTS engines
+ "current_engine_loaded": model_engine.engine_loaded, # Lists the currently loaded TTS engine
+ "models_available": [{"name": name} for name in model_engine.available_models.keys()], # Lists the currently available models that can be loaded on this TTS engine.
+ "current_model_loaded": model_engine.current_model_loaded, # Lists the currently loaded model
+ "manufacturer_name": model_engine.manufacturer_name, # The name of the person/company/group who made the current TTS engine
+ "audio_format": model_engine.audio_format, # The default audio format the the currently loaded TTS engine generates in
+ "deepspeed_capable": model_engine.deepspeed_capable, # Is the TTS engine capable of supporting DeepSpeed
+ "deepspeed_available": model_engine.deepspeed_available, # Was DeepSpeed detected as being available for use
+ "deepspeed_enabled": model_engine.deepspeed_enabled, # Is DeepSpeed current enabled or disabled
+ "generationspeed_capable": model_engine.generationspeed_capable, # Does the TTS engine support changing speed of generated TTS
+ "generationspeed_set": model_engine.generationspeed_set, # What is the currently set TTS speed
+ "lowvram_capable": model_engine.lowvram_capable, # Does the TTS engine support moving the model between VRAM and System Ram
+ "lowvram_enabled": model_engine.lowvram_enabled, # What is the currently enabled/disabled state of low vram
+ "pitch_capable": model_engine.pitch_capable, # Does the TTS engine support pitch on tts generation
+ "pitch_set": model_engine.pitch_set, # What is the currently set value for pitch
+ "repetitionpenalty_capable": model_engine.repetitionpenalty_capable, # Does the TTS engine support differnt repitition penalties
+ "repetitionpenalty_set": model_engine.repetitionpenalty_set, # What is the currently set repetition penalty
+ "streaming_capable": model_engine.streaming_capable, # Does the model support streaming TTS generation
+ "temperature_capable": model_engine.temperature_capable, # Does the TTS engine support model temperature settings
+ "temperature_set": model_engine.temperature_set, # What is the currently set temperature
+ "ttsengines_installed": model_engine.engine_installed, #
+ "languages_capable": model_engine.languages_capable, # Does the TTS engine support multi languages
+ "multivoice_capable": model_engine.multivoice_capable, # Does the TTS engine support multi voice models
+ "multimodel_capable": model_engine.multimodel_capable, # Does the engine support loading many different models
+ }
+ return settings
+
+######################################
+# API Endpoint - /api/lowvramsetting #
+######################################
+@app.post("/api/lowvramsetting")
+async def apifunction_low_vram(request: Request, new_low_vram_value: bool):
+ if not model_engine.lowvram_capable:
+ return {"status": "error", "message": f"The currently loaded TTS engine '{model_engine.engine_loaded}' does not support lowvram."}
+ try:
+ if new_low_vram_value is None:
+ raise ValueError("Missing 'low_vram' parameter")
+ if model_engine.lowvram_enabled == new_low_vram_value:
+ return Response(content=json.dumps({"status": "success", "message": f"[{branding}Model] LowVRAM is already {'enabled' if new_low_vram_value else 'disabled'}.",}))
+ model_engine.lowvram_enabled = new_low_vram_value
+ await model_engine.unload_model()
+
+ if model_engine.cuda_is_available:
+ if model_engine.lowvram_enabled:
+ model_engine.device = "cpu"
+ print(f"[{branding}Engine] \033[94mLowVRAM Enabled.\033[0m Model will move between \033[93mVRAM(cuda) <> System RAM(cpu)\033[0m")
+ else:
+ model_engine.device = "cuda"
+ print(f"[{branding}Engine] \033[94mLowVRAM Disabled.\033[0m Model will stay in \033[93mVRAM(cuda)\033[0m")
+ await model_engine.setup()
+ else:
+ # Handle the case where CUDA is not available
+ print(f"[{branding}Engine] \033[91mError:\033[0m Nvidia CUDA is not available on this system. Unable to use LowVRAM mode.")
+ model_engine.lowvram_enabled = False
+
+ return Response(content=json.dumps({"status": "lowvram-success"}))
+ except Exception as e:
+ return Response(content=json.dumps({"status": "error", "message": str(e)}))
+
+#################################
+# API Endpoint - /api/deepspeed #
+#################################
+@app.post("/api/deepspeed")
+async def deepspeed(request: Request, new_deepspeed_value: bool):
+ if not model_engine.deepspeed_capable or not model_engine.deepspeed_available:
+ return {"status": "error", "message": f"The currently loaded TTS engine '{model_engine.engine_loaded}' does not support DeepSpeed or DeepSpeed is not available on this system."}
+ try:
+ if new_deepspeed_value is None:
+ raise ValueError("Missing 'deepspeed' parameter")
+ if model_engine.deepspeed_enabled == new_deepspeed_value:
+ return Response(
+ content=json.dumps({"status": "success", "message": f"DeepSpeed is already {'enabled' if new_deepspeed_value else 'disabled'}.",}))
+ model_engine.deepspeed_enabled = new_deepspeed_value
+ await model_engine.handle_deepspeed_change(new_deepspeed_value)
+ return Response(content=json.dumps({"status": "deepspeed-success"}))
+ except Exception as e:
+ return Response(content=json.dumps({"status": "error", "message": str(e)}))
+
+#################################
+# API Endpoint - /api/voice2rvc #
+#################################
+@app.post("/api/voice2rvc")
+async def voice2rvc(input_tts_path: str = Form(...), output_rvc_path: str = Form(...), pth_name: str = Form(...), pitch: str = Form(...), method: str = Form(...)):
+ try:
+ # Handle "Disabled" case
+ if pth_name.lower() in ["disabled", "disable"]:
+ print(f"[{branding}TTS] \033[94mVoice2RVC Convert: No voice was specified or the name was Disabled\033[0m")
+ return {"status": "error", "message": "No voice was specified or the name was Disabled"}
+ input_tts_path = Path(input_tts_path)
+ output_rvc_path = Path(output_rvc_path)
+ # Check if input file exists
+ if not input_tts_path.is_file():
+ raise HTTPException(status_code=400, detail=f"Input file {input_tts_path} does not exist.")
+ # Define the model path based on the selected model name
+ pth_path = this_dir / "models" / "rvc_voices" / pth_name
+ # Check if model file exists
+ if not pth_path.is_file():
+ raise HTTPException(status_code=400, detail=f"Model file {pth_path} does not exist.")
+ # Run the RVC conversion
+ result_path = run_voice2rvc(input_tts_path, output_rvc_path, pth_path, pitch, method)
+ if result_path:
+ return {"status": "success", "output_path": str(result_path)}
+ else:
+ raise HTTPException(status_code=500, detail="RVC conversion failed.")
+ except Exception as e:
+ print(f"Error during Voice2RVC conversion: {e}")
+ raise HTTPException(status_code=500, detail=str(e))
+
+# Define the run_rvc function
+def run_voice2rvc(input_tts_path, output_rvc_path, pth_path, pitch, method):
+ print(f"[{branding}GEN] \033[94mVoice2RVC Convert: Started\033[0m")
+ generate_start_time = time.time()
+ f0up_key = pitch
+ filter_radius = rvc_settings["filter_radius"]
+ index_rate = rvc_settings["index_rate"]
+ rms_mix_rate = rvc_settings["rms_mix_rate"]
+ protect = rvc_settings["protect"]
+ hop_length = rvc_settings["hop_length"]
+ f0method = method
+ split_audio = rvc_settings["split_audio"]
+ f0autotune = rvc_settings["autotune"]
+ embedder_model = rvc_settings["embedder_model"]
+ training_data_size = rvc_settings["training_data_size"]
+ # Convert path variables to strings
+ input_tts_path = str(input_tts_path)
+ pth_path = str(pth_path)
+ # Check if the model file exists
+ if not os.path.isfile(pth_path):
+ print(f"Model file {pth_path} does not exist. Exiting.")
+ return
+ # Get the directory of the model file
+ model_dir = os.path.dirname(pth_path)
+ # Find all .index files in the model directory
+ index_files = [file for file in os.listdir(model_dir) if file.endswith(".index")]
+ if len(index_files) == 1:
+ index_path = str(os.path.join(model_dir, index_files[0]))
+ else:
+ index_path = ""
+ # Call the infer_pipeline function
+ infer_pipeline(f0up_key, filter_radius, index_rate, rms_mix_rate, protect, hop_length, f0method,
+ input_tts_path, output_rvc_path, pth_path, index_path, split_audio, f0autotune, embedder_model, training_data_size, debug_rvc)
+ generate_end_time = time.time()
+ generate_elapsed_time = generate_end_time - generate_start_time
+ print(f"[{branding}GEN] \033[94mVoice2RVC Convert: \033[91m{generate_elapsed_time:.2f} seconds.\033[0m")
+ return output_rvc_path
+
+##################################
+# Transcode between file formats #
+##################################
+async def transcode_audio(input_file, output_format):
+ print(f"[{branding}Debug] *************************************************",) if debug_transcode else None
+ print(f"[{branding}Debug] transcode_audio function called (debug_transcode)",) if debug_transcode else None
+ print(f"[{branding}Debug] *************************************************",) if debug_transcode else None
+ print(f"[{branding}Debug] Input file : {input_file}") if debug_transcode else None
+ print(f"[{branding}Debug] Output format : {output_format}") if debug_transcode else None
+ if output_format == "Disabled":
+ print(f"[{branding}Debug] Transcode format is set to Disabled so skipping transcode.") if debug_transcode else None
+ return input_file
+ if not ffmpeg_installed:
+ print(f"[{branding}TTS] FFmpeg is not installed. Format conversion is not possible.")
+ raise Exception("FFmpeg is not installed. Format conversion is not possible.")
+ # Get the file extension of the input file
+ input_extension = os.path.splitext(input_file)[1][1:].lower()
+ print(f"[{branding}Debug] Input file extension: {input_extension}") if debug_transcode else None
+ # Check if the input extension matches the requested output format
+ if input_extension == output_format.lower():
+ print(f"[{branding}Debug] Input file is already in the requested format: {output_format}") if debug_transcode else None
+ return input_file
+ output_file = os.path.splitext(input_file)[0] + f".{output_format}"
+ print(f"[{branding}Debug] Output file: {output_file}") if debug_transcode else None
+ ffmpeg_path = "ffmpeg" # Default path for Linux and macOS
+ if sys.platform == "win32":
+ ffmpeg_path = os.path.join(this_dir, "system", "win_ffmpeg", "ffmpeg.exe")
+ ffmpeg = (FFmpeg(ffmpeg_path).option("y").input(input_file).output(output_file))
+ print(f"[{branding}Debug] Transcoding to {output_format}") if debug_transcode else None
+ if output_format == "opus":
+ print(f"[{branding}Debug] Configuring Opus options") if debug_transcode else None
+ ffmpeg.output(output_file, {"codec:a": "libopus", "b:a": "128k", "vbr": "on", "compression_level": 10, "frame_duration": 60, "application": "voip"})
+ elif output_format == "aac":
+ print(f"[{branding}Debug] Configuring AAC options") if debug_transcode else None
+ ffmpeg.output(output_file, {"codec:a": "aac", "b:a": "192k"})
+ elif output_format == "flac":
+ print(f"[{branding}Debug] Configuring FLAC options") if debug_transcode else None
+ ffmpeg.output(output_file, {"codec:a": "flac", "compression_level": 8})
+ elif output_format == "wav":
+ print(f"[{branding}Debug] Configuring WAV options") if debug_transcode else None
+ ffmpeg.output(output_file, {"codec:a": "pcm_s16le"})
+ elif output_format == "mp3":
+ print(f"[{branding}Debug] Configuring MP3 options") if debug_transcode else None
+ ffmpeg.output(output_file, {"codec:a": "libmp3lame", "b:a": "192k"})
+ else:
+ print(f"[{branding}TTS] Unsupported output format: {output_format}")
+ raise ValueError(f"Unsupported output format: {output_format}")
+ try:
+ print(f"[{branding}Debug] Starting transcoding process") if debug_transcode else None
+ await ffmpeg.execute()
+ print(f"[{branding}Debug] Transcoding completed successfully") if debug_transcode else None
+ except Exception as e:
+ print(f"[{branding}TTS] Error occurred during transcoding: {str(e)}")
+ raise
+ print(f"[{branding}Debug] Deleting original input file") if debug_transcode else None
+ os.remove(input_file)
+ print(f"[{branding}Debug] Transcoding process completed") if debug_transcode else None
+ print(f"[{branding}Debug] Transcoded file: {output_file}") if debug_transcode else None
+ return output_file
+
+##############################
+# Central Transcode function #
+##############################
+async def transcode_audio_if_necessary(output_file, model_audio_format, output_audio_format):
+ if debug_transcode:
+ print(f"[{branding}Debug] model_engine.audio_format is:", model_audio_format)
+ print(f"[{branding}Debug] audio format is:", output_audio_format)
+ print(f"[{branding}Debug] Entering the transcode condition")
+ try:
+ if debug_transcode:
+ print(f"[{branding}Debug] Calling transcode_audio function")
+ output_file = await transcode_audio(output_file, output_audio_format)
+ if debug_transcode:
+ print(f"[{branding}Debug] Transcode completed successfully")
+ except Exception as e:
+ print(f"[{branding}Debug] Error occurred during transcoding:", str(e))
+ raise
+ if debug_transcode:
+ print(f"[{branding}Debug] Transcode condition completed")
+ # Update the output file path and URLs based on the transcoded file
+ if debug_tts:
+ print(f"[{branding}Debug] Updating output file paths and URLs")
+ if params["api_def"]["api_use_legacy_api"]:
+ output_file_url = f'http://{api_defaults["api_legacy_ip_address"]}:{api_defaults["api_port_number"]}/audio/{os.path.basename(output_file)}'
+ output_cache_url = f'http://{api_defaults["api_legacy_ip_address"]}:{api_defaults["api_port_number"]}/audiocache/{os.path.basename(output_file)}'
+ else:
+ output_file_url = f'/audio/{os.path.basename(output_file)}'
+ output_cache_url = f'/audiocache/{os.path.basename(output_file)}'
+ if debug_tts:
+ print(f"[{branding}Debug] Output file paths and URLs updated")
+ return output_file, output_file_url, output_cache_url
+
+##############################
+#### Streaming Generation ####
+##############################
+@app.get("/api/tts-generate-streaming", response_class=StreamingResponse)
+async def apifunction_generate_streaming(text: str, voice: str, language: str, output_file: str):
+ if model_engine.streaming_capable == False:
+ print(f"[{branding}GEN] The selected TTS Engine does not support streaming. To use streaming, please select a TTS")
+ print(f"[{branding}GEN] Engine that has streaming capability. You can find the streaming support information for")
+ print(f"[{branding}GEN] each TTS Engine in the 'Engine Information' section of the Gradio interface.")
+ try:
+ output_file_path = f'{this_dir / output_directory / output_file}.{model_engine.audio_format}'
+ stream = await generate_audio(text, voice, language, model_engine.temperature_set, model_engine.repetitionpenalty_set, 1.0, 1.0, output_file_path, streaming=True)
+ return StreamingResponse(stream, media_type="audio/wav")
+ except Exception as e:
+ print(f"An error occurred: {e}")
+ return JSONResponse(content={"error": "An error occurred"}, status_code=500)
+
+@app.post("/api/tts-generate-streaming", response_class=JSONResponse)
+async def tts_generate_streaming(request: Request, text: str = Form(...), voice: str = Form(...), language: str = Form(...), output_file: str = Form(...)):
+ if model_engine.streaming_capable == False:
+ print(f"[{branding}GEN] The selected TTS Engine does not support streaming. To use streaming, please select a TTS")
+ print(f"[{branding}GEN] Engine that has streaming capability. You can find the streaming support information for")
+ print(f"[{branding}GEN] each TTS Engine in the 'Engine Information' section of the Gradio interface.")
+ try:
+ output_file_path = f'{this_dir / output_directory / output_file}.{model_engine.audio_format}'
+ await generate_audio(text, voice, language, model_engine.temperature_set, model_engine.repetitionpenalty_set, "1.0", "1.0", output_file_path, streaming=False)
+ return JSONResponse(content={"output_file_path": str(output_file)}, status_code=200)
+ except Exception as e:
+ print(f"An error occurred: {e}")
+ return JSONResponse(content={"error": "An error occurred"}, status_code=500)
+
+###################################
+# Central generate_audio function #
+###################################
+async def generate_audio(text, voice, language, temperature, repetition_penalty, speed, pitch, output_file, streaming=False):
+ if model_engine.streaming_capable == False and streaming==True:
+ print(f"[{branding}GEN] The selected TTS Engine does not support streaming. To use streaming, please select a TTS")
+ print(f"[{branding}GEN] Engine that has streaming capability. You can find the streaming support information for")
+ print(f"[{branding}GEN] each TTS Engine in the 'Engine Information' section of the Gradio interface.")
+ # Get the async generator from the internal function
+ response = model_engine.generate_tts(text, voice, language, temperature, repetition_penalty, speed, pitch, output_file, streaming)
+ # If streaming, then return the generator as-is, otherwise just exhaust it and return
+ if streaming:
+ # Return an async generator that yields from the response
+ async def stream_response():
+ async for chunk in response:
+ yield chunk
+ return stream_response()
+ else:
+ # Exhaust the generator and handle any errors
+ try:
+ async for _ in response:
+ pass
+ except Exception as e:
+ print(f"[GEN] Error during audio generation: {str(e)}")
+ raise
+
+###########################
+#### PREVIEW VOICE API ####
+###########################
+@app.post("/api/previewvoice/", response_class=JSONResponse)
+async def apifunction_preview_voice(request: Request, voice: str = Form(...)):
+ try:
+ # Hardcoded settings
+ language = "en"
+ output_file_name = "api_preview_voice"
+ # Clean the voice filename for inclusion in the text
+ clean_voice_filename = re.sub(r'\.wav$', '', voice.replace(' ', '_'))
+ clean_voice_filename = re.sub(r'[^a-zA-Z0-9]', ' ', clean_voice_filename)
+ # Generate the audio
+ text = f"Hello, this is a preview of voice {clean_voice_filename}."
+ # Generate the audio
+ output_file_path = this_dir / output_directory / f'{output_file_name}.{model_engine.audio_format}'
+ await generate_audio(text, voice, language, model_engine.temperature_set, model_engine.repetitionpenalty_set, 1.0, 0, output_file_path, streaming=False)
+ # Generate the URL
+ output_file_url = f'/audio/{output_file_name}.{model_engine.audio_format}'
+ # Return the response with both local file path and URL
+ return JSONResponse(
+ content={
+ "status": "generate-success",
+ "output_file_path": str(output_file_path),
+ "output_file_url": str(output_file_url),
+ },
+ status_code=200,
+ )
+ except Exception as e:
+ print(f"An error occurred: {e}")
+ return JSONResponse(content={"error": "An error occurred"}, status_code=500)
+
+##################################################################
+# API Endpoint - OpenAI Speech API compatable endpoint Validator #
+##################################################################
+class OpenAIInput(BaseModel):
+ model: str = Field(..., description="The TTS model to use. Currently ignored.")
+ input: str = Field(..., max_length=4096, description="The text to generate audio for.")
+ voice: str = Field(..., description="The voice to use when generating the audio.")
+ response_format: str = Field(default="wav", description="The format of the audio. Currently only 'wav' is supported.")
+ speed: float = Field(default=1.0, ge=0.25, le=4.0, description="The speed of the generated audio.")
+
+ @field_validator('voice')
+ def validate_voice(cls, value):
+ supported_voices = ["alloy", "echo", "fable", "nova", "onyx", "shimmer"]
+ if value not in supported_voices:
+ raise ValueError(f'Voice must be one of {supported_voices}')
+ return value
+
+class OpenAIGenerator:
+ @staticmethod
+ def validate_input(json_data: dict) -> Union[None, str]:
+ try:
+ OpenAIInput(**json_data)
+ return None
+ except ValidationError as e:
+ errors = []
+ for err in e.errors():
+ field = err['loc'][0]
+ message = err['msg']
+ description = OpenAIInput.model_fields[field].field_info.description
+ errors.append(f"Error in field '{field}': {message}. Description: {description}")
+ return ', '.join(errors)
+
+#########################################################################
+# API Endpoint - OpenAI Speech API compatable endpoint /v1/audio/speech #
+#########################################################################
+@app.post("/v1/audio/speech", response_class=JSONResponse)
+async def openai_tts_generate(request: Request):
+ try:
+ json_data = await request.json()
+ print(f"[{branding}Debug] Received JSON data: {json_data}") if debug_openai else None
+ validation_error = OpenAIGenerator.validate_input(json_data)
+ if validation_error:
+ print(f"Validation error: {validation_error}") if debug_openai else None
+ return JSONResponse(content={"error": validation_error}, status_code=400)
+ model = json_data["model"] # Currently ignored
+ input_text = json_data["input"]
+ voice = json_data["voice"]
+ response_format = json_data.get("response_format", "wav").lower()
+ speed = json_data.get("speed", 1.0)
+ print(f"[{branding}Debug] Input text: {input_text}") if debug_openai else None
+ print(f"[{branding}Debug] Voice: {voice}") if debug_openai else None
+ print(f"[{branding}Debug] Speed: {speed}") if debug_openai else None
+ cleaned_string = html.unescape(standard_filtering(input_text))
+ # Map the OpenAI voice to the corresponding internal voice
+ voice_mapping = {
+ "alloy": model_engine.openai_alloy,
+ "echo": model_engine.openai_echo,
+ "fable": model_engine.openai_fable,
+ "nova": model_engine.openai_nova,
+ "onyx": model_engine.openai_onyx,
+ "shimmer": model_engine.openai_shimmer
+ }
+ mapped_voice = voice_mapping.get(voice)
+ if not mapped_voice:
+ print(f"Unsupported voice: {voice}")
+ return JSONResponse(content={"error": "Unsupported voice"}, status_code=400)
+ print(f"[{branding}Debug]Mapped voice: {mapped_voice}") if debug_openai else None
+ # Generate the audio
+ # Generate a unique filename
+ unique_id = uuid.uuid4()
+ timestamp = int(time.time())
+ output_file_path = f'{this_dir / output_directory / f"openai_output_{unique_id}_{timestamp}.{model_engine.audio_format}"}'
+ await generate_audio(cleaned_string, mapped_voice, "en", model_engine.temperature_set,
+ model_engine.repetitionpenalty_set, speed, model_engine.pitch_set,
+ output_file_path, streaming=False)
+ print(f"[{branding}Debug] Audio generated at: {output_file_path}") if debug_openai else None
+ if rvc_settings["rvc_enabled"]:
+ if rvc_settings["rvc_char_model_file"].lower() in ["disabled", "disable"]:
+ print(f"[{branding}Debug] Pass rvccharacter_voice_gen") if debug_openai else None
+ pass # Skip RVC processing for character part
+ else:
+ print(f"[{branding}Debug] send to rvc") if debug_openai else None
+ pth_path = this_dir / "models" / "rvc_voices" / rvc_settings["rvc_char_model_file"]
+ run_rvc(output_file_path, pth_path, infer_pipeline)
+ # Transcode the audio to the requested format
+ transcoded_file_path = await transcode_for_openai(output_file_path, response_format)
+ print(f"[{branding}Debug] Audio transcoded to: {transcoded_file_path}") if debug_openai else None
+ # Return the audio file
+ return FileResponse(transcoded_file_path, media_type=f"audio/{response_format}", filename=f"output.{response_format}")
+ except Exception as e:
+ print(f"[{branding}Debug] An error occurred: {str(e)}") if debug_openai else None
+ return JSONResponse(content={"error": "An error occurred"}, status_code=500)
+
+###########################################################################
+# API Endpoint - OpenAI Speech API compatable endpoint Transcode Function #
+###########################################################################
+async def transcode_for_openai(input_file, output_format):
+ print(f"[{branding}Debug] ************************************") if debug_openai else None
+ print(f"[{branding}Debug] transcode_for_openai function called") if debug_openai else None
+ print(f"[{branding}Debug] ************************************") if debug_openai else None
+ print(f"[{branding}Debug] Input file : {input_file}") if debug_openai else None
+ print(f"[{branding}Debug] Output format : {output_format}") if debug_openai else None
+ if not ffmpeg_installed:
+ print(f"[{branding}TTS] FFmpeg is not installed. Format conversion is not possible.")
+ raise Exception("FFmpeg is not installed. Format conversion is not possible.")
+ # Get the file extension of the input file
+ input_extension = os.path.splitext(input_file)[1][1:].lower()
+ print(f"[{branding}Debug] Input file extension: {input_extension}") if debug_openai else None
+ # Check if the input extension matches the requested output format
+ if input_extension == output_format.lower():
+ print(f"[{branding}Debug] Input file is already in the requested format: {output_format}") if debug_openai else None
+ return input_file
+ output_file = os.path.splitext(input_file)[0] + f".{output_format}"
+ print(f"[{branding}Debug] Output file: {output_file}") if debug_openai else None
+ ffmpeg_path = "ffmpeg" # Default path for Linux and macOS
+ if sys.platform == "win32":
+ ffmpeg_path = os.path.join(this_dir, "system", "win_ffmpeg", "ffmpeg.exe")
+ ffmpeg = (FFmpeg(ffmpeg_path).option("y").input(input_file).output(output_file))
+ print(f"[{branding}Debug] Transcoding to {output_format}") if debug_openai else None
+ if output_format == "opus":
+ print(f"[{branding}Debug] Configuring Opus options") if debug_openai else None
+ ffmpeg.output(output_file, {"codec:a": "libopus", "b:a": "128k", "vbr": "on", "compression_level": 10, "frame_duration": 60, "application": "voip"})
+ elif output_format == "aac":
+ print(f"[{branding}Debug] Configuring AAC options") if debug_openai else None
+ ffmpeg.output(output_file, {"codec:a": "aac", "b:a": "192k"})
+ elif output_format == "flac":
+ print(f"[{branding}Debug] Configuring FLAC options") if debug_openai else None
+ ffmpeg.output(output_file, {"codec:a": "flac", "compression_level": 8})
+ elif output_format == "wav":
+ print(f"[{branding}Debug] Configuring WAV options") if debug_openai else None
+ ffmpeg.output(output_file, {"codec:a": "pcm_s16le"})
+ elif output_format == "mp3":
+ print(f"[{branding}Debug] Configuring MP3 options") if debug_openai else None
+ ffmpeg.output(output_file, {"codec:a": "libmp3lame", "b:a": "192k"})
+ elif output_format in ["ogg", "m4a"]:
+ if output_format == "ogg":
+ print(f"[{branding}Debug] Configuring OGG options") if debug_openai else None
+ ffmpeg.output(output_file, {"codec:a": "libvorbis"})
+ elif output_format == "m4a":
+ print(f"[{branding}Debug] Configuring M4A options") if debug_openai else None
+ ffmpeg.output(output_file, {"codec:a": "aac", "b:a": "192k"})
+ else:
+ print(f"[{branding}TTS] Unsupported output format: {output_format}")
+ raise ValueError(f"Unsupported output format: {output_format}")
+ try:
+ print(f"[{branding}Debug] Starting transcoding process") if debug_openai else None
+ await ffmpeg.execute()
+ print(f"[{branding}Debug] Transcoding completed successfully") if debug_openai else None
+ except Exception as e:
+ print(f"[{branding}TTS] Error occurred during transcoding: {str(e)}")
+ raise
+ print(f"[{branding}Debug] Transcoding process completed") if debug_openai else None
+ print(f"[{branding}Debug] Transcoded file: {output_file}") if debug_openai else None
+ return output_file
+
+######################################################################################
+# API Endpoint - OpenAI Speech API compatable endpoint change engine voices Function #
+######################################################################################
+class VoiceMappings(BaseModel):
+ alloy: str
+ echo: str
+ fable: str
+ nova: str
+ onyx: str
+ shimmer: str
+
+@app.put("/api/openai-voicemap")
+async def update_openai_voice_mappings(mappings: VoiceMappings):
+ # Update in-memory versions (wont display in Gradio without a restart as gradio caches)
+ model_engine.openai_alloy = mappings.alloy
+ model_engine.openai_echo = mappings.echo
+ model_engine.openai_fable = mappings.fable
+ model_engine.openai_nova = mappings.nova
+ model_engine.openai_onyx = mappings.onyx
+ model_engine.openai_shimmer = mappings.shimmer
+ # Update model_settings.json file for the currently loaded TTS engine
+ try:
+ settings_file = this_dir / "system" / "tts_engines" / model_engine.engine_loaded / "model_settings.json"
+ with open(settings_file, "r+") as f:
+ settings = json.load(f)
+ settings["openai_voices"] = mappings.dict()
+ f.seek(0)
+ json.dump(settings, f, indent=4)
+ f.truncate()
+ except Exception as e:
+ raise HTTPException(status_code=500, detail=f"Failed to update model settings file: {str(e)}")
+ return {"message": "OpenAI voice mappings updated successfully"}
+
+#######################
+# Play at the console #
+#######################
+def play_audio(file_path, volume):
+ print(f"[{branding}GEN] \033[94m File path is: {file_path}") if debug_tts else None
+ print(f"[{branding}GEN] \033[94m Volume is: {volume}") if debug_tts else None
+ normalized_file_path = os.path.normpath(file_path)
+ print(f"[{branding}GEN] \033[94m Normalized file path is: {normalized_file_path}") if debug_tts else None
+ directory = os.path.dirname(normalized_file_path)
+ if os.path.isdir(directory):
+ print(f"[{branding}GEN] \033[94m Directory contents: {os.listdir(directory)}") if debug_tts else None
+ else:
+ print(f"[{branding}GEN] \033[94mError: Directory does not exist: {directory}") if debug_tts else None
+ if not os.path.isfile(normalized_file_path):
+ print(f"[{branding}GEN] \033[94mError: File does not exist: {normalized_file_path}\033[0m") if debug_tts else None
+ return
+ # Check for AAC format
+ if normalized_file_path.lower().endswith('.aac'):
+ print(f"[{branding}GEN] \033[94mPlay Audio : \033[0mAAC format files cannot be played at the console. Please choose another format")
+ return
+ try:
+ print(f"[{branding}GEN] \033[94mPlay Audio : \033[0mPlaying audio at console")
+ data, fs = sf.read(normalized_file_path)
+ sd.play(volume * data, fs)
+ sd.wait()
+ except Exception as e:
+ print(f"[{branding}GEN] \033[94mError playing audio file: {e}\033[0m")
+
+class Request(BaseModel):
+ # Define the structure of the 'Request' class if needed
+ pass
+
+
+class TTSGenerator:
+ @staticmethod
+ def validate_json_input(json_data: Union[Dict, str]) -> Union[None, str]:
+ try:
+ if isinstance(json_data, str):
+ json_data = json.loads(json_data)
+ JSONInput(**json_data)
+ return None # JSON is valid
+ except ValidationError as e:
+ return str(e)
+
+#################################################################
+# /api/tts-generate Generation API Endpoint Narration Filtering #
+#################################################################
+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
+
+def standard_filtering(text_input):
+ text_output = (text_input
+ .replace("***", "")
+ .replace("**", "")
+ .replace("*", "")
+ .replace("\n\n", "\n")
+ .replace("'", "'")
+ )
+ return text_output
+
+#################################################################
+# /api/tts-generate Generation API Endpoint Narration Combining #
+#################################################################
+def combine(output_file_timestamp, output_file_name, audio_files, target_sample_rate=44100):
+ audio = np.array([])
+ try:
+ for audio_file in audio_files:
+ normalized_audio_file = os.path.normpath(audio_file)
+ print(f"[{branding}Debug] Processing file: {normalized_audio_file}") if debug_concat else None
+ # Check if the file exists
+ if not os.path.isfile(normalized_audio_file):
+ print(f"[{branding}Debug] Error: File does not exist: {normalized_audio_file}") if debug_concat else None
+ return None, None
+ # Read the audio file
+ audio_data, current_sample_rate = sf.read(normalized_audio_file)
+ print(f"[{branding}Debug] Read file: {normalized_audio_file}, Sample rate: {current_sample_rate}, Data shape: {audio_data.shape}") if debug_concat else None
+ # Resample if necessary
+ if current_sample_rate != target_sample_rate:
+ print(f"[{branding}Debug] Resampling file from {current_sample_rate} to {target_sample_rate} Hz") if debug_concat else None
+ audio_data = librosa.resample(audio_data, orig_sr=current_sample_rate, target_sr=target_sample_rate)
+ # Concatenate audio data
+ if audio.size == 0:
+ audio = audio_data
+ else:
+ audio = np.concatenate((audio, audio_data))
+ if output_file_timestamp:
+ timestamp = int(time.time())
+ output_file_path = os.path.join(this_dir / "outputs" / f'{output_file_name}_{timestamp}_combined.wav')
+ sf.write(output_file_path, audio, target_sample_rate)
+ # Legacy API or New API return
+ if params["api_def"]["api_use_legacy_api"]:
+ output_file_url = f"http://{api_defaults['api_legacy_ip_address']}:{params['api_def']['api_port_number']}/audio/{output_file_name}_{timestamp}_combined.wav"
+ output_cache_url = f"http://{api_defaults['api_legacy_ip_address']}:{params['api_def']['api_port_number']}/audiocache/{output_file_name}_{timestamp}_combined.wav"
+ else:
+ output_file_url = f'/audio/{output_file_name}_{timestamp}_combined.wav'
+ output_cache_url = f'/audiocache/{output_file_name}_{timestamp}_combined.wav'
+ else:
+ output_file_path = os.path.join(this_dir / "outputs" / f'{output_file_name}_combined.wav')
+ sf.write(output_file_path, audio, target_sample_rate)
+ # Legacy API or New API return
+ if params["api_def"]["api_use_legacy_api"]:
+ output_file_url = f"http://{api_defaults['api_legacy_ip_address']}:{api_defaults['api_port_number']}/audio/{output_file_name}_combined.wav"
+ output_cache_url = f"http://{api_defaults['api_legacy_ip_address']}:{api_defaults['api_port_number']}/audiocache/{output_file_name}_combined.wav"
+ else:
+ output_file_url = f'/audio/{output_file_name}_combined.wav'
+ output_cache_url = f'/audiocache/{output_file_name}_combined.wav'
+ print(f"[{branding}Debug] Output file changed to:", output_file_path) if debug_concat else None
+ print(f"[{branding}Debug] Output file changed to:", output_file_url) if debug_concat else None
+ print(f"[{branding}Debug] Output file changed to:", output_cache_url) if debug_concat else None
+ except Exception as e:
+ print(f"Error occurred: {e}")
+ return output_file_path, output_file_url, output_cache_url
+
+##################################
+# Central RVC Generation request #
+##################################
+def run_rvc(input_tts_path, pth_path, pitch, infer_pipeline):
+ generate_start_time = time.time()
+ f0up_key = pitch
+ filter_radius = rvc_settings["filter_radius"]
+ index_rate = rvc_settings["index_rate"]
+ rms_mix_rate = rvc_settings["rms_mix_rate"]
+ protect = rvc_settings["protect"]
+ hop_length = rvc_settings["hop_length"]
+ f0method = rvc_settings["f0method"]
+ split_audio = rvc_settings["split_audio"]
+ f0autotune = rvc_settings["autotune"]
+ embedder_model = rvc_settings["embedder_model"]
+ training_data_size = rvc_settings["training_data_size"]
+ # Convert path variables to strings
+ input_tts_path = str(input_tts_path)
+ pth_path = str(pth_path)
+ # Check if the model file exists
+ if not os.path.isfile(pth_path):
+ print(f"Model file {pth_path} does not exist. Exiting.")
+ return
+ # Get the directory of the model file
+ model_dir = os.path.dirname(pth_path)
+ # Get the filename of pth_path
+ pth_filename = os.path.basename(pth_path)
+ # Find all .index files in the model directory
+ index_files = [file for file in os.listdir(model_dir) if file.endswith(".index")]
+ if len(index_files) == 1:
+ index_path = str(os.path.join(model_dir, index_files[0]))
+ # Get the filename of index_path
+ index_filename = os.path.basename(index_path)
+ index_filename_print = index_filename
+ index_size_print = training_data_size
+ elif len(index_files) > 1:
+ print(f"[{branding}GEN] \033[94mRVC Convert :\033[0m Multiple RVC index files found in the models folder where \033[93m{pth_filename}\033[0m is")
+ print(f"[{branding}GEN] \033[94mRVC Convert :\033[0m located. Unable to determine which index to use. Continuing without an index file.")
+ index_path = ""
+ index_filename = None
+ index_filename_print = "None used"
+ index_size_print = "N/A"
+ else:
+ index_path = ""
+ index_filename = None
+ index_filename_print = "None used"
+ index_size_print = "N/A"
+ # Set the output path to be the same as the input path
+ output_rvc_path = input_tts_path
+ if debug_rvc:
+ debug_lines = [
+ f"[{branding}Debug] ************************************",
+ f"[{branding}Debug] run_rvc function called (debug_rvc)",
+ f"[{branding}Debug] ***********************************",
+ f"[{branding}Debug] f0up_key : {f0up_key}",
+ f"[{branding}Debug] filter_radius : {filter_radius}",
+ f"[{branding}Debug] index_rate : {index_rate}",
+ f"[{branding}Debug] rms_mix_rate : {rms_mix_rate}",
+ f"[{branding}Debug] protect : {protect}",
+ f"[{branding}Debug] hop_length : {hop_length}",
+ f"[{branding}Debug] f0method : {f0method}",
+ f"[{branding}Debug] input_tts_path : {input_tts_path}",
+ f"[{branding}Debug] output_rvc_path : {output_rvc_path}",
+ f"[{branding}Debug] pth_path : {pth_path}",
+ f"[{branding}Debug] index_path : {index_path}",
+ f"[{branding}Debug] split_audio : {split_audio}",
+ f"[{branding}Debug] f0autotune : {f0autotune}",
+ f"[{branding}Debug] embedder_model : {embedder_model}",
+ f"[{branding}Debug] training_data_size: {training_data_size}"
+ ]
+ debug_info = "\n".join(debug_lines)
+ print(debug_info)
+ # Call the infer_pipeline function
+ infer_pipeline(f0up_key, filter_radius, index_rate, rms_mix_rate, protect, hop_length, f0method,
+ input_tts_path, output_rvc_path, pth_path, index_path, split_audio, f0autotune, embedder_model, training_data_size, debug_rvc)
+ generate_end_time = time.time()
+ generate_elapsed_time = generate_end_time - generate_start_time
+ print(f"[{branding}GEN] \033[94mRVC Convert : \033[94mModel: \033[93m{pth_filename} \033[94mIndex: \033[93m{index_filename_print}\033[0m")
+ print(f"[{branding}GEN] \033[94mRVC Convert : \033[93m{generate_elapsed_time:.2f} seconds. \033[94mMethod: \033[93m{f0method} \033[94mIndex size used \033[93m{index_size_print}\033[0m")
+ return
+
+####################################################################
+# /api/tts-generate Generation API Endpoint Narration RVC handling #
+####################################################################
+def process_rvc_narrator(part_type, voice_gen, pitch, default_model_file, output_file, infer_pipeline):
+ if voice_gen.lower() in ["disabled", "disable"]:
+ return
+ else:
+ voice_gen_path = this_dir / "models" / "rvc_voices" / voice_gen
+ pth_path = voice_gen_path if voice_gen else default_model_file
+ run_rvc(output_file, pth_path, pitch, infer_pipeline)
+
+#############################################################
+# /api/tts-generate Generation API Endpoint JSON validation #
+#############################################################
+class JSONInput(BaseModel):
+ text_input: str = Field(..., max_length=int(api_defaults["api_max_characters"]), description=f"text_input needs to be {api_defaults['api_max_characters']} characters or less.")
+ text_filtering: str = Field(..., pattern="^(none|standard|html)$", description="text_filtering needs to be 'none', 'standard' or 'html'.")
+ #character_voice_gen: str = Field(..., pattern=r'^[\(\)a-zA-Z0-9\_\-./\s]+$', description="character_voice_gen needs to be the name of a valid voice for the loaded TTS engine.")
+ rvccharacter_voice_gen: str = Field(..., description="rvccharacter_voice_gen needs to be the name of a valid pth file in the 'folder\\file.pth' format or the word 'Disabled'.")
+ rvccharacter_pitch: float = Field(..., description="RVC Character pitch needs to be a number between -24 and 24")
+ narrator_enabled: str = Field(..., pattern="^(true|false|silent)$", description="narrator_enabled needs to be 'true', 'false' or 'silent'.")
+ narrator_voice_gen: str = Field(..., pattern=r'^[\(\)a-zA-Z0-9\_\-./\s]+$', description="character_voice_gen needs to be the name of a valid voice for the loaded TTS engine.")
+ rvcnarrator_voice_gen: str = Field(..., description="rvcnarrator_voice_gen needs to be the name of a valid pth file in the 'folder\\file.pth' format or the word 'Disabled'.")
+ rvcnarrator_pitch: float = Field(..., description="RVC Narrator pitch needs to be a number between -24 and 24")
+ text_not_inside: str = Field(..., pattern="^(character|narrator|silent)$", description="text_not_inside needs to be 'character', 'narrator' or 'silent'.")
+ language: str = Field(..., pattern="^(ar|zh-cn|zh|cs|nl|en|fr|de|hu|hi|it|ja|ko|pl|pt|ru|es|tr)$", description="language needs to be one of the following: ar, zh-cn, zh, cs, nl, en, fr, de, hu, hi, it, ja, ko, pl, pt, ru, es, tr.")
+ output_file_name: str = Field(..., pattern="^[a-zA-Z0-9_]+$", description="output_file_name needs to be the name without any special characters or file extension, e.g., 'filename'.")
+ output_file_timestamp: bool = Field(..., description="output_file_timestamp needs to be true or false.")
+ autoplay: bool = Field(..., description="autoplay needs to be a true or false value.")
+ autoplay_volume: float = Field(..., ge=0.1, le=1.0, description="autoplay_volume needs to be from 0.1 to 1.0.")
+ speed: float = Field(..., ge=0.25, le=2.0, description="speed needs to be between 0.25 and 2.0.")
+ pitch: float = Field(..., ge=-10, le=10, description="pitch needs to be between -10 and 10.")
+ temperature: float = Field(..., ge=0.1, le=1.0, description="temperature needs to be between 0.1 and 1.0.")
+ repetition_penalty: float = Field(..., ge=1.0, le=20.0, description="repetition_penalty needs to be between 1.0 and 20.0.")
+
+ @classmethod
+ def validate_autoplay_volume(cls, value):
+ if not (0.1 <= value <= 1.0):
+ raise ValueError("Autoplay volume must be between 0.1 and 1.0")
+ return value
+
+ def validate_rvccharacter_voice_gen(cls, v):
+ if v.lower() == "disabled":
+ return v
+ pattern = re.compile(r'^.*\.(pth)$')
+ if not pattern.match(v):
+ raise ValueError("rvccharacter_voice_gen needs to be the name of a valid pth file in the 'folder\\file.pth' format or 'Disabled'.")
+ return v
+
+ def validate_rvnarrator_voice_gen(cls, v):
+ if v.lower() == "disabled":
+ return v
+ pattern = re.compile(r'^.*\.(pth)$')
+ if not pattern.match(v):
+ raise ValueError("rvcnarrator_voice_gen needs to be the name of a valid pth file in the 'folder\\file.pth' format or 'Disabled'.")
+ return v
+
+ def validate_pitches(cls, value):
+ try:
+ num_value = float(value)
+ except ValueError:
+ raise ValueError("Pitch must be a number or a string representing a number.")
+ if not -24 <= num_value <= 24:
+ raise ValueError("Pitch needs to be a number between -24 and 24.")
+ return value
+
+def validate_json_input(json_input_data):
+ try:
+ JSONInput(**json_input_data)
+ except ValidationError as e:
+ error_messages = []
+ for error in e.errors():
+ field = error["loc"][0]
+ description = JSONInput.__fields__[field].description
+ error_messages.append(f"{field}: {description}")
+ error_message = "\n".join(error_messages)
+ print(f"[{branding}API] \033[91mError with API request:\033[0m", error_message)
+ return error_message
+ return None
+
+###############################################################
+# /api/tts-generate Generation API Endpoint pull fresh values #
+###############################################################
+def get_api_text_filtering():
+ return api_defaults["api_text_filtering"]
+def get_api_narrator_enabled():
+ return api_defaults["api_narrator_enabled"]
+def get_api_text_not_inside():
+ return api_defaults["api_text_not_inside"]
+def get_api_language():
+ return api_defaults["api_language"]
+def get_api_output_file_name():
+ return api_defaults["api_output_file_name"]
+def get_api_output_file_timestamp():
+ return api_defaults["api_output_file_timestamp"]
+def get_api_autoplay():
+ return api_defaults["api_autoplay"]
+def get_api_autoplay_volume():
+ return api_defaults["api_autoplay_volume"]
+def get_params_speed():
+ return model_engine.generationspeed_set
+def get_params_temperature():
+ return model_engine.temperature_set
+def get_params_repetition():
+ value = model_engine.repetitionpenalty_set
+ return float(str(value).replace(',', '.'))
+def get_params_pitch():
+ return model_engine.pitch_set
+def get_character_voice_gen():
+ return model_engine.def_character_voice
+def get_rvccharacter_voice_gen():
+ return rvc_settings["rvc_char_model_file"]
+def get_rvccharacter_pitch():
+ return rvc_settings["pitch"]
+def get_narrator_voice_gen():
+ return model_engine.def_narrator_voice
+def get_rvcnarrator_voice_gen():
+ return rvc_settings["rvc_narr_model_file"]
+def get_rvcnarrator_pitch():
+ return rvc_settings["pitch"]
+
+#############################################
+# /api/tts-generate Generation API Endpoint #
+#############################################
+@app.post("/api/tts-generate", response_class=JSONResponse)
+async def apifunction_generate_tts_standard(
+ text_input: str = Form(...),
+ text_filtering: str = Form(None),
+ character_voice_gen: str = Form(None),
+ rvccharacter_voice_gen: str = Form(None),
+ rvccharacter_pitch: float = Form(None),
+ narrator_enabled: str = Form(None),
+ narrator_voice_gen: str = Form(None),
+ rvcnarrator_voice_gen: str = Form(None),
+ rvcnarrator_pitch: float = Form(None),
+ text_not_inside: str = Form(None),
+ language: str = Form(None),
+ output_file_name: str = Form(None),
+ output_file_timestamp: bool = Form(None),
+ autoplay: bool = Form(None),
+ autoplay_volume: float = Form(None),
+ streaming: bool = Form(False),
+ speed: float = Form(None),
+ temperature: float = Form(None),
+ repetition_penalty: float = Form(None),
+ pitch: float = Form(None),
+ _text_filtering: str = Depends(get_api_text_filtering),
+ _character_voice_gen: str = Depends(get_character_voice_gen),
+ _rvccharacter_voice_gen: str = Depends(get_rvccharacter_voice_gen),
+ _rvccharacter_pitch: str = Depends(get_rvccharacter_pitch),
+ _narrator_enabled: str = Depends(get_api_narrator_enabled),
+ _narrator_voice_gen: str = Depends(get_narrator_voice_gen),
+ _rvcnarrator_voice_gen: str = Depends(get_rvcnarrator_voice_gen),
+ _rvcnarrator_pitch: str = Depends(get_rvcnarrator_pitch),
+ _text_not_inside: str = Depends(get_api_text_not_inside),
+ _language: str = Depends(get_api_language),
+ _output_file_name: str = Depends(get_api_output_file_name),
+ _output_file_timestamp: bool = Depends(get_api_output_file_timestamp),
+ _autoplay: bool = Depends(get_api_autoplay),
+ _autoplay_volume: float = Depends(get_api_autoplay_volume),
+ _speed: float = Depends(get_params_speed),
+ _temperature: float = Depends(get_params_temperature),
+ _repetition_penalty: float = Depends(get_params_repetition),
+ _pitch: float = Depends(get_params_pitch),
+):
+ if debug_tts or debug_tts_variables:
+ debug_lines = [
+ f"[{branding}Debug] *******************************************************",
+ f"[{branding}Debug] /api/tts_generate function called (debug_tts_variables)",
+ f"[{branding}Debug] **** PRE validation checks ****",
+ f"[{branding}Debug] *******************************************************",
+ f"[{branding}Debug] Defaults will be pulled from Settings if not specified in API Request!",
+ f"[{branding}Debug] max_characters : {api_defaults['api_max_characters']}",
+ f"[{branding}Debug] length_stripping : {api_defaults['api_length_stripping']}",
+ f"[{branding}Debug] legacy_api : {api_defaults['api_use_legacy_api']}",
+ f"[{branding}Debug] legacy_api IPadd : {api_defaults['api_legacy_ip_address']}",
+ f"[{branding}Debug] allowed_filter : {api_defaults['api_allowed_filter']}",
+ f"[{branding}Debug] text_filtering : {text_filtering}",
+ f"[{branding}Debug] character_voice_gen : {character_voice_gen}",
+ f"[{branding}Debug] rvccharacter_voice_gen : {rvccharacter_voice_gen}",
+ f"[{branding}Debug] rvccharacter_pitch : {rvccharacter_pitch}",
+ f"[{branding}Debug] narrator_enabled : {narrator_enabled}",
+ f"[{branding}Debug] narrator_voice_gen : {narrator_voice_gen}",
+ f"[{branding}Debug] rvcnarrator_voice_gen : {rvcnarrator_voice_gen}",
+ f"[{branding}Debug] rvcnarrator_pitch : {rvcnarrator_pitch}",
+ f"[{branding}Debug] text_not_inside : {text_not_inside}",
+ f"[{branding}Debug] language : {language}",
+ f"[{branding}Debug] output_file_name : {output_file_name}",
+ f"[{branding}Debug] output_file_timestamp : {output_file_timestamp}",
+ f"[{branding}Debug] autoplay : {autoplay}",
+ f"[{branding}Debug] autoplay_volume : {autoplay_volume}",
+ f"[{branding}Debug] streaming : {streaming}",
+ f"[{branding}Debug] speed : {speed}",
+ f"[{branding}Debug] temperature : {temperature}",
+ f"[{branding}Debug] repetition_penalty : {repetition_penalty}",
+ f"[{branding}Debug] pitch : {pitch}"
+ ]
+ debug_info = "\n".join(debug_lines)
+ print(debug_info)
+ text_filtering = text_filtering or _text_filtering
+ character_voice_gen = character_voice_gen or _character_voice_gen
+ rvccharacter_voice_gen = rvccharacter_voice_gen or _rvccharacter_voice_gen
+ rvccharacter_pitch = rvccharacter_pitch if rvccharacter_pitch is not None else _rvccharacter_pitch
+ narrator_enabled = narrator_enabled or _narrator_enabled # NEED TO COME BACK AND LOOK AT THIS!!!
+ narrator_voice_gen = narrator_voice_gen or _narrator_voice_gen
+ rvcnarrator_voice_gen = rvcnarrator_voice_gen or _rvcnarrator_voice_gen
+ rvcnarrator_pitch = rvcnarrator_pitch if rvcnarrator_pitch is not None else _rvcnarrator_pitch
+ text_not_inside = text_not_inside or _text_not_inside
+ language = language or _language
+ output_file_name = output_file_name or _output_file_name
+ output_file_timestamp = output_file_timestamp if output_file_timestamp is not None else _output_file_timestamp
+ autoplay = autoplay if autoplay is not None else _autoplay
+ autoplay_volume = autoplay_volume or _autoplay_volume
+ speed = speed or _speed
+ temperature = temperature or _temperature
+ repetition_penalty = repetition_penalty or _repetition_penalty
+ pitch = pitch or _pitch
+ try:
+ json_input_data = {
+ "text_input": text_input,
+ "text_filtering": text_filtering,
+ "character_voice_gen": character_voice_gen,
+ "rvccharacter_voice_gen": rvccharacter_voice_gen,
+ "rvccharacter_pitch": rvccharacter_pitch,
+ "narrator_enabled": narrator_enabled,
+ "narrator_voice_gen": narrator_voice_gen,
+ "rvcnarrator_voice_gen": rvcnarrator_voice_gen,
+ "rvcnarrator_pitch": rvcnarrator_pitch,
+ "text_not_inside": text_not_inside,
+ "language": language,
+ "output_file_name": output_file_name,
+ "output_file_timestamp": output_file_timestamp,
+ "autoplay": autoplay,
+ "autoplay_volume": autoplay_volume,
+ "streaming": streaming,
+ "speed": speed,
+ "temperature": temperature,
+ "repetition_penalty": repetition_penalty,
+ "pitch": pitch,
+ }
+ JSONresult = validate_json_input(json_input_data)
+ if JSONresult is None:
+ pass
+ else:
+ return JSONResponse(content={"error": JSONresult}, status_code=400)
+
+ if debug_tts or debug_tts_variables:
+ debug_lines = [
+ f"[{branding}Debug] *******************************************************",
+ f"[{branding}Debug] /api/tts_generate function called (debug_tts_variables)",
+ f"[{branding}Debug] **** POST validation checks ****",
+ f"[{branding}Debug] *******************************************************",
+ f"[{branding}Debug] Defaults will be pulled from Settings if not specified in API Request!",
+ f"[{branding}Debug] max_characters : {api_defaults['api_max_characters']}",
+ f"[{branding}Debug] length_stripping : {api_defaults['api_length_stripping']}",
+ f"[{branding}Debug] legacy_api : {api_defaults['api_use_legacy_api']}",
+ f"[{branding}Debug] legacy_api IPadd : {api_defaults['api_legacy_ip_address']}",
+ f"[{branding}Debug] allowed_filter : {api_defaults['api_allowed_filter']}",
+ f"[{branding}Debug] text_filtering : {text_filtering}",
+ f"[{branding}Debug] character_voice_gen : {character_voice_gen}",
+ f"[{branding}Debug] rvccharacter_voice_gen : {rvccharacter_voice_gen}",
+ f"[{branding}Debug] rvccharacter_pitch : {rvccharacter_pitch}",
+ f"[{branding}Debug] narrator_enabled : {narrator_enabled}",
+ f"[{branding}Debug] narrator_voice_gen : {narrator_voice_gen}",
+ f"[{branding}Debug] rvcnarrator_voice_gen : {rvcnarrator_voice_gen}",
+ f"[{branding}Debug] rvcnarrator_pitch : {rvcnarrator_pitch}",
+ f"[{branding}Debug] text_not_inside : {text_not_inside}",
+ f"[{branding}Debug] language : {language}",
+ f"[{branding}Debug] output_file_name : {output_file_name}",
+ f"[{branding}Debug] output_file_timestamp : {output_file_timestamp}",
+ f"[{branding}Debug] autoplay : {autoplay}",
+ f"[{branding}Debug] autoplay_volume : {autoplay_volume}",
+ f"[{branding}Debug] streaming : {streaming}",
+ f"[{branding}Debug] speed : {speed}",
+ f"[{branding}Debug] temperature : {temperature}",
+ f"[{branding}Debug] repetition_penalty : {repetition_penalty}",
+ f"[{branding}Debug] pitch : {pitch}"
+ ]
+ debug_info = "\n".join(debug_lines)
+ print(debug_info)
+ if narrator_enabled.lower() == "silent" and text_not_inside.lower() == "silent":
+ print(f"[{branding}GEN] \033[92mBoth Narrator & Text-not-inside are set to \033[91msilent\033[92m. If you get no TTS, this is why.\033[0m")
+ if narrator_enabled.lower() in ["true", "silent"]:
+ if model_engine.lowvram_enabled and model_engine.device == "cpu":
+ await model_engine.handle_lowvram_change()
+ model_engine.tts_narrator_generatingtts = True
+ print(f"[{branding}Debug] Moved into Narrator") if debug_tts else None
+ print(f"[{branding}Debug] original text : {text_input}") if debug_tts else None
+ processed_parts = process_text(text_input)
+ audio_files_all_paragraphs = []
+ for part_type, part in processed_parts:
+ # Skip parts that are too short
+ if len(part.strip()) <= int(params["api_def"]["api_length_stripping"]):
+ continue
+ # Determine the voice to use based on the part type
+ if part_type == 'narrator':
+ if narrator_enabled.lower() == "silent":
+ print(f"[{branding}GEN] \033[95mNarrator Silent:\033[0m", part) # Green
+ continue # Skip generating audio for narrator parts if set to "silent"
+ voice_to_use = narrator_voice_gen
+ print(f"[{branding}GEN] \033[92mNarrator:\033[0m", part) # Purple
+ elif part_type == 'character':
+ voice_to_use = character_voice_gen
+ print(f"[{branding}GEN] \033[36mCharacter:\033[0m", part) # Yellow
+ else:
+ # Handle ambiguous parts based on user preference
+ if text_not_inside == "silent":
+ print(f"[{branding}GEN] \033[95mText-not-inside Silent:\033[0m", part) # Purple
+ continue # Skip generating audio for ambiguous parts if set to "silent"
+ voice_to_use = character_voice_gen if text_not_inside == "character" else narrator_voice_gen
+ voice_description = "\033[36mCharacter (Text-not-inside)\033[0m" if text_not_inside == "character" else "\033[92mNarrator (Text-not-inside)\033[0m"
+ print(f"[{branding}GEN] {voice_description}:", part)
+ # 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(rf'{api_defaults["api_allowed_filter"]}', '', cleaned_part)
+ print(f"[{branding}Debug] text after api_allowed: {text_input}") if debug_tts else None
+ # Remove all newline characters (single or multiple)
+ cleaned_part = re.sub(r'\n+', ' ', cleaned_part)
+ output_file = this_dir / output_directory / f'{output_file_name}_{uuid.uuid4()}_{int(time.time())}.{model_engine.audio_format}'
+ output_file_str = output_file.as_posix()
+ response = await generate_audio(cleaned_part, voice_to_use, language,temperature, repetition_penalty, speed, pitch, output_file_str, streaming)
+ audio_path = output_file_str
+ if rvc_settings["rvc_enabled"]:
+ if part_type == 'character':
+ process_rvc_narrator(part_type, rvccharacter_voice_gen, rvccharacter_pitch, rvc_settings["rvc_char_model_file"], output_file, infer_pipeline)
+ elif part_type == 'narrator':
+ process_rvc_narrator(part_type, rvcnarrator_voice_gen, rvcnarrator_pitch, rvc_settings["rvc_narr_model_file"], output_file, infer_pipeline)
+ else:
+ if text_not_inside == 'character':
+ process_rvc_narrator('character', rvccharacter_voice_gen, rvccharacter_pitch, rvc_settings["rvc_char_model_file"], output_file, infer_pipeline)
+ elif text_not_inside == 'narrator':
+ process_rvc_narrator('narrator', rvcnarrator_voice_gen, rvcnarrator_pitch, rvc_settings["rvc_narr_model_file"], output_file, infer_pipeline)
+ print(f"[{branding}Debug] Appending audio path to list") if debug_tts else None
+ audio_files_all_paragraphs.append(audio_path)
+ # Combine audio files across paragraphs
+ model_engine.tts_narrator_generatingtts = False
+ print(f"[{branding}Debug] Narrator sending to combine") if debug_tts else None
+ output_file_path, output_file_url, output_cache_url = combine(output_file_timestamp, output_file_name, audio_files_all_paragraphs)
+ # Transcode audio if necessary
+ model_audio_format = str(model_engine.audio_format).lower()
+ output_audio_format = str(params["transcode_audio_format"]).lower()
+ if output_audio_format == "disabled":
+ pass
+ else:
+ if model_audio_format != output_audio_format and not model_engine.tts_narrator_generatingtts:
+ print(f"[{branding}Debug] Pre Transcode call - Output file: {output_file_path}") if debug_tts else None
+ output_file_path, output_file_url, output_cache_url = await transcode_audio_if_necessary(output_file_path, model_audio_format, output_audio_format)
+ print(f"[{branding}Debug] Post Transcode call - Output file: {output_file_path}") if debug_tts else None
+ if sounddevice_installed == False:
+ autoplay = False
+ if autoplay:
+ play_audio(output_file_path, autoplay_volume)
+ # Move model back to cpu system ram if needed.
+ if model_engine.lowvram_enabled and model_engine.device == "cuda" and model_engine.lowvram_capable:
+ await model_engine.handle_lowvram_change()
+ return JSONResponse(content={"status": "generate-success", "output_file_path": str(output_file_path), "output_file_url": str(output_file_url), "output_cache_url": str(output_cache_url)}, status_code=200)
+ else:
+ print(f"[{branding}Debug] Moved into Standard generation") if debug_tts else None
+ print(f"[{branding}Debug] original text : {text_input}") if debug_tts else None
+ if output_file_timestamp:
+ timestamp = int(time.time())
+ # Generate a standard UUID
+ original_uuid = uuid.uuid4()
+ # Hash the UUID using SHA-256
+ hash_object = hashlib.sha256(str(original_uuid).encode())
+ hashed_uuid = hash_object.hexdigest()
+ # Truncate to the desired length, for example, 16 characters
+ short_uuid = hashed_uuid[:5]
+ output_file_path = this_dir / output_directory / f'{output_file_name}_{timestamp}{short_uuid}.{model_engine.audio_format}'
+ #Legacy API or New API return
+ if params["api_def"]["api_use_legacy_api"]:
+ output_file_url = f'http://{api_defaults["api_legacy_ip_address"]}:{api_defaults["api_port_number"]}/audio/{output_file_name}_{timestamp}{short_uuid}.{model_engine.audio_format}'
+ output_cache_url = f'http://{api_defaults["api_legacy_ip_address"]}:{api_defaults["api_port_number"]}/audiocache/{output_file_name}_{timestamp}{short_uuid}.{model_engine.audio_format}'
+ else:
+ output_file_url = f'/audio/{output_file_name}_{timestamp}{short_uuid}.{model_engine.audio_format}'
+ output_cache_url = f'/audiocache/{output_file_name}_{timestamp}{short_uuid}.{model_engine.audio_format}'
+ else:
+ output_file_path = this_dir / output_directory / f"{output_file_name}.{model_engine.audio_format}"
+ # Legacy API or New API return
+ if params["api_def"]["api_use_legacy_api"]:
+ output_file_url = f'http://{api_defaults["api_legacy_ip_address"]}:{api_defaults["api_port_number"]}/audio/{output_file_name}.{model_engine.audio_format}'
+ output_cache_url = f'http://{api_defaults["api_legacy_ip_address"]}:{api_defaults["api_port_number"]}/audiocache/{output_file_name}.{model_engine.audio_format}'
+ else:
+ output_file_url = f'/audio/{output_file_name}.{model_engine.audio_format}'
+ output_cache_url = f'/audiocache/{output_file_name}.{model_engine.audio_format}'
+ if text_filtering == "html":
+ cleaned_string = html.unescape(standard_filtering(text_input))
+ cleaned_string = re.sub(r'([!?.])\1+', r'\1', cleaned_string)
+ # Further clean to remove any other unwanted characters
+ cleaned_string = re.sub(rf'{api_defaults["api_allowed_filter"]}', '', cleaned_string)
+ print(f"[{branding}Debug] HTML Filtering - text after api_allowed: {cleaned_string}") if debug_tts else None
+ # Remove all newline characters (single or multiple)
+ cleaned_string = re.sub(r'\n+', ' ', cleaned_string)
+ elif text_filtering == "standard":
+ cleaned_string = re.sub(r'([!?.])\1+', r'\1', text_input)
+ # Further clean to remove any other unwanted characters
+ cleaned_string = re.sub(rf'{api_defaults["api_allowed_filter"]}', '', cleaned_string)
+ print(f"[{branding}Debug] Standard Filtering - text after api_allowed: {cleaned_string}") if debug_tts else None
+ # Remove all newline characters (single or multiple)
+ cleaned_string = re.sub(r'\n+', ' ', cleaned_string)
+ else:
+ print(f"[{branding}Debug] No filtering text : {text_input}") if debug_tts else None
+ cleaned_string = text_input
+ print(f"[{branding}GEN]", cleaned_string)
+ print(f"[{branding}Debug] Sending request to generate_audio in tts_server.py") if debug_tts else None
+ try:
+ if streaming:
+ response = await generate_audio(cleaned_string, character_voice_gen, language, temperature, repetition_penalty, speed, pitch, output_file_path, streaming)
+ return StreamingResponse(response, media_type="audio/wav")
+ else:
+ response = await generate_audio(cleaned_string, character_voice_gen, language, temperature, repetition_penalty, speed, pitch, output_file_path, streaming)
+ if rvc_settings["rvc_enabled"]:
+ if rvccharacter_voice_gen.lower() in ["disabled", "disable"]:
+ print(f"[{branding}Debug] Pass rvccharacter_voice_gen") if debug_tts else None
+ pass # Skip RVC processing for character part
+ else:
+ print(f"[{branding}Debug] send to rvc") if debug_tts else None
+ rvccharacter_voice_gen = this_dir / "models" / "rvc_voices" / rvccharacter_voice_gen
+ pth_path = rvccharacter_voice_gen if rvccharacter_voice_gen else rvc_settings["rvc_char_model_file"]
+ run_rvc(output_file_path, pth_path, rvccharacter_pitch, infer_pipeline)
+ # Transcode audio if necessary
+ model_audio_format = str(model_engine.audio_format).lower()
+ output_audio_format = str(params["transcode_audio_format"]).lower()
+ print(f"[{branding}Debug] At Model audio format") if debug_tts else None
+ if output_audio_format == "disabled":
+ pass
+ else:
+ if model_audio_format != output_audio_format and not model_engine.tts_narrator_generatingtts:
+ print(f"[{branding}Debug] Pre Transcode call - Output file: {output_file_path}") if debug_tts else None
+ output_file_path, output_file_url, output_cache_url = await transcode_audio_if_necessary(output_file_path, model_audio_format, output_audio_format)
+ print(f"[{branding}Debug] Post Transcode call - Output file: {output_file_path}") if debug_tts else None
+ if sounddevice_installed == False:
+ autoplay = False
+ if autoplay:
+ play_audio(output_file_path, autoplay_volume)
+ return JSONResponse(content={"status": "generate-success", "output_file_path": str(output_file_path), "output_file_url": str(output_file_url), "output_cache_url": str(output_cache_url)}, status_code=200)
+ except Exception as e:
+ return JSONResponse(content={"status": "generate-failure", "error": "An error occurred"}, status_code=500)
+ except Exception as e:
+ return JSONResponse(content={"status": "generate-failure", "error": "An error occurred"}, status_code=500)
+
+#############################
+#### Word Add-in Sharing ####
+#############################
+# Mount the static files from the 'word_addin' directory
+app.mount("/api/word_addin", StaticFiles(directory=os.path.join(this_dir / 'system' / 'word_addin')), name="word_addin")
+
+#############################################
+#### TTS Generator Comparision Endpoints ####
+#############################################
+import aiofiles
+class TTSItem(BaseModel):
+ id: int
+ fileUrl: str
+ text: str
+ characterVoice: str
+ language: str
+
+class TTSData(BaseModel):
+ ttsList: List[TTSItem]
+
+@app.post("/api/save-tts-data")
+async def apifunction_save_tts_data(tts_data: List[TTSItem]):
+ # Convert the list of Pydantic models to a list of dictionaries
+ tts_data_list = [item.dict() for item in tts_data]
+ # Serialize the list of dictionaries to a JSON string
+ tts_data_json = json.dumps(tts_data_list, indent=4)
+ async with aiofiles.open(this_dir / output_directory / "ttsList.json", 'w') as f:
+ await f.write(tts_data_json)
+ return {"message": "Data saved successfully"}
+
+########################################
+# Trigger TTS Gen Text/Speech Analysis #
+########################################
+@app.get("/api/trigger-analysis")
+async def apifunction_trigger_analysis(threshold: int = Query(default=98)):
+ venv_path = sys.prefix
+ env = os.environ.copy()
+ env["PATH"] = os.path.join(venv_path, "bin") + ":" + env["PATH"]
+ ttslist_path = this_dir / output_directory / "ttsList.json"
+ wavfile_path = this_dir / output_directory
+ subprocess.run([sys.executable, "tts_diff.py", f"--threshold={threshold}", f"--ttslistpath={ttslist_path}", f"--wavfilespath={wavfile_path}"], cwd=this_dir / "system" / "tts_diff", env=env)
+ # Read the analysis summary
+ try:
+ with open(this_dir / output_directory / "analysis_summary.json", "r") as summary_file:
+ summary_data = json.load(summary_file)
+ except FileNotFoundError:
+ summary_data = {"error": "Analysis summary file not found."}
+ return {"message": "Analysis Completed", "summary": summary_data}
+
+###########################################
+# TTS Generator SRT Subtitiles generation #
+###########################################
+@app.get("/api/srt-generation")
+async def apifunction_srt_generation():
+ venv_path = sys.prefix
+ env = os.environ.copy()
+ env["PATH"] = os.path.join(venv_path, "bin") + ":" + env["PATH"]
+ ttslist_path = this_dir / output_directory / "ttsList.json"
+ wavfile_path = this_dir / output_directory
+ subprocess.run([sys.executable, "tts_srt.py", f"--ttslistpath={ttslist_path}", f"--wavfilespath={wavfile_path}"], cwd=this_dir / "system" / "tts_srt", env=env)
+ srt_file_path = this_dir / output_directory / "subtitles.srt"
+ if not srt_file_path.exists():
+ raise HTTPException(status_code=404, detail="Subtitle file not found.")
+ return FileResponse(path=srt_file_path, filename="subtitles.srt", media_type='application/octet-stream')
+
+#################################
+# Static Mount for file serving #
+#################################
+app.mount("/static", StaticFiles(directory=str(this_dir / "system")), name="static")
+
+########################################
+# Legacy JSON update settings function #
+########################################
+# Setup logging
+logging.basicConfig(level=logging.DEBUG)
+def get_json_data():
+ with open(this_dir / "confignew.json", "r") as json_file:
+ data = json.load(json_file)
+ return data
+
+@app.get("/settings")
+async def get_settings(request: Request):
+ data = get_json_data()
+ return templates.TemplateResponse("admin.html", {"request": request, "data": data})
+
+@app.get("/settings-json")
+async def get_settings_json():
+ return get_json_data()
+
+@app.post("/update-settings")
+async def update_settings(
+ request: Request,
+ delete_output_wavs: str = Form(...),
+ gradio_interface: str = Form(...),
+ gradio_port_number: int = Form(...),
+ api_port_number: int = Form(...),
+):
+ data = get_json_data()
+ # Update the settings based on the form values
+ data["delete_output_wavs"] = delete_output_wavs
+ data["gradio_interface"] = gradio_interface.lower() == 'true'
+ data["gradio_port_number"] = gradio_port_number
+ data["api_def"]["api_port_number"] = api_port_number
+ # Save the updated settings back to the JSON file
+ with open(this_dir / "confignew.json", "w") as json_file:
+ json.dump(data, json_file, indent=4)
+ # Redirect to the settings page to display the updated settings
+ return templates.TemplateResponse("admin.html", {"request": request, "data": data})
+
+# Create an instance of Jinja2Templates for rendering HTML templates
+templates = Jinja2Templates(directory=this_dir / "system")
+# Get the admin interface template
+template = templates.get_template("admin.html")
+# Render the template with the dynamic values
+rendered_html = template.render(params=params)
+
+###################################################
+#### Webserver Startup & Initial model Loading ####
+###################################################
+@app.get("/")
+async def read_root():
+ return HTMLResponse(content=rendered_html, status_code=200)
+
+# Start Uvicorn Webserver
+# port_parameter = int(params["api_def"]["api_port_number"])
+
+if __name__ == "__main__":
+ import uvicorn
+ # Command line argument parser
+ parser = argparse.ArgumentParser(description="AllTalk TTS Server")
+ parser.add_argument("--port", type=int, help="Port number for the server")
+ args = parser.parse_args()
+ # Determine the port to use
+ config_port = int(params["api_def"]["api_port_number"])
+ port_to_use = args.port if args.port is not None else config_port
+ # Start Uvicorn Webserver
+ uvicorn_server = uvicorn.run(app, host="0.0.0.0", port=port_to_use, log_level="debug")
+
From 37be9472ca33f2763ae692e2b247aabc68c8f608 Mon Sep 17 00:00:00 2001
From: erew123 <35898566+erew123@users.noreply.github.com>
Date: Mon, 16 Sep 2024 21:54:25 +0100
Subject: [PATCH 33/38] Update tts_server.py
---
tts_server.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/tts_server.py b/tts_server.py
index a74ba0a..ba93900 100644
--- a/tts_server.py
+++ b/tts_server.py
@@ -660,7 +660,7 @@ async def generate_audio(text, voice, language, temperature, repetition_penalty,
async for _ in response:
pass
except Exception as e:
- print(f"[GEN] Error during audio generation: {str(e)}")
+ print(f"{branding}[GEN] Error during audio generation: {str(e)}")
raise
###########################
From e68ec7d733080644fe4f392499456480a77002df Mon Sep 17 00:00:00 2001
From: erew123 <35898566+erew123@users.noreply.github.com>
Date: Tue, 17 Sep 2024 11:40:01 +0100
Subject: [PATCH 34/38] add settings mgmt
---
tts_mem.py | 308 ++++++++++++++++++++++++++++++++++++++++++++++-------
1 file changed, 269 insertions(+), 39 deletions(-)
diff --git a/tts_mem.py b/tts_mem.py
index 20a6abd..7e827b3 100644
--- a/tts_mem.py
+++ b/tts_mem.py
@@ -1,4 +1,6 @@
import subprocess
+import threading
+import signal
import sys
import os
import gradio as gr
@@ -8,12 +10,53 @@ from requests.exceptions import RequestException
import socket
import json
+# Add these at the beginning of your file, after other imports
+CONFIG_FILE = "mem_config.json"
+
# Dictionary to store all processes
processes = {}
-max_instances = 16
script_path = "tts_server.py"
-current_base_port = 7001
-server_port = 7500
+
+# Global flag to indicate if the program should exit
+should_exit = threading.Event()
+
+def signal_handler(signum, frame):
+ print("[AllTalk MEM] Interrupt received, shutting down...")
+ should_exit.set()
+
+# Default configuration
+default_config = {
+ "base_port": 7001,
+ "auto_start_engines": 0,
+ "max_instances": 8,
+ "gradio_interface_port": 7500,
+ "max_retries": 8,
+ "initial_wait": 3,
+ "backoff_factor": 1.2,
+ "debug_mode": False
+}
+
+def load_config():
+ if os.path.exists(CONFIG_FILE):
+ with open(CONFIG_FILE, 'r') as f:
+ loaded_config = json.load(f)
+ # Update loaded_config with any missing keys from default_config
+ for key, value in default_config.items():
+ if key not in loaded_config:
+ loaded_config[key] = value
+ # Ensure debug_mode is a boolean
+ loaded_config['debug_mode'] = bool(loaded_config['debug_mode'])
+ return loaded_config
+ return default_config.copy()
+
+def save_config(config):
+ with open(CONFIG_FILE, 'w') as f:
+ json.dump(config, f, indent=2)
+
+# Load configuration at the start of the script
+config = load_config()
+current_base_port = config['base_port']
+max_instances = config['max_instances']
############################################
# START-UP # Display initial splash screen #
@@ -29,8 +72,8 @@ print(f"[AllTalk MEM]")
print(f"[AllTalk MEM] \033[93m MEM is not intended for production use and\033[00m")
print(f"[AllTalk MEM] \033[93m there is NO support being offered on MEM\033[00m")
print(f"[AllTalk MEM]")
-print(f"[AllTalk MEM] \033[94mGradio Light:\033[00m \033[92mhttp://127.0.0.1:{server_port}\033[00m")
-print(f"[AllTalk MEM] \033[94mGradio Dark :\033[00m \033[92mhttp://127.0.0.1:{server_port}?__theme=dark\033[00m")
+print(f"[AllTalk MEM] \033[94mGradio Light:\033[00m \033[92mhttp://127.0.0.1:{config['gradio_interface_port']}\033[00m")
+print(f"[AllTalk MEM] \033[94mGradio Dark :\033[00m \033[92mhttp://127.0.0.1:{config['gradio_interface_port']}?__theme=dark\033[00m")
print(f"[AllTalk MEM]")
def is_port_in_use(port):
@@ -92,23 +135,23 @@ def check_subprocess_status(instance_id, port):
# This code block tests if one of the engines has started in a timely fashion. You can increase the max retries and/or backoff factor
# to allow for longer startup times of the TTS engine.
-def retry_with_backoff(func, max_retries=8, initial_wait=3, backoff_factor=1.2):
+def retry_with_backoff(func):
retries = 0
- wait_time = initial_wait
- while retries < max_retries:
+ wait_time = config['initial_wait']
+ while retries < config['max_retries']:
try:
result = func()
- # print(f"[AllTalk MEM] Operation successful on attempt {retries + 1}")
+ # print(f"[AllTalk MEM] Operation successful on attempt {retries + 1}")
return result
except RequestException as e:
retries += 1
- # print(f"[AllTalk MEM] Attempt {retries}")
- if retries == max_retries:
- # print(f"[AllTalk MEM] All {max_retries} attempts failed waiting for this instance of the Engine to load.")
+ # print(f"[AllTalk MEM] Attempt {retries}")
+ if retries == config['max_retries']:
+ # print(f"[AllTalk MEM] All {max_retries} attempts failed waiting for this instance of the Engine to load.")
return False
- # print(f"[AllTalk MEM] Retrying in {wait_time} seconds...")
+ # print(f"[AllTalk MEM] Retrying in {wait_time} seconds...")
time.sleep(wait_time)
- wait_time = round(wait_time * backoff_factor, 1) # Round to 1 decimal place
+ wait_time = round(wait_time * config['backoff_factor'], 1)
return False
def is_server_ready(port, timeout=5):
@@ -236,18 +279,24 @@ def create_gradio_interface():
with gr.Row():
for j in range(8):
if i + j < max_instances:
+ instance_id = i + j + 1
+
+ # Use a column for each engine instance to control layout
with gr.Column(scale=1, min_width=160):
- instance_id = i + j + 1
gr.Markdown(f"### Engine Instance {instance_id}")
status = gr.Markdown("โ Not running")
- start_btn = gr.Button("Start")
- stop_btn = gr.Button("Stop")
- restart_btn = gr.Button("Restart")
+ with gr.Row():
+ start_btn = gr.Button("โถ๏ธ Start", size="sm", scale=1, min_width=10)
+ with gr.Row():
+ stop_btn = gr.Button("โน๏ธ Stop", size="sm", scale=1, min_width=10)
+ restart_btn = gr.Button("โป๏ธ Re-St", size="sm", scale=1, min_width=10)
+
+ # Define click behavior for each button
start_btn.click(lambda id=instance_id: start_and_update(id, current_base_port + id - 1), outputs=status)
stop_btn.click(lambda id=instance_id: stop_and_update(id), outputs=status)
restart_btn.click(lambda id=instance_id: restart_and_update(id, current_base_port + id - 1), outputs=status)
-
+
instance_statuses.append(status)
def refresh_and_update_slider():
@@ -357,33 +406,156 @@ def create_gradio_interface():
with gr.Tab("MEM Settings"):
with gr.Row():
- base_port_input = gr.Number(
- value=current_base_port,
- label=f"Starting Port Number (Current: {current_base_port})",
- step=1
- )
+ with gr.Column(scale=2):
+ gr.Markdown("### Engine Instance Management")
+ with gr.Row():
+ base_port_input = gr.Number(
+ value=config['base_port'],
+ label=f"Starting Port Number (Current: {config['base_port']})",
+ step=1
+ )
+ max_instances_input = gr.Number(
+ value=config['max_instances'],
+ label=f"Maximum Engine Instances (Current: {config['max_instances']})",
+ step=1
+ )
+ auto_start_engines = gr.Number(
+ value=config['auto_start_engines'],
+ label="Auto-start Engine Instances (0 to disable)",
+ step=1
+ )
- set_port_button = gr.Button("Set Start Port Number")
+ with gr.Column(scale=1):
+ gr.Markdown("### Gradio Port & Debugging")
+ with gr.Row():
+ gradio_port_input = gr.Number(
+ value=config['gradio_interface_port'],
+ label=f"Gradio Interface Port (Current: {config['gradio_interface_port']})",
+ step=1
+ )
+ debug_mode_input = gr.Dropdown(
+ choices=["Disabled", "Enabled"],
+ value="Enabled" if config['debug_mode'] else "Disabled",
+ label="Debug Mode"
+ )
+
+ with gr.Row():
+ with gr.Column(scale=2):
+ gr.Markdown("### Engine Start-up Time Contol")
+ with gr.Row():
+ max_retries_input = gr.Number(
+ value=config['max_retries'],
+ label=f"Max Retries (Current: {config['max_retries']})",
+ step=1
+ )
+ initial_wait_input = gr.Number(
+ value=config['initial_wait'],
+ label=f"Initial Wait (Current: {config['initial_wait']})",
+ step=0.1
+ )
+ backoff_factor_input = gr.Number(
+ value=config['backoff_factor'],
+ label=f"Backoff Factor (Current: {config['backoff_factor']})",
+ step=0.1
+ )
+ with gr.Column(scale=1):
+ gr.Markdown("### Save Settings")
+ with gr.Row():
+ set_settings_button = gr.Button("Save Settings")
+ with gr.Row():
+ settings_status = gr.Textbox(label="Settings Status")
+
+
+ def update_settings(new_port, new_max_instances, new_auto_start, new_gradio_port,
+ new_max_retries, new_initial_wait, new_backoff_factor, new_debug_mode):
+ global current_base_port, max_instances, config, server_port
- def set_base_port(new_port):
- global current_base_port
if new_port < 1024:
- current_base_port = 7001
- return (
- f"Invalid port. Set to default: {current_base_port}",
- gr.update(value=current_base_port, label=f"Starting Port Number (Current: {current_base_port})")
- )
+ new_port = 7001
+
+ new_auto_start = min(int(new_auto_start), int(new_max_instances))
+
current_base_port = int(new_port)
+ max_instances = int(new_max_instances)
+ server_port = int(new_gradio_port)
+
+ config['base_port'] = current_base_port
+ config['max_instances'] = max_instances
+ config['auto_start_engines'] = new_auto_start
+ config['gradio_interface_port'] = server_port
+ config['max_retries'] = int(new_max_retries)
+ config['initial_wait'] = float(new_initial_wait)
+ config['backoff_factor'] = float(new_backoff_factor)
+ config['debug_mode'] = (new_debug_mode == "Enabled")
+
+ save_config(config)
+
return (
- f"Start port set to: {current_base_port}",
- gr.update(value=current_base_port, label=f"Starting Port Number (Current: {current_base_port})")
+ f"Settings updated: Start port: {current_base_port}, Max instances: {max_instances}, "
+ f"Auto-start engines: {config['auto_start_engines']}, Gradio port: {server_port}, "
+ f"Max retries: {config['max_retries']}, Initial wait: {config['initial_wait']}, "
+ f"Backoff factor: {config['backoff_factor']}, Debug mode: {config['debug_mode']}",
+ gr.update(value=current_base_port, label=f"Starting Port Number (Current: {current_base_port})"),
+ gr.update(value=max_instances, label=f"Maximum Instances (Current: {max_instances})"),
+ gr.update(value=config['auto_start_engines']),
+ gr.update(value=server_port, label=f"Gradio Interface Port (Current: {server_port})"),
+ gr.update(value=config['max_retries']),
+ gr.update(value=config['initial_wait']),
+ gr.update(value=config['backoff_factor']),
+ gr.update(value="Enabled" if config['debug_mode'] else "Disabled")
)
- set_port_button.click(
- set_base_port,
- inputs=[base_port_input],
- outputs=[error_box, base_port_input]
+ set_settings_button.click(
+ update_settings,
+ inputs=[base_port_input, max_instances_input, auto_start_engines, gradio_port_input,
+ max_retries_input, initial_wait_input, backoff_factor_input, debug_mode_input],
+ outputs=[settings_status, base_port_input, max_instances_input, auto_start_engines,
+ gradio_port_input, max_retries_input, initial_wait_input, backoff_factor_input,
+ debug_mode_input]
)
+
+ with gr.Row():
+ gr.Markdown("""
+ All settings are saved in the `mem_config.json` file in the same directory as the MEM script. If `mem_config.json` doesn't exist, default settings will be used. Settings take effect immediately after saving. Some settings (like Gradio Interface Port) may require a restart of MEM to take effect.
+ """)
+ with gr.Row():
+ with gr.Column():
+ gr.Markdown("""
+ ### ๐ฉ Starting Port Number
+ **Default: 7001**
+ This is the base port number from which MEM starts assigning ports to TTS engine instances. Each subsequent instance will use the next available port number. Ensure this port and the following ones are free on your system.
+
+ ### ๐ฉ Maximum Instances
+ **Default: 8**
+ The maximum number of TTS engine instances that can be run simultaneously. This limit helps prevent system overload. Adjust based on your system's capabilities and requirements.
+
+ ### ๐ฉ Auto-start Engines
+ **Default: 0 (disabled)**
+ The number of TTS engine instances to automatically start when MEM launches. Set to 0 to disable auto-start. This number cannot exceed the Maximum Instances setting.
+
+ ### ๐ฉ Gradio Interface Port
+ **Default: 7500**
+ The port on which the Gradio web interface for MEM will be accessible. Make sure this port is free and not blocked by your firewall. Gradio will bind to all IP address on your machine on this port, aka 0.0.0.0.
+ """)
+
+ with gr.Column():
+ gr.Markdown("""
+ ### ๐ฉ Max Retries
+ **Default: 8**
+ The maximum number of attempts MEM will make to connect to a TTS engine instance before considering it failed. This setting helps handle slow-starting engines.
+
+ ### ๐ฉ Initial Wait
+ **Default: 3 (seconds)**
+ The initial waiting time between retry attempts (Max Retries) when connecting to a TTS engine instance. This delay helps prevent overwhelming the system with rapid reconnection attempts.
+
+ ### ๐ฉBackoff Factor
+ **Default: 1.2**
+ A multiplier applied to the waiting time between retry attempts. With each failed attempt, the wait time (Initial Wait) is multiplied by this factor, implementing an exponential backoff strategy. This helps to reduce system load during persistent issues.
+
+ ### ๐ฉ Debug Mode
+ **Default: Disabled**
+ When enabled, MEM will output additional diagnostic information. This is useful for troubleshooting issues but may increase console output significantly. Use this when you need detailed information about MEM's operations .
+ """)
with gr.Tab("MEM FAQ/Help"):
with gr.Row():
@@ -475,11 +647,69 @@ def restart_and_update(instance_id, port):
result = restart_subprocess(instance_id, port)
return check_subprocess_status(instance_id, port)
+def start_MEMple_instances(num_instances, base_port):
+ currently_running = count_running_instances()
+
+ if currently_running >= num_instances:
+ return update_all_statuses(base_port) # All requested instances are already running
+
+ instances_to_start = num_instances - currently_running
+ start_port, available_ports = find_available_port(base_port + currently_running, instances_to_start)
+
+ if len(available_ports) < instances_to_start:
+ return ["Not enough available ports"] * max_instances
+
+ results = update_all_statuses(base_port)
+ for i, port in enumerate(available_ports[:instances_to_start], start=currently_running+1):
+ result = start_subprocess(i, port)
+ results[i-1] = result
+ time.sleep(10) # Increase wait time to 10 seconds
+
+ return results
+
print(f"[AllTalk MEM] Please use \033[91mCtrl+C\033[0m when exiting otherwise Python")
print(f"[AllTalk MEM] subprocess's will continue running in the background.")
print(f"[AllTalk MEM] ")
print(f"[AllTalk MEM] MEM Server Ready")
+def launch_interface(interface):
+ interface.launch(quiet=True, server_port=config['gradio_interface_port'], prevent_thread_lock=True)
+
+def auto_start_engines():
+ num_engines = config['auto_start_engines']
+ if num_engines > 0:
+ print(f"[AllTalk MEM] Auto-starting {num_engines} engines...")
+ results = start_MEMple_instances(num_engines, current_base_port)
+ print(f"[AllTalk MEM] Auto-start complete. Results: {results}")
+
+def shutdown():
+ print("[AllTalk MEM] Shutting down all engines...")
+ stop_all_instances()
+ print("[AllTalk MEM] All engines stopped.")
+
if __name__ == "__main__":
+ # Set up signal handler
+ signal.signal(signal.SIGINT, signal_handler)
+
interface = create_gradio_interface()
- interface.launch(quiet=True, server_port=server_port)
\ No newline at end of file
+
+ # Start the Gradio interface in a separate thread
+ interface_thread = threading.Thread(target=launch_interface, args=(interface,))
+ interface_thread.start()
+
+ # Auto-start engines
+ auto_start_engines()
+
+ try:
+ # Main loop
+ while not should_exit.is_set():
+ should_exit.wait(1) # Wait for 1 second or until should_exit is set
+ except Exception as e:
+ print(f"[AllTalk MEM] An error occurred: {e}")
+ finally:
+ print("[AllTalk MEM] Initiating shutdown...")
+ shutdown()
+ # Give the interface thread a chance to close gracefully
+ interface_thread.join(timeout=5)
+ print("[AllTalk MEM] Shutdown complete.")
+ sys.exit(0)
\ No newline at end of file
From bb26cb8f28e79055a2bd02d679cd59f5c1771073 Mon Sep 17 00:00:00 2001
From: erew123 <35898566+erew123@users.noreply.github.com>
Date: Wed, 18 Sep 2024 01:09:20 +0100
Subject: [PATCH 35/38] MEM updated w/Queue system & Load Tester
Is there an in-built queue system to handle different requests to different loaded engines?
A: MEM incorporates a built-in queue system to manage multiple TTS requests across loaded engine instances:
All TTS requests are received through the API port (default: 7401).
The queue system distributes incoming requests among available TTS engine instances.
If all engines are busy, new requests are held in a queue until an engine becomes available.
The system continuously checks for available engines to process waiting requests.
If a request cannot be processed within the allocated time, it will be marked as failed.
This queue system aims to balance the load across all running engines efficiently. Advanced features for queue management and dynamic timeout calculations are available and can be configured in the MEM settings.
Note: As this is a research and testing implementation, its performance in high-load or production environments is not guaranteed. For load testing, you can use the provided mem_load_test.py script to simulate multiple simultaneous requests.
To use the load testing tool: `python mem_load_test.py --requests [number_of_requests] --length [text_length] --url "http://127.0.0.1:7501/api/tts-generate"`
---
mem_load_test.py | 81 ++++++++
tts_mem.py | 522 ++++++++++++++++++++++++++++++++++++++++-------
2 files changed, 525 insertions(+), 78 deletions(-)
create mode 100644 mem_load_test.py
diff --git a/mem_load_test.py b/mem_load_test.py
new file mode 100644
index 0000000..636c605
--- /dev/null
+++ b/mem_load_test.py
@@ -0,0 +1,81 @@
+import asyncio
+import aiohttp
+import time
+import argparse
+from concurrent.futures import ThreadPoolExecutor
+
+async def send_tts_request(session, url, text, request_id):
+ start_time = time.time()
+ try:
+ async with session.post(url, data={
+ "text_input": text,
+ "text_filtering": "standard",
+ "character_voice_gen": "en_US-ljspeech-high.onnx",
+ "narrator_enabled": "false",
+ "narrator_voice_gen": "en_US-ljspeech-high.onnx",
+ "text_not_inside": "character",
+ "language": "en",
+ "output_file_name": f"loadtest_{request_id}",
+ "output_file_timestamp": "true",
+ "autoplay": "false",
+ "autoplay_volume": "0.8"
+ }) as response:
+ result = await response.json()
+ end_time = time.time()
+ print(f"Request {request_id} completed in {end_time - start_time:.2f} seconds.")
+ print(f"Response: {result}")
+ return result
+ except Exception as e:
+ print(f"Request {request_id} failed: {str(e)}")
+ return None
+
+async def run_load_test(num_requests, text_length, api_url):
+ async with aiohttp.ClientSession() as session:
+ tasks = []
+ for i in range(num_requests):
+ text = f"This is a test of the MEM load balancing system. Request number {i+1}. " * (text_length // 10)
+ task = asyncio.ensure_future(send_tts_request(session, api_url, text, i+1))
+ tasks.append(task)
+
+ results = await asyncio.gather(*tasks)
+ return results
+
+def main():
+ parser = argparse.ArgumentParser(description="Load test for MEM TTS API")
+ parser.add_argument("--requests", type=int, default=5, help="Number of simultaneous requests to send")
+ parser.add_argument("--length", type=int, default=50, help="Approximate length of text for each request")
+ parser.add_argument("--url", type=str, default="http://127.0.0.1:7401/api/tts-generate", help="URL of the MEM API")
+ args = parser.parse_args()
+
+ print(f"AllTalk MEM load tester.")
+ print(f"\nYou can use this to load test MEM. You can specify:")
+ print(f"\n - the amount of simultaneous request to send --requests")
+ print(f" - the amount of character text per request --length")
+ print(f" - the URL to send the request to --url")
+ print(f"\nA full command line would look like this")
+ print(f"\npython mem_load_test.py --requests 3 --length 50 --url `http://127.0.0.1:7401/api/tts-generate`")
+ print(f"\nAll requests are sent simultaniously at the same time. As such the 'completed in' time is")
+ print(f"calculated from the time the requests were sent, not the actual TTS processing time per")
+ print(f"individual request within the TTS engine.")
+ print(f"\nStarting load test with {args.requests} simultaneous requests...")
+ print(f"Each request will have approximately {args.length} characters of text.")
+ print(f"Sending requests to: {args.url}\n")
+
+ start_time = time.time()
+
+ # Run the async function in a separate thread to avoid blocking
+ with ThreadPoolExecutor() as executor:
+ future = executor.submit(asyncio.run, run_load_test(args.requests, args.length, args.url))
+ results = future.result()
+
+ end_time = time.time()
+
+ successful_requests = sum(1 for result in results if result and result.get("status") == "generate-success")
+ failed_requests = sum(1 for result in results if result is None or result.get("status") != "generate-success")
+
+ print(f"\nLoad test completed in {end_time - start_time:.2f} seconds.")
+ print(f"Successful requests: {successful_requests}")
+ print(f"Failed requests: {failed_requests}")
+
+if __name__ == "__main__":
+ main()
\ No newline at end of file
diff --git a/tts_mem.py b/tts_mem.py
index 7e827b3..7ea7f77 100644
--- a/tts_mem.py
+++ b/tts_mem.py
@@ -9,6 +9,9 @@ import requests
from requests.exceptions import RequestException
import socket
import json
+import queue
+from flask import Flask, request, jsonify, send_from_directory
+from werkzeug.serving import make_server, WSGIRequestHandler
# Add these at the beginning of your file, after other imports
CONFIG_FILE = "mem_config.json"
@@ -27,13 +30,21 @@ def signal_handler(signum, frame):
# Default configuration
default_config = {
"base_port": 7001,
+ "api_server_port": 7401,
"auto_start_engines": 0,
"max_instances": 8,
"gradio_interface_port": 7500,
"max_retries": 8,
"initial_wait": 3,
"backoff_factor": 1.2,
- "debug_mode": False
+ "debug_mode": False,
+ "max_queue_time": 60, # Maximum time a request can wait in the queue (in seconds)
+ "queue_check_interval": 0.1, # Time between checks for available instances (in seconds)
+ "tts_request_timeout": 30, # Timeout for individual TTS requests (in seconds)
+ "text_length_factor": 0.2, # Increase timeout by 20% per 100 characters
+ "concurrent_request_factor": 0.5, # Increase timeout by 50% per concurrent request
+ "diminishing_factor": 0.5, # Reduce additional time for long-running requests by 50%
+ "queue_position_factor": 1.0 # Add 100% of base timeout for each queue position
}
def load_config():
@@ -72,6 +83,7 @@ print(f"[AllTalk MEM]")
print(f"[AllTalk MEM] \033[93m MEM is not intended for production use and\033[00m")
print(f"[AllTalk MEM] \033[93m there is NO support being offered on MEM\033[00m")
print(f"[AllTalk MEM]")
+print(f"[AllTalk MEM] \033[94mAPI/Queue :\033[00m \033[92mhttp://127.0.0.1:{config['api_server_port']}/api/tts-generate\033[00m")
print(f"[AllTalk MEM] \033[94mGradio Light:\033[00m \033[92mhttp://127.0.0.1:{config['gradio_interface_port']}\033[00m")
print(f"[AllTalk MEM] \033[94mGradio Dark :\033[00m \033[92mhttp://127.0.0.1:{config['gradio_interface_port']}?__theme=dark\033[00m")
print(f"[AllTalk MEM]")
@@ -189,7 +201,8 @@ def stop_all_instances():
return results
def update_all_statuses(base_port):
- return [check_subprocess_status(i, base_port + i - 1) for i in range(1, max_instances + 1)]
+ return [check_subprocess_status(i, base_port + i - 1) for i in range(1, max_instances + 1)]
+
def count_running_instances():
return sum(1 for p in processes.values() if p.poll() is None)
@@ -249,7 +262,7 @@ def test_tts(engine, voice, text):
return None, f"Error in test_tts: {str(e)}"
def create_gradio_interface():
- global current_base_port
+ global current_base_port, max_instances
with gr.Blocks(title="AllTalk Multi Engine Manager", theme=gr.themes.Base()) as interface:
with gr.Row():
gr.Markdown("# AllTalk Multi Engine Manager")
@@ -407,7 +420,7 @@ def create_gradio_interface():
with gr.Tab("MEM Settings"):
with gr.Row():
with gr.Column(scale=2):
- gr.Markdown("### Engine Instance Management")
+ gr.Markdown("### ๐ฆ Engine Instance Management")
with gr.Row():
base_port_input = gr.Number(
value=config['base_port'],
@@ -426,22 +439,22 @@ def create_gradio_interface():
)
with gr.Column(scale=1):
- gr.Markdown("### Gradio Port & Debugging")
+ gr.Markdown("### ๐จ Gradio Port & API Port")
with gr.Row():
gradio_port_input = gr.Number(
value=config['gradio_interface_port'],
label=f"Gradio Interface Port (Current: {config['gradio_interface_port']})",
step=1
- )
- debug_mode_input = gr.Dropdown(
- choices=["Disabled", "Enabled"],
- value="Enabled" if config['debug_mode'] else "Disabled",
- label="Debug Mode"
)
+ api_server_port_input = gr.Number(
+ value=config['api_server_port'],
+ label=f"API Server Port (Current: {config['api_server_port']})",
+ step=1
+ )
with gr.Row():
with gr.Column(scale=2):
- gr.Markdown("### Engine Start-up Time Contol")
+ gr.Markdown("### ๐ฅ Engine Start-up Time Contol")
with gr.Row():
max_retries_input = gr.Number(
value=config['max_retries'],
@@ -459,15 +472,67 @@ def create_gradio_interface():
step=0.1
)
with gr.Column(scale=1):
- gr.Markdown("### Save Settings")
+ gr.Markdown("### ๐ช Debug Settings")
with gr.Row():
- set_settings_button = gr.Button("Save Settings")
- with gr.Row():
- settings_status = gr.Textbox(label="Settings Status")
+ debug_mode_input = gr.Dropdown(
+ choices=["Disabled", "Enabled"],
+ value="Enabled" if config['debug_mode'] else "Disabled",
+ label="Debug Mode"
+ )
+ with gr.Row():
+ with gr.Column():
+ gr.Markdown("### ๐ฉ Core Queue Management")
+ with gr.Row():
+ max_queue_time_input = gr.Number(
+ value=config['max_queue_time'],
+ label=f"Max Queue Time (seconds) (Current: {config['max_queue_time']})",
+ step=1
+ )
+ queue_check_interval_input = gr.Number(
+ value=config['queue_check_interval'],
+ label=f"Queue Check Interval (seconds) (Current: {config['queue_check_interval']})",
+ step=0.1
+ )
+ tts_request_timeout_input = gr.Number(
+ value=config['tts_request_timeout'],
+ label=f"TTS Request Timeout (seconds) (Current: {config['tts_request_timeout']})",
+ step=1
+ )
+ with gr.Column():
+ gr.Markdown("### ๐ฉ Dynamic Timeout Factors")
+ with gr.Row():
+ text_length_factor_input = gr.Number(
+ value=config['text_length_factor'],
+ label=f"Text Length Factor (Current: {config['text_length_factor']})",
+ step=0.1
+ )
+ concurrent_request_factor_input = gr.Number(
+ value=config['concurrent_request_factor'],
+ label=f"Concurrent Request Factor (Current: {config['concurrent_request_factor']})",
+ step=0.1
+ )
+ diminishing_factor_input = gr.Number(
+ value=config['diminishing_factor'],
+ label=f"Diminishing Factor (Current: {config['diminishing_factor']})",
+ step=0.1
+ )
+ queue_position_factor_input = gr.Number(
+ value=config['queue_position_factor'],
+ label=f"Queue Position Factor (Current: {config['queue_position_factor']})",
+ step=0.1
+ )
+
+ with gr.Row():
+ settings_status = gr.Textbox(label="Settings Status", scale=3)
+ set_settings_button = gr.Button("Save Settings", scale=1)
def update_settings(new_port, new_max_instances, new_auto_start, new_gradio_port,
- new_max_retries, new_initial_wait, new_backoff_factor, new_debug_mode):
+ new_api_server_port, new_max_retries, new_initial_wait,
+ new_backoff_factor, new_debug_mode, new_max_queue_time,
+ new_queue_check_interval, new_tts_request_timeout,
+ new_text_length_factor, new_concurrent_request_factor,
+ new_diminishing_factor, new_queue_position_factor):
global current_base_port, max_instances, config, server_port
if new_port < 1024:
@@ -483,79 +548,194 @@ def create_gradio_interface():
config['max_instances'] = max_instances
config['auto_start_engines'] = new_auto_start
config['gradio_interface_port'] = server_port
+ config['api_server_port'] = int(new_api_server_port)
config['max_retries'] = int(new_max_retries)
config['initial_wait'] = float(new_initial_wait)
config['backoff_factor'] = float(new_backoff_factor)
config['debug_mode'] = (new_debug_mode == "Enabled")
+ config['max_queue_time'] = int(new_max_queue_time)
+ config['queue_check_interval'] = float(new_queue_check_interval)
+ config['tts_request_timeout'] = int(new_tts_request_timeout)
+ config['text_length_factor'] = float(new_text_length_factor)
+ config['concurrent_request_factor'] = float(new_concurrent_request_factor)
+ config['diminishing_factor'] = float(new_diminishing_factor)
+ config['queue_position_factor'] = float(new_queue_position_factor)
save_config(config)
- return (
+ status_message = (
f"Settings updated: Start port: {current_base_port}, Max instances: {max_instances}, "
f"Auto-start engines: {config['auto_start_engines']}, Gradio port: {server_port}, "
+ f"API Server Port: {config['api_server_port']}, "
f"Max retries: {config['max_retries']}, Initial wait: {config['initial_wait']}, "
- f"Backoff factor: {config['backoff_factor']}, Debug mode: {config['debug_mode']}",
+ f"Backoff factor: {config['backoff_factor']}, Debug mode: {config['debug_mode']}, "
+ f"Max queue time: {config['max_queue_time']}, "
+ f"Queue check interval: {config['queue_check_interval']}, "
+ f"TTS request timeout: {config['tts_request_timeout']}, "
+ f"Text length factor: {config['text_length_factor']}, "
+ f"Concurrent request factor: {config['concurrent_request_factor']}, "
+ f"Diminishing factor: {config['diminishing_factor']}, "
+ f"Queue position factor: {config['queue_position_factor']}"
+ )
+
+ return (
+ status_message,
gr.update(value=current_base_port, label=f"Starting Port Number (Current: {current_base_port})"),
gr.update(value=max_instances, label=f"Maximum Instances (Current: {max_instances})"),
gr.update(value=config['auto_start_engines']),
gr.update(value=server_port, label=f"Gradio Interface Port (Current: {server_port})"),
+ gr.update(value=config['api_server_port'], label=f"API Server Port (Current: {config['api_server_port']})"),
gr.update(value=config['max_retries']),
gr.update(value=config['initial_wait']),
gr.update(value=config['backoff_factor']),
- gr.update(value="Enabled" if config['debug_mode'] else "Disabled")
+ gr.update(value="Enabled" if config['debug_mode'] else "Disabled"),
+ gr.update(value=config['max_queue_time'], label=f"Max Queue Time (seconds) (Current: {config['max_queue_time']})"),
+ gr.update(value=config['queue_check_interval'], label=f"Queue Check Interval (seconds) (Current: {config['queue_check_interval']})"),
+ gr.update(value=config['tts_request_timeout'], label=f"TTS Request Timeout (seconds) (Current: {config['tts_request_timeout']})"),
+ gr.update(value=config['text_length_factor'], label=f"Text Length Factor (Current: {config['text_length_factor']})"),
+ gr.update(value=config['concurrent_request_factor'], label=f"Concurrent Request Factor (Current: {config['concurrent_request_factor']})"),
+ gr.update(value=config['diminishing_factor'], label=f"Diminishing Factor (Current: {config['diminishing_factor']})"),
+ gr.update(value=config['queue_position_factor'], label=f"Queue Position Factor (Current: {config['queue_position_factor']})")
)
set_settings_button.click(
update_settings,
inputs=[base_port_input, max_instances_input, auto_start_engines, gradio_port_input,
- max_retries_input, initial_wait_input, backoff_factor_input, debug_mode_input],
+ api_server_port_input, max_retries_input, initial_wait_input, backoff_factor_input,
+ debug_mode_input, max_queue_time_input, queue_check_interval_input,
+ tts_request_timeout_input, text_length_factor_input, concurrent_request_factor_input,
+ diminishing_factor_input, queue_position_factor_input],
outputs=[settings_status, base_port_input, max_instances_input, auto_start_engines,
- gradio_port_input, max_retries_input, initial_wait_input, backoff_factor_input,
- debug_mode_input]
+ gradio_port_input, api_server_port_input, max_retries_input, initial_wait_input,
+ backoff_factor_input, debug_mode_input, max_queue_time_input, queue_check_interval_input,
+ tts_request_timeout_input, text_length_factor_input, concurrent_request_factor_input,
+ diminishing_factor_input, queue_position_factor_input]
)
with gr.Row():
- gr.Markdown("""
- All settings are saved in the `mem_config.json` file in the same directory as the MEM script. If `mem_config.json` doesn't exist, default settings will be used. Settings take effect immediately after saving. Some settings (like Gradio Interface Port) may require a restart of MEM to take effect.
- """)
- with gr.Row():
+ gr.Markdown("### ๐ Settings Help")
+ with gr.Tab("Engine Instance Management"):
+ with gr.Row():
+ with gr.Column():
+ gr.Markdown("""
+ These settings control the initialization, quantity, and port assignment of TTS engine instances within MEM. They determine how many engines can run simultaneously and how network ports are allocated to them.
+
+ ### ๐ฆ Starting Port Number
+ **Default: 7001**
+ The base port number for TTS engine instances. Each instance uses the next available port. Ensure these ports are free on your system.
+
+ ### ๐ฆ Maximum Instances
+ **Default: 8**
+ The maximum number of simultaneous TTS engine instances. Adjust based on your system's capabilities to prevent overload.
+
+ ### ๐ฆ Auto-start Engines
+ **Default: 0 (disabled)**
+ Number of TTS engine instances to start automatically on MEM launch. Cannot exceed Maximum Instances.
+ """)
+ with gr.Tab("Gradio Port & API Port"):
+ with gr.Row():
+ with gr.Column():
+ gr.Markdown("""
+ These settings define the network ports for user interface access and API communication. They are crucial for ensuring that users can interact with MEM and that other applications can send requests to MEM.
+
+ ### ๐จ Gradio Interface Port
+ **Default: 7500**
+ Port for the Gradio web interface. MEM binds to 0.0.0.0 on this port.
+
+ ### ๐จ API Server Port
+ **Default: 7401**
+ Port for incoming TTS requests to the MEM API. Ensure it's free and not firewalled.
+ """)
+ with gr.Tab("Engine Start-up Time Contol"):
+ with gr.Row():
+ with gr.Column():
+ gr.Markdown("""
+ These settings manage the initialization process and error handling for individual TTS engine instances. They control how MEM attempts to start each engine, how long it waits between attempts, and how it handles potential failures during the start-up process. These settings are particularly relevant when starting multiple TTS engines simultaneously or during automatic start-up.
+
+ ### ๐ฅ Max Retries
+ **Default: 8**
+ Maximum number of attempts MEM will make to connect to a TTS engine before marking it as failed. This helps handle slow-starting engines or temporary network issues.
+
+ ### ๐ฅ Initial Wait
+ **Default: 3 (seconds)**
+ Initial delay between retry attempts when connecting to a TTS engine. This gives the engine time to initialize before MEM attempts to connect again.
+
+ ### ๐ฅ Backoff Factor
+ **Default: 1.2**
+ Multiplier for wait time between retries, implementing an exponential backoff strategy. This reduces system load during persistent issues by increasing the wait time between each retry attempt.
+ """)
+ with gr.Tab("Debug Settings"):
+ with gr.Row():
+ with gr.Column():
+ gr.Markdown("""
+ **Default: Disabled**
+ When enabled, MEM outputs additional diagnostic information. Useful for troubleshooting but increases console output.
+
+ Note: All settings are saved in `mem_config.json` in the MEM script directory. Default settings are used if the file doesn't exist. Most settings take effect immediately, but some (like port changes) may require a MEM restart.
+ """)
+ with gr.Tab("Core Queue Management"):
with gr.Column():
gr.Markdown("""
- ### ๐ฉ Starting Port Number
- **Default: 7001**
- This is the base port number from which MEM starts assigning ports to TTS engine instances. Each subsequent instance will use the next available port number. Ensure this port and the following ones are free on your system.
+ This section defines the fundamental behavior of the request queue system. These settings control how long requests can wait, how often MEM checks for available engines, and the base timeout for TTS requests. They work together to manage the flow of requests through the system.
- ### ๐ฉ Maximum Instances
- **Default: 8**
- The maximum number of TTS engine instances that can be run simultaneously. This limit helps prevent system overload. Adjust based on your system's capabilities and requirements.
+ ### ๐ฉ Max Queue Time
+ **Default: 60 seconds**
+ Maximum time a request can wait before being processed or rejected. Prevents indefinite waiting.
- ### ๐ฉ Auto-start Engines
- **Default: 0 (disabled)**
- The number of TTS engine instances to automatically start when MEM launches. Set to 0 to disable auto-start. This number cannot exceed the Maximum Instances setting.
+ ### ๐ฉ Queue Check Interval
+ **Default: 0.1 seconds**
+ Frequency of checking for available TTS engines. Lower values increase responsiveness but may increase CPU usage.
- ### ๐ฉ Gradio Interface Port
- **Default: 7500**
- The port on which the Gradio web interface for MEM will be accessible. Make sure this port is free and not blocked by your firewall. Gradio will bind to all IP address on your machine on this port, aka 0.0.0.0.
- """)
-
- with gr.Column():
- gr.Markdown("""
- ### ๐ฉ Max Retries
- **Default: 8**
- The maximum number of attempts MEM will make to connect to a TTS engine instance before considering it failed. This setting helps handle slow-starting engines.
+ ### ๐ฉ TTS Request Timeout
+ **Default: 30 seconds**
+ Base maximum processing time for a single TTS request. This value serves as the foundation for the Dynamic Timeout Factors. The actual timeout for each request is calculated by applying the Dynamic Timeout Factors to this base value. For a detailed explanation of how this base timeout is adjusted, please refer to the "Dynamic Timeout Factors" section in the settings help.
- ### ๐ฉ Initial Wait
- **Default: 3 (seconds)**
- The initial waiting time between retry attempts (Max Retries) when connecting to a TTS engine instance. This delay helps prevent overwhelming the system with rapid reconnection attempts.
-
- ### ๐ฉBackoff Factor
- **Default: 1.2**
- A multiplier applied to the waiting time between retry attempts. With each failed attempt, the wait time (Initial Wait) is multiplied by this factor, implementing an exponential backoff strategy. This helps to reduce system load during persistent issues.
-
- ### ๐ฉ Debug Mode
- **Default: Disabled**
- When enabled, MEM will output additional diagnostic information. This is useful for troubleshooting issues but may increase console output significantly. Use this when you need detailed information about MEM's operations .
+ These settings work together to balance system responsiveness and resource usage. Adjust Max Queue Time for overall request lifespan, Queue Check Interval for system reactivity, and TTS Request Timeout as a baseline for individual request processing. The TTS Request Timeout is particularly important as it forms the basis for all dynamic timeout calculations.
""")
+ with gr.Tab("Dynamic Timeout Factors"):
+ with gr.Column():
+ gr.Markdown("""
+ These advanced settings allow adaptive adjustment of request timeouts based on various factors such as text length, system load, and queue position. They all work to modify the base TTS Request Timeout, creating a flexible timeout system that can adapt to changing conditions and ensure fair processing of requests.
+
+ ### ๐ฉ Text Length Factor
+ **Default: 0.2**
+ Increases the base TTS Request Timeout by this percentage for every 100 characters of text. For example, with a base timeout of 30 seconds and a 200-character text, the adjusted timeout would be 30 * (1 + (0.2 * 2)) = 42 seconds.
+
+ ### ๐ฉ Concurrent Request Factor
+ **Default: 0.5**
+ Increases the base TTS Request Timeout by this percentage for each concurrent request. For instance, if there are 2 concurrent requests, the timeout for each would be increased by 50% (0.5 * 2 = 1, so 30 * (1 + 1) = 60 seconds).
+
+ ### ๐ฉ Diminishing Factor
+ **Default: 0.5**
+ Reduces the additional time given to long-running requests. This factor is applied to the extra time calculated from other factors. For example, if other factors have increased the timeout by 20 seconds, this might be reduced to 10 seconds (20 * 0.5) for a long-running request.
+
+ ### ๐ฉ Queue Position Factor
+ **Default: 1.0**
+ Adds this percentage of the base TTS Request Timeout for each position in the queue. For a request that's second in the queue, this would add 100% of the base timeout (e.g., 30 + 30 = 60 seconds).
+
+ These factors work together to create a flexible timeout system:
+ 1. The base TTS Request Timeout is first adjusted by the Text Length Factor and Concurrent Request Factor.
+ 2. The Queue Position Factor then adds additional time based on the request's position.
+ 3. For requests that have been processing for a while, the Diminishing Factor reduces the extra time added by other factors.
+ 4. The final calculated timeout is capped at the Max Queue Time to prevent excessive wait times.
+
+ Example calculation:
+ - Base TTS Request Timeout: 30 seconds
+ - Text length: 300 characters
+ - Concurrent requests: 2
+ - Queue position: 3
+ - Request has been processing for a while
+
+ Calculation:
+ 1. Text Length adjustment: 30 * (1 + (0.2 * 3)) = 48 seconds
+ 2. Concurrent Request adjustment: 48 * (1 + (0.5 * 2)) = 96 seconds
+ 3. Queue Position adjustment: 96 + (30 * 1.0 * 3) = 186 seconds
+ 4. Diminishing Factor (assuming it reduces extra time by half): 30 + ((186 - 30) * 0.5) = 108 seconds
+
+ Final timeout would be 108 seconds, unless this exceeds the Max Queue Time, in which case Max Queue Time would be used instead.
+
+ This dynamic system allows for flexible handling of various request scenarios while still respecting overall system limits.
+ """)
with gr.Tab("MEM FAQ/Help"):
with gr.Row():
@@ -575,34 +755,47 @@ def create_gradio_interface():
## Queue System
**Q: Is there an in-built queue system to handle different requests to different loaded engines?**
- A: Not currently. AllTalk MEM is presently a demonstration and research tool without an integrated queue system. You are welcome to build your own and test it out.
+ A: MEM incorporates a built-in queue system to manage multiple TTS requests across loaded engine instances:
+ 1. All TTS requests are received through the API port (default: 7401).
+ 2. The queue system distributes incoming requests among available TTS engine instances.
+ 3. If all engines are busy, new requests are held in a queue until an engine becomes available.
+ 4. The system continuously checks for available engines to process waiting requests.
+ 5. If a request cannot be processed within the allocated time, it will be marked as failed.
+
+ This queue system aims to balance the load across all running engines efficiently. Advanced features for queue management and dynamic timeout calculations are available and can be configured in the MEM settings.
+
+ Note: As this is a research and testing implementation, its performance in high-load or production environments is not guaranteed. For load testing, you can use the provided `mem_load_test.py` script to simulate multiple simultaneous requests.
+
+ To use the load testing tool:
+ `python mem_load_test.py --requests [number_of_requests] --length [text_length] --url "http://127.0.0.1:7501/api/tts-generate"`
+
+ ## API Requests to each loaded engine
+ **Q: Can I use the AllTalk API to each engine instance?**
+ A: Yes, each engine loaded will respond fully as if it was a standalone AllTalk instance. So you can use the standard API requests to the port number of each TTS engine loaded in.
+
+ ## What happens if it tries to load an engine thats on a port already in use
+ **Q: When a engine instance starts up, what happens if a port is already used by something else?**
+ A: A test is performed of the port before starting each instance, so the engine trying to load on that port number wouldnt load in.
+
+ """)
+ with gr.Column():
+ gr.Markdown("""
## Handling Multiple Requests
**Q: How will different TTS engines from different manufacturers OR how will CUDA/Python handle multiple requests coming in at the same time for TTS generation?**
A: The behavior in such scenarios is currently unknown and is part of the research aspect of this tool. Its highly possible some TTS engines may respond in a timely fashion and its also possible they may not. I cannot say how things like CUDA/Python will handle multiple requests to the same hadware.
CUDA has additional code and tools to deal with multiplexing requests and none of these have been implimented. So for example, if you are using a TTS engine that uses a GPU, and 2x different loaded TTS engines make a request of that hardware, you may get a 50/50 spit use of the hardware and you may not. 3x requests, you may get a 33/33/33 hardware load sharing sceanario nad you may not. I cannot say at the moment and havnt tested.
- Python - As each instance of each TTS engine will be loaded into a seperate Python instance, all memory management and requests should remain segregated from one another, so there should be no bleed over of tensors in CUDA requests etc, however, I have not tested.
-
- ## API Requests to each loaded engine
- **Q: Can I use the AllTalk API to each engine instance?**
- A: Yes, each engine loaded will respond fully as if it was a standalone AllTalk instance. So you can use the standard API requests to the port number of each TTS engine loaded in.
-
- """)
- with gr.Column():
- gr.Markdown("""
+ Python - As each instance of each TTS engine will be loaded into a seperate Python instance, all memory management and requests should remain segregated from one another, so there should be no bleed over of tensors in CUDA requests etc, however, I have not tested.
+
## What settings are used for the TTS engines loaded
**Q: What exact settings are being used by the TTS engines being loaded in?**
A: Whatever you have set in the main AllTalk Gradio interface when you load AllTalk as a standalone, under the `TTS Engine Settings` and the specific TTS engine, those are the settings loaded/used.
## How can I change which TTS engine is loaded
**Q: How can you change the TTS engines being loaded?**
- A: Whatever you have set in the main AllTalk Gradio interface when you load AllTalk as a standalone, that will be the engine loaded in.
-
- ## What happens if it tries to load on a port in use
- **Q: When a engine instance starts up, what happens if a port is already used by something else?**
- A: A test is performed of the port before starting each instance, so the engine trying to load on that port number wouldnt load in.
+ A: Whatever you have set in the main AllTalk Gradio interface when you load AllTalk as a standalone, that will be the engine loaded in.
## Future Updates
**Q: Will you be updating this in the future?**
@@ -616,10 +809,7 @@ def create_gradio_interface():
If time permits, potential future developments include:
- Potential control over each TTS engines's settings and manipulating the JSON files that control AllTalk, so as to handle which engine loads and its settings from the MEM interface.
- - Storing settings in a configuration JSON file (e.g., start port number, number of engines to start, automatic start of engines on script start)
- - Implementing an incoming queue management system to handle multiple requests and distribute them among loaded engines
- Developing a basic API management suite for MEM
- - Refining and optimizing the existing logi for model loading etc
Please note that these are potential plans and not guaranteed features.
@@ -635,10 +825,12 @@ def start_and_update(instance_id, port):
if is_port_in_use(port):
return f"Port {port} is already in use"
result = start_subprocess(instance_id, port)
+ update_tts_instances()
return check_subprocess_status(instance_id, port)
def stop_and_update(instance_id):
result = stop_subprocess(instance_id)
+ update_tts_instances()
return "โ Not running"
def restart_and_update(instance_id, port):
@@ -667,6 +859,174 @@ def start_MEMple_instances(num_instances, base_port):
return results
+###############
+## WEBSERVER ##
+###############
+
+app = Flask(__name__)
+
+# Queue for holding TTS requests
+request_queue = queue.Queue()
+
+# Lock for thread-safe operations
+lock = threading.Lock()
+
+# Dictionary to keep track of TTS instance status
+tts_instances = {}
+
+# Maximum wait time for a request (in seconds)
+MAX_WAIT_TIME = 60
+
+def initialize_instances():
+ global tts_instances
+ with lock:
+ tts_instances.clear()
+ for i in range(1, max_instances + 1):
+ port = current_base_port + i - 1
+ tts_instances[i] = {"port": port, "locked": False}
+
+def get_available_instance():
+ with lock:
+ for instance, info in tts_instances.items():
+ if not info["locked"] and is_instance_active(instance):
+ info["locked"] = True
+ return instance
+ return None
+
+def is_instance_active(instance):
+ port = tts_instances[instance]["port"]
+ try:
+ response = requests.get(f"http://127.0.0.1:{port}/api/ready", timeout=1)
+ return response.text.strip() == "Ready"
+ except:
+ return False
+
+def release_instance(instance):
+ with lock:
+ if instance in tts_instances:
+ tts_instances[instance]["locked"] = False
+
+def update_tts_instances():
+ global tts_instances
+ with lock:
+ tts_instances.clear()
+ for i, process in processes.items():
+ if process.poll() is None: # Check if the process is running
+ port = current_base_port + i - 1
+ tts_instances[i] = {"port": port, "locked": False}
+
+def calculate_dynamic_timeout(text, concurrent_requests, queue_position, total_queue_length, request_start_time):
+ base_timeout = config['tts_request_timeout'] * (1 + (concurrent_requests * 0.5))
+
+ text_length_factor = len(text) / 100
+ adjusted_timeout = base_timeout * (1 + (text_length_factor * 0.2))
+
+ time_in_process = time.time() - request_start_time
+ diminishing_factor = max(0, 1 - (time_in_process / base_timeout))
+ timeout_with_diminishing = adjusted_timeout * (1 + (diminishing_factor * 0.5))
+
+ queue_position_factor = queue_position / total_queue_length
+ final_timeout = timeout_with_diminishing + (config['tts_request_timeout'] * queue_position_factor)
+
+ return min(final_timeout, config['max_queue_time']) # Ensure we don't exceed max queue time
+
+@app.route('/api/tts-generate', methods=['POST'])
+def tts_generate():
+ print("[AllTalk MEM] Received TTS generate request") if config['debug_mode'] else None
+ start_time = time.time()
+ request_data = request.form.to_dict()
+
+ # Add request to queue
+ request_queue.put(request_data)
+ queue_position = request_queue.qsize()
+
+ while time.time() - start_time < config['max_queue_time']:
+ instance = get_available_instance()
+ if instance:
+ try:
+ print(f"[AllTalk MEM] Processing request with instance {instance}") if config['debug_mode'] else None
+
+ # Calculate dynamic timeout just before processing
+ dynamic_timeout = calculate_dynamic_timeout(
+ text=request_data['text_input'],
+ concurrent_requests=len([i for i in tts_instances.values() if i['locked']]),
+ queue_position=queue_position,
+ total_queue_length=request_queue.qsize(),
+ request_start_time=start_time
+ )
+
+ result = process_tts_request(instance, request_data, dynamic_timeout)
+ print(f"[AllTalk MEM] Request processed, result: {result}") if config['debug_mode'] else None
+
+ # Remove request from queue
+ request_queue.get()
+
+ return jsonify(result)
+ except Exception as e:
+ print(f"[AllTalk MEM] Error processing request: {str(e)}")
+ release_instance(instance)
+ return jsonify({"status": "error", "message": str(e)})
+ time.sleep(config['queue_check_interval'])
+
+ # Remove request from queue if it times out
+ request_queue.get()
+
+ print("[AllTalk MEM] No instance available within timeout period")
+ return jsonify({"status": "error", "message": "No TTS instance available within the maximum wait time"})
+
+def process_tts_request(instance, data, timeout):
+ port = tts_instances[instance]["port"]
+ try:
+ response = requests.post(f"http://127.0.0.1:{port}/api/tts-generate", data=data, timeout=timeout)
+ return response.json()
+ except Exception as e:
+ return {"status": "error", "message": str(e)}
+ finally:
+ release_instance(instance)
+
+@app.route('/audio/')
+def serve_audio(filename):
+ return send_from_directory(app.config['OUTPUT_FOLDER'], filename)
+
+# Make sure to update the path to match your actual output directory
+app.config['OUTPUT_FOLDER'] = 'E:\\installtest\\alltalk_tts\\outputs'
+
+class SilentWSGIRequestHandler(WSGIRequestHandler):
+ def log_request(self, *args, **kwargs):
+ pass
+
+class ServerThread(threading.Thread):
+ def __init__(self, app):
+ threading.Thread.__init__(self)
+ self.server = make_server('0.0.0.0', config['api_server_port'], app, threaded=True, request_handler=SilentWSGIRequestHandler)
+ self.ctx = app.app_context()
+ self.ctx.push()
+
+ def run(self):
+ print(f"[AllTalk MEM] Starting API server on port {config['api_server_port']}") if config['debug_mode'] else None
+ self.server.serve_forever()
+
+ def shutdown(self):
+ self.server.shutdown()
+
+def start_api_server():
+ global server_thread
+ server_thread = ServerThread(app)
+ server_thread.start()
+ print(f"[AllTalk MEM] API server thread started on port {config['api_server_port']}") if config['debug_mode'] else None
+
+def stop_api_server():
+ global server_thread
+ if server_thread:
+ print("[AllTalk MEM] Shutting down API server")
+ server_thread.shutdown()
+ server_thread.join()
+ print("[AllTalk MEM] API server shut down successfully")
+
+######################
+### INITIALISATION ###
+######################
+
print(f"[AllTalk MEM] Please use \033[91mCtrl+C\033[0m when exiting otherwise Python")
print(f"[AllTalk MEM] subprocess's will continue running in the background.")
print(f"[AllTalk MEM] ")
@@ -680,15 +1040,17 @@ def auto_start_engines():
if num_engines > 0:
print(f"[AllTalk MEM] Auto-starting {num_engines} engines...")
results = start_MEMple_instances(num_engines, current_base_port)
- print(f"[AllTalk MEM] Auto-start complete. Results: {results}")
+ update_tts_instances()
+ print(f"[AllTalk MEM] Auto-start complete.")
+ print(f"[AllTalk MEM] {results}")
def shutdown():
print("[AllTalk MEM] Shutting down all engines...")
stop_all_instances()
- print("[AllTalk MEM] All engines stopped.")
+ print("[AllTalk MEM] All engines stopped.")
+# Main execution
if __name__ == "__main__":
- # Set up signal handler
signal.signal(signal.SIGINT, signal_handler)
interface = create_gradio_interface()
@@ -697,7 +1059,10 @@ if __name__ == "__main__":
interface_thread = threading.Thread(target=launch_interface, args=(interface,))
interface_thread.start()
- # Auto-start engines
+ # Start the API server
+ start_api_server()
+
+ initialize_instances()
auto_start_engines()
try:
@@ -708,6 +1073,7 @@ if __name__ == "__main__":
print(f"[AllTalk MEM] An error occurred: {e}")
finally:
print("[AllTalk MEM] Initiating shutdown...")
+ stop_api_server()
shutdown()
# Give the interface thread a chance to close gracefully
interface_thread.join(timeout=5)
From e36c67efe40910c3397509a09614d653d6abcf08 Mon Sep 17 00:00:00 2001
From: erew123 <35898566+erew123@users.noreply.github.com>
Date: Wed, 18 Sep 2024 01:35:36 +0100
Subject: [PATCH 36/38] Add files via upload
---
mem_load_test.py | 22 +++++++++++++---------
1 file changed, 13 insertions(+), 9 deletions(-)
diff --git a/mem_load_test.py b/mem_load_test.py
index 636c605..2823c06 100644
--- a/mem_load_test.py
+++ b/mem_load_test.py
@@ -4,13 +4,13 @@ import time
import argparse
from concurrent.futures import ThreadPoolExecutor
-async def send_tts_request(session, url, text, request_id):
+async def send_tts_request(session, url, text, voice, request_id):
start_time = time.time()
try:
async with session.post(url, data={
"text_input": text,
"text_filtering": "standard",
- "character_voice_gen": "en_US-ljspeech-high.onnx",
+ "character_voice_gen": voice,
"narrator_enabled": "false",
"narrator_voice_gen": "en_US-ljspeech-high.onnx",
"text_not_inside": "character",
@@ -29,12 +29,12 @@ async def send_tts_request(session, url, text, request_id):
print(f"Request {request_id} failed: {str(e)}")
return None
-async def run_load_test(num_requests, text_length, api_url):
+async def run_load_test(num_requests, text_length, api_url, voice):
async with aiohttp.ClientSession() as session:
tasks = []
for i in range(num_requests):
text = f"This is a test of the MEM load balancing system. Request number {i+1}. " * (text_length // 10)
- task = asyncio.ensure_future(send_tts_request(session, api_url, text, i+1))
+ task = asyncio.ensure_future(send_tts_request(session, api_url, text, voice, i+1))
tasks.append(task)
results = await asyncio.gather(*tasks)
@@ -45,18 +45,22 @@ def main():
parser.add_argument("--requests", type=int, default=5, help="Number of simultaneous requests to send")
parser.add_argument("--length", type=int, default=50, help="Approximate length of text for each request")
parser.add_argument("--url", type=str, default="http://127.0.0.1:7401/api/tts-generate", help="URL of the MEM API")
+ parser.add_argument("--voice", type=str, default="en_US-ljspeech-high.onnx", help="A voice the current TTS engine supports")
args = parser.parse_args()
print(f"AllTalk MEM load tester.")
print(f"\nYou can use this to load test MEM. You can specify:")
- print(f"\n - the amount of simultaneous request to send --requests")
- print(f" - the amount of character text per request --length")
- print(f" - the URL to send the request to --url")
+ print(f"\n --requests (the amount of simultaneous request to send)")
+ print(f" --length (the amount of character text per request)")
+ print(f" --url (the URL to send the request to)")
+ print(f" --voice (the voice/audio sample to use)")
print(f"\nA full command line would look like this")
- print(f"\npython mem_load_test.py --requests 3 --length 50 --url `http://127.0.0.1:7401/api/tts-generate`")
+ print(f"\npython mem_load_test.py --requests 3 --length 50 --url http://127.0.0.1:7401/api/tts-generate --voice female_01.wav")
print(f"\nAll requests are sent simultaniously at the same time. As such the 'completed in' time is")
print(f"calculated from the time the requests were sent, not the actual TTS processing time per")
print(f"individual request within the TTS engine.")
+ print(f"\nIf you dont specify a voice the loaded TTS engine uses, this will fail. The default voice")
+ print(f"being sent is en_US-ljspeech-high.onnx which is for Piper TTS.")
print(f"\nStarting load test with {args.requests} simultaneous requests...")
print(f"Each request will have approximately {args.length} characters of text.")
print(f"Sending requests to: {args.url}\n")
@@ -65,7 +69,7 @@ def main():
# Run the async function in a separate thread to avoid blocking
with ThreadPoolExecutor() as executor:
- future = executor.submit(asyncio.run, run_load_test(args.requests, args.length, args.url))
+ future = executor.submit(asyncio.run, run_load_test(args.requests, args.length, args.url, args.voice))
results = future.result()
end_time = time.time()
From 375b2ba31e7acdba718d52bef39c338e7b0dae85 Mon Sep 17 00:00:00 2001
From: erew123 <35898566+erew123@users.noreply.github.com>
Date: Wed, 18 Sep 2024 07:31:55 +0100
Subject: [PATCH 37/38] path correction
---
tts_mem.py | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/tts_mem.py b/tts_mem.py
index 7ea7f77..12699b3 100644
--- a/tts_mem.py
+++ b/tts_mem.py
@@ -3,6 +3,7 @@ import threading
import signal
import sys
import os
+from pathlib import Path
import gradio as gr
import time
import requests
@@ -988,8 +989,8 @@ def process_tts_request(instance, data, timeout):
def serve_audio(filename):
return send_from_directory(app.config['OUTPUT_FOLDER'], filename)
-# Make sure to update the path to match your actual output directory
-app.config['OUTPUT_FOLDER'] = 'E:\\installtest\\alltalk_tts\\outputs'
+# Set the output folder to be 'outputs' in the current directory
+app.config['OUTPUT_FOLDER'] = str(Path(os.getcwd()) / 'outputs')
class SilentWSGIRequestHandler(WSGIRequestHandler):
def log_request(self, *args, **kwargs):
From c6cf8afce31211be30a78e8313ed3f42d3434e5c Mon Sep 17 00:00:00 2001
From: erew123 <35898566+erew123@users.noreply.github.com>
Date: Thu, 19 Sep 2024 08:53:25 +0100
Subject: [PATCH 38/38] Document rvccharacter_pitch
---
system/gradio_pages/api_documentation.py | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/system/gradio_pages/api_documentation.py b/system/gradio_pages/api_documentation.py
index 08236a9..c2eec76 100644
--- a/system/gradio_pages/api_documentation.py
+++ b/system/gradio_pages/api_documentation.py
@@ -443,6 +443,10 @@ def api_documentation():
#### **rvccharacter_voice_gen**
> The name of the RVC voice file for the character. Should be in the format `folder\\file.pth` or the word `Disabled`. When Disabled is sent and RVC is globally enabled, the RVC pipeline will not be used for the character/main voice.
-d "rvccharacter_voice_gen=folder\\voice1.pth"
+
+ #### **rvccharacter_pitch**
+ > The pitch for the RVC voice for the character. Should be in the range `-24` up to `24` with `0` being the central point and the global setting, set in the Gradio RVC page being used if the pitch is not specified in the TTS request.
+ -d "rvccharacter_pitch=3"
#### **narrator_enabled**
> Enable or disable the narrator function. If true, minimum text filtering is set to standard.
@@ -456,6 +460,10 @@ def api_documentation():
#### **rvcnarrator_voice_gen**
> The name of the RVC voice file for the narrator. Should be in the format folder\\file.pth or the word Disabled. When Disabled is sent and RVC is globally enabled, the RVC pipeline will not be used for the character/main voice.
-d "rvcnarrator_voice_gen=folder\\voice2.pth"
+
+ #### **rvcnarrator_pitch**
+ > The pitch for the RVC voice for the narrator. Should be in the range `-24` up to `24` with `0` being the central point and the global setting, set in the Gradio RVC page being used if the pitch is not specified in the TTS request.
+ -d "rvcnarrator_pitch=3"
#### **text_not_inside**
> Specify the handling of lines not inside double quotes or asterisks for the narrator feature. Options: