diff --git a/system/tts_engines/f5tts/f5tts_settings_page.py b/system/tts_engines/f5tts/f5tts_settings_page.py index 74a720f..f58802d 100644 --- a/system/tts_engines/f5tts/f5tts_settings_page.py +++ b/system/tts_engines/f5tts/f5tts_settings_page.py @@ -2,8 +2,16 @@ import os import json import requests import gradio as gr +import whisper +from typing import List, Tuple from tqdm import tqdm from pathlib import Path +import gc +import torch +import warnings +# Filter Flash Attention warning +warnings.filterwarnings("ignore", message="1Torch was not compiled with flash attention") +from .help_content import AllTalkHelpContent 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 @@ -53,6 +61,71 @@ def f5tts_voices_file_list(): return sorted(voices) +def get_files_needing_transcription(directory: Path) -> List[Tuple[Path, str]]: + """Get all audio files that need transcription.""" + files_to_transcribe = [] + + for ext in ['.wav', '.mp3', '.flac']: + # Check main directory + for f in directory.glob(f"*{ext}"): + if not f.with_suffix('.reference.txt').exists(): + files_to_transcribe.append((f, ext[1:])) # Remove the dot from extension + + # Check subdirectories + for folder in directory.iterdir(): + if folder.is_dir(): + for f in folder.glob(f"*{ext}"): + if not f.with_suffix('.reference.txt').exists(): + files_to_transcribe.append((f, ext[1:])) + + return files_to_transcribe + +def transcribe_files(model_name: str, use_cpu: bool = False, progress=gr.Progress()) -> str: + """Transcribe all files without reference text.""" + files = get_files_needing_transcription(Path(main_dir)/'voices') + + if not files: + return "No files need transcription!" + + model = None + try: + progress(0, desc="Loading Whisper model...") + + # Determine device + device = "cpu" if use_cpu else ("cuda" if torch.cuda.is_available() else "cpu") + model = whisper.load_model(model_name).to(device) + + progress(0, desc=f"Transcribing files using {device.upper()}...") + + for idx, (file_path, _) in enumerate(progress.tqdm(files, desc="Transcribing files")): + # Transcribe the audio + result = model.transcribe(str(file_path)) + + # Save the transcription + with open(file_path.with_suffix('.reference.txt'), 'w', encoding='utf-8') as f: + f.write(result["text"].strip()) + + progress((idx + 1) / len(files)) + + return f"Successfully transcribed {len(files)} files using {device.upper()}!" + + except Exception as e: + return f"Error during transcription: {str(e)}" + + finally: + if model is not None: + # Move model to CPU before deletion if it was on CUDA + if torch.cuda.is_available() and not use_cpu: + model.cpu() + torch.cuda.empty_cache() + + # Delete the model explicitly + del model + model = None # Add this line to explicitly clear the reference + + # Run garbage collection + gc.collect() + ###################################################### # REQUIRED CHANGE # # Imports and saves the TTS engine-specific settings # @@ -143,10 +216,16 @@ def f5tts_model_alltalk_settings(model_config_data): 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.") + details_text = gr.Textbox(label="Details", show_label=False, lines=5, interactive=False, value="Configure default settings and voice mappings for the selected TTS engine. Unavailable options are grayed out based on engine capabilities. See the Help section below for detailed information about each setting.") with gr.Row(): submit_button = gr.Button("Update Settings") output_message = gr.Textbox(label="Output Message", interactive=False, show_label=False) + with gr.Accordion("HELP - 🔊 Understanding TTS Engine Default Settings Page", open=False): + with gr.Row(): + gr.Markdown(AllTalkHelpContent.DEFAULT_SETTINGS, elem_classes="custom-markdown") + with gr.Row(): + gr.Markdown(AllTalkHelpContent.DEFAULT_SETTINGS1, elem_classes="custom-markdown") + gr.Markdown(AllTalkHelpContent.DEFAULT_SETTINGS2, elem_classes="custom-markdown") submit_button.click(f5tts_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) ########################################################################################### @@ -180,34 +259,12 @@ def f5tts_model_alltalk_settings(model_config_data): 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.Accordion("HELP - 🔊 Understanding TTS Engine Capabilities", open=False): + with gr.Row(): + gr.Markdown(AllTalkHelpContent.ENGINE_INFORMATION, elem_classes="custom-markdown") + with gr.Row(): + gr.Markdown(AllTalkHelpContent.ENGINE_INFORMATION1, elem_classes="custom-markdown") + gr.Markdown(AllTalkHelpContent.ENGINE_INFORMATION2, elem_classes="custom-markdown") ####################################################################################################################################################################################################### # REQUIRED CHANGE # @@ -292,7 +349,6 @@ def f5tts_model_alltalk_settings(model_config_data): confirm_button.click(confirm_download, inputs=model_dropdown, outputs=[confirm_button, download_button, cancel_button, download_status]) cancel_button.click(cancel_download, inputs=None, outputs=[confirm_button, download_button, cancel_button, download_status]) - def get_voice_files(): """Get list of voice files with their status""" files = [ @@ -302,76 +358,47 @@ def f5tts_model_alltalk_settings(model_config_data): ] return sorted(files) - with gr.Tab("Reference Text/Sample Manager"): + with gr.Tab("Reference Text/Sample Manager"): with gr.Row(): - gr.Markdown(""" - ### Reference Text/Sample Manager - This tool helps you manage reference text files for your voice samples. Each WAV file should have a corresponding .reference.txt file - containing the exact text that was spoken in the recording. This is important for voice cloning quality.
- -              Files marked in 🔴 red need a reference text file
-              Files marked in 🟢 green already have a reference text file
- - It is important to note that F5-TTS only supports English and Chinese languages. - - For more information on the F5-TTS engine, please visit the: [F5-TTS GitHub Repository](https://github.com/SWivid/F5-TTS/) - """) - - gr.Markdown(""" - #### Important Guidelines: - * Reference WAV files should be no longer than 15 seconds in length, or it may affect playback speed. - * You can play the audio below and listen to it. You then need to populate the Reference Text box with the exact spoken audio, preferably adding punctuation where appropriate:
-          - Period (.)
-          - Comma (,)
-          - Semi-colon (;)
-          - Apostrophe (') - Important for words like "I'm", "don't", "can't", etc.
- * After saving the reference text file, you'll need to go to the main generation page and click "Refresh Server Settings" for the voice to be shown as available. - """) - + # List all WAV files and their status + file_list = gr.Dropdown( + label="Voice Files", + choices=get_voice_files(), + value=None + ) + refresh_btn = gr.Button("Refresh List") + model_selector = gr.Dropdown( + label="Whisper Model", + choices=["base", "medium", "large-v3"], + value="base" + ) + transcribe_button = gr.Button("Auto-Transcribe Missing Files") with gr.Row(): - with gr.Column(): - # List all WAV files and their status - file_list = gr.Dropdown( - label="Voice Files", - choices=get_voice_files(), - value=None - ) - - # Add audio player with small height - audio_player = gr.Audio( - label="Preview Voice Sample", - type="filepath", - interactive=False, - elem_classes="small-audio-player" # Custom CSS class for sizing - ) - - refresh_btn = gr.Button("Refresh List") - - with gr.Column(): - current_text = gr.Textbox( - label="Reference Text", - placeholder="Enter the exact text that was spoken in the recording...", - lines=5 - ) - - with gr.Row(): - save_btn = gr.Button("Save Reference Text", variant="primary") - delete_btn = gr.Button("Delete Reference Text", variant="secondary") - - status_text = gr.Textbox(label="Status", interactive=False) - - # Add custom CSS to make the audio player smaller - gr.HTML(""" - - """) + # Add audio player with small height + audio_player = gr.Audio( + label="Preview Voice Sample", + type="filepath", + interactive=False, + elem_classes="small-audio-player" # Custom CSS class for sizing + ) + with gr.Row(): + current_text = gr.Textbox( + label="Manually Edit Reference Text", + placeholder="Enter the exact text that was spoken in the recording...", + lines=2, + scale=2 + ) + status_text = gr.Textbox(label="Status", interactive=False, scale=1) + with gr.Row(): + save_btn = gr.Button("Save Reference Text", variant="primary") + delete_btn = gr.Button("Delete Reference Text", variant="secondary") + with gr.Accordion("HELP - 🎯 Reference Text/Sample Manager", open=False): + with gr.Row(): + gr.Markdown(AllTalkHelpContent.REFERENCE_MANAGER, elem_classes="custom-markdown") + with gr.Row(): + gr.Markdown(AllTalkHelpContent.REFERENCE_MANAGER1, elem_classes="custom-markdown") + gr.Markdown(AllTalkHelpContent.REFERENCE_MANAGER2, elem_classes="custom-markdown") + def load_file_data(wav_file): if not wav_file: @@ -457,6 +484,14 @@ def f5tts_model_alltalk_settings(model_config_data): outputs=file_list ) refresh_btn.click(refresh_file_list, outputs=file_list) + transcribe_button.click( + fn=transcribe_files, + inputs=[model_selector], + outputs=status_text # Use the existing status box instead of creating a new one + ).then( + fn=refresh_file_list, + outputs=file_list + ) ################################################################################################### # REQUIRED CHANGE # @@ -465,43 +500,10 @@ def f5tts_model_alltalk_settings(model_config_data): ################################################################################################### with gr.Tab("Engine Help"): with gr.Row(): - gr.Markdown(""" - ### 🟧 Where are the f5tts models stored? - This extension will download the f5tts models to `/alltalk_tts/models/f5tts/` folder. - - ### 🟧 How do clone/reference voices work? - F5-TTS uses voice samples with corresponding reference text files for voice cloning: - 1. Place your WAV voice samples in the `/alltalk_tts/voices/` folder - 2. Create a matching `.reference.txt` file containing the exact text spoken in the recording. - 3. Use the `Reference Text/Sample Manager` tab to create and manage these text files - 4. Only voice samples with valid reference text files will be available for use - - For best results: - - Keep voice samples under 15 seconds - - Ensure the reference text exactly matches what is spoken - - Use high-quality recordings with minimal background noise - - ### 🟧 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. - """) - gr.Markdown(""" - ### 🟧 Speed settings and voice cloning - F5-TTS appears to generate audio a little faster than the audio sample. As such, in AllTalk, the speed setting for generation is set to 0.9 (see the F5-TTS `Default Settings` tab). Certainly the better the punctuation you use, the better the end result, both in the audio samples reference file and the text you send to be generated as TTS. If you wish to globally adjust this, you can make the change on the Default Settings tab. - - ### 🟧 Cloned audio and punctuation. - When F5-TTS reproduces audio, punctuation appears to matter greatly. As such `Im` and `I'm` or `I am` will all sound a little different. Additionally it appears to want to spell out text in CAPS. You may want to look at the F5-TTS developers page for more information on this. - - ### 🟧 Difference between F5-TTS and E2-TTS models. - F5-TTS and E2-TTS represent two different approaches to zero-shot text-to-speech: - - - F5-TTS is optimized for voice fidelity and natural speech patterns. It excels at capturing the nuances and characteristics of the reference voice, producing highly natural and faithful speech output. The tradeoff is slightly longer generation times and higher computational requirements.
- - E2-TTS prioritizes efficiency and ease of use. It offers faster generation speeds and better memory efficiency, making it ideal for longer texts or resource-constrained environments. While its voice reproduction may be slightly less precise than F5-TTS, it still produces high-quality output with good speaker similarity. - - Choose F5-TTS when voice fidelity is paramount, or E2-TTS when generation speed and efficiency are the priority. - """) + gr.Markdown(AllTalkHelpContent.HELP_PAGE, elem_classes="custom-markdown") + with gr.Row(): + gr.Markdown(AllTalkHelpContent.HELP_PAGE1, elem_classes="custom-markdown") + gr.Markdown(AllTalkHelpContent.HELP_PAGE2, elem_classes="custom-markdown") return app diff --git a/system/tts_engines/f5tts/help_content.py b/system/tts_engines/f5tts/help_content.py new file mode 100644 index 0000000..d339c25 --- /dev/null +++ b/system/tts_engines/f5tts/help_content.py @@ -0,0 +1,546 @@ +# help_content.py +# pylint: disable=no-member + +class AllTalkHelpContent: + """CSS and help content for help_content.py""" + custom_css = """ + /* Add this to your existing CSS */ + .gradio-container .prose { + max-width: none !important; + padding: 0.5rem !important; /* Reduced padding */ + margin: 0 !important; + } + + .custom-markdown div { + border: none !important; /* Remove the inner border */ + margin-top: 0 !important; /* Remove top margin */ + margin-bottom: 0 !important; /* Remove bottom margin */ + padding-top: 0 !important; /* Remove top padding */ + padding-bottom: 0 !important; /* Remove bottom padding */ + } + + /* Update the existing custom-markdown class */ + .custom-markdown { + font-size: 15px !important; + line-height: 1.6 !important; + color: var(--body-text-color) !important; + background-color: var(--background-fill-primary) !important; + border: 1px solid var(--border-color-primary) !important; + border-radius: 8px !important; + padding: 0.5rem !important; + margin: 0 !important; /* Changed from 1rem 0 to 0 */ + max-width: 100% !important; + box-sizing: border-box !important; + /* Default text color for all content (grey) */ + color: rgba(156, 163, 175, 1) !important; /* Adjust this grey value to match your interface */ + } + + /* Make headings white */ + .custom-markdown h1, + .custom-markdown h2, + .custom-markdown h3, + .custom-markdown h4, + .custom-markdown h5, + .custom-markdown h6 { + color: var(--heading-text-color, var(--body-text-color)) !important; + font-weight: 600 !important; + } + + /* Keep all other elements in the grey color */ + .custom-markdown p, + .custom-markdown li, + .custom-markdown ul, + .custom-markdown ol { + color: rgba(156, 163, 175, 1) !important; /* Same grey as the base text */ + } + + /* Additional targeting for any wrapping elements */ + .gradio-container .prose > * { + margin: 0 !important; + padding: 0 !important; + } + + /* Update the list item spacing in your existing CSS */ + .custom-markdown li { + font-size: 0.95rem !important; + margin-bottom: 0.2rem !important; /* Reduced from 0.5rem to 0.3rem */ + } + + /* Add specific styling for definition-style lists (like your Default, Recommendation, Tip items) */ + .custom-markdown li p { + margin: 0 !important; /* Remove paragraph margins within list items */ + } + + /* If you need even tighter spacing for specific types of lists */ + .custom-markdown li + li { + margin-top: 0.2rem !important; /* Space between consecutive list items */ + } + + /* Ensure nested lists maintain proper spacing */ + .custom-markdown li > ul, + .custom-markdown li > ol { + margin: 0.2rem 0 0.2rem 1rem !important; /* Reduced from 0.5rem */ + } + + /* Consistent heading styles */ + .custom-markdown h2 { + font-size: 1.2rem !important; /* Fixed size relative to root */ + font-weight: 600 !important; + margin: 1.5rem 0 1rem !important; + color: var(--heading-text-color, var(--body-text-color)) !important; + border-bottom: 1px solid var(--border-color-primary) !important; + padding-bottom: 0.5rem !important; + } + + /* First paragraph styling */ + .custom-markdown p:first-of-type { + font-size: 0.95rem !important; /* Match base size */ + font-weight: 400 !important; + margin-bottom: 1rem !important; + } + + /* Regular paragraphs */ + .custom-markdown p { + font-size: 0.95rem !important; + margin: 0.8rem 0 !important; + } + + /* List styling */ + .custom-markdown ul, + .custom-markdown ol { + margin: 0.8rem 0 !important; + padding-left: 1.5rem !important; + } + + /* Code styling */ + .custom-markdown code { + font-family: ui-monospace, monospace !important; + background-color: var(--background-fill-secondary) !important; + padding: 0.2rem 0.4rem !important; + border-radius: 4px !important; + font-size: 0.8rem !important; /* Slightly smaller than regular text */ + color: var(--body-text-color) !important; + } + + /* Add styles for bold and emphasis */ + .custom-markdown strong { + font-weight: 600 !important; + color: var(--body-text-color) !important; + } + + .custom-markdown em { + font-style: italic !important; + } + + /* Ensure both columns take equal width */ + .gradio-row > div { + flex: 1 1 50% !important; + min-width: 0 !important; /* Prevents flex items from overflowing */ + } + """ + + ENGINE_INFORMATION = """ + ## 🔊 TTS Engine Capabilities Help + + This guide explains the various capabilities that **may** be available in different TTS engines and models. Each capability affects how the TTS engine processes and generates speech output. + """ + + ENGINE_INFORMATION1 = """ + ## 🚀 Performance Features + + - **DeepSpeed Capable**: Enables GPU acceleration using the DeepSpeed optimization library. + - **Requires**: NVIDIA GPU with CUDA support + - **Benefit**: Significantly faster text-to-speech generation + - **Note**: Model must specifically support DeepSpeed inference + + - **Low VRAM Capable**: Optimized for systems with limited GPU memory. + - **Benefit**: Efficient memory management between CPU and GPU + - **Use Case**: Ideal when running alongside other GPU-intensive applications like LLMs + - **Note**: May trade speed for memory efficiency + + - **Streaming Capable**: Enables real-time speech generation. + - **Benefit**: Immediate playback without generating entire audio first + - **Use Case**: Interactive applications and real-time responses + - **Note**: Not all output formats support streaming + + ## 🎵 Voice Control Features + + - **Pitch Capable**: Allows adjustment of voice pitch. + - **Benefit**: Creates more expressive and varied speech output + - **Use Case**: Customizing voice characteristics + - **Note**: Adjustment range varies by model + + - **Generation Speed Capable**: Controls speech rate. + - **Benefit**: Adjustable playback speed during generation + - **Use Case**: Creating faster or slower speech output + - **Note**: May affect audio quality at extreme settings + + - **Temperature Capable**: Controls output randomness. + - **Benefit**: Adjustable speech variation and creativity + - **Use Case**: Balancing consistency vs. naturality + - **Note**: Higher values increase variation but may reduce quality + """ + + ENGINE_INFORMATION2 = """ + ## 🎯 Quality Enhancement Features + + - **Repetition Penalty Capable**: Prevents unnatural speech patterns. + - **Benefit**: Reduces repeated sounds and phrases + - **Use Case**: Improving natural flow of longer texts + - **Note**: Strength of penalty may be adjustable + + ## 🌐 Multi-Feature Support + + - **Multi-Languages Capable Models**: Each model Supports multiple languages. + - **Benefit**: Generate speech in different languages + - **Note**: Quality may vary between languages + - **Tip**: Check model-specific language support + + - **Multi-Voice Capable**: Supports multiple speaking voices. + - **Benefit**: Different voices or speaking styles + - **Types**: Pre-trained voices or voice cloning + - **Note**: Voice quality may vary by model + + - **Multi-Model Capable Engine**: Supports multiple TTS models. + - **Benefit**: Flexibility in model selection + - **Use Case**: Switching between models for different needs + - **Note**: Each model may have different capabilities + + ## 📁 Technical Features + + - **Default Audio Output Format**: Specifies output file format. + - **Common Formats**: WAV, MP3, FLAC, Opus, AAC, PCM + - **Note**: Transcoding to different formats adds processing time + - **Warning**: Not all formats support streaming + + - **Platform Support**: Operating system compatibility. + - **Platforms**: Windows, Linux, macOS + - **Note**: Additional setup may be required + - **Warning**: Support level may vary by platform + """ + + DEFAULT_SETTINGS = """ + ## 🎯 TTS Engine Settings Help + + This guide explains the settings and configuration options available for individual Text-to-Speech engines within AllTalk. + """ + + DEFAULT_SETTINGS1 = """ + ## 🎚️ Engine Capabilities & Controls + + - **Low VRAM Mode** + - Optimizes memory usage for systems with limited GPU memory + - Efficiently manages resources by moving data between CPU and GPU + - Recommended for systems with less than 4GB VRAM or when running alongside other GPU-intensive applications + + - **DeepSpeed Capability** + - Accelerates TTS generation using optimized inference + - Only available for engines and models that support DeepSpeed + - Requires NVIDIA GPU with CUDA support + + - **Temperature Control** + - Adjusts the variability in speech generation + - **Range**: 0.0 to 1.0 + - **Lower values** (0.1-0.5): More consistent, stable output + - **Higher values** (0.6-1.0): More variable, potentially more natural-sounding output + + - **Repetition Penalty** + - Helps prevent repetitive speech patterns + - **Range**: 1.0 to 15.0 + - Higher values more strongly discourage repetition + - Typically most effective between 1.0-3.0 + + - **Pitch Adjustment** + - Modifies the voice pitch when supported + - **Range**: -20 to +20 + - Use subtle adjustments for most natural results + + - **Generation Speed** + - Controls the pace of generated speech + - **Range**: 0.5 to 2.0 + - 1.0 represents normal speed + """ + + DEFAULT_SETTINGS2 = """ + + ## 🗣️ Voice Configuration + + ### OpenAI Voice Mappings + - Only relevant when using the OpenAI-compatible API endpoint + - Maps OpenAI's six standard voices to equivalent voices in the current engine: + - `alloy` + - `echo` + - `fable` + - `nova` + - `onyx` + - `shimmer` + - Essential for maintaining compatibility with OpenAI API calls + - Each mapping can be customized to any available voice in the current engine + + Information on the OpenAI Endpoint is available in the [Github Wiki](https://github.com/erew123/alltalk_tts/wiki) + + ### Default Voices + - **Default/Character Voice**: Used when no specific voice is specified in API calls + - **Narrator Voice**: Separate default for narrator-specific text + - These defaults are engine-specific and won't affect other TTS engines + - Can be overridden by explicitly specifying voices in API calls + + ## ⚙️ Important Notes + + - Settings availability is determined by engine capabilities + - Grayed-out options indicate features not supported by the current engine + - Changes only affect the currently selected TTS engine + - Settings here act as defaults but can be overridden via API parameters + - All changes require clicking "Update Settings" to take effect + - Some settings require an engine reload to take effect + """ + + HELP_PAGE = """ + ## 🎯 F5-TTS Voice Cloning Engine Help + + F5-TTS is a zero-shot voice cloning system that specializes in high-fidelity voice reproduction with precise reference text matching. + """ + + HELP_PAGE1 = """ + ## 🎚️ Model Variants + + ### F5-TTS (High Fidelity) + - Optimized for voice accuracy and naturalness + - Superior voice characteristic capture + - Higher computational requirements + - Best for quality-focused applications + - Longer generation times + + ### E2-TTS (Efficient) + - Focused on speed and efficiency + - Lower resource requirements + - Faster generation times + - Good for longer texts + - Suitable for resource-constrained systems + + ## 🗣️ Voice Cloning System + + ### Reference Text Requirements + - Each voice sample needs a matching `.reference.txt` file + - Text must exactly match spoken audio + - Punctuation significantly affects output quality + - Use the Reference Text/Sample Manager for setup + - Supports English and Chinese languages only + + ### Audio Sample Guidelines + - Format: WAV/MP3/Flac files + - Length: Maximum 15 seconds recommended + - Quality: Clear audio, minimal background noise + - Location: `/alltalk_tts/voices/` directory + - Names: Should match reference text filename + + ### Punctuation Impact + - Precise punctuation affects voice reproduction + - Examples of differences: + - `Im` vs `I'm` vs `I am` + - Commas affect pacing + - Periods affect sentence flow + - Capital letters may be spelled out + - Use appropriate punctuation marks: + - Periods (.) + - Commas (,) + - Semi-colons (;) + - Apostrophes (') + + ## ⚙️ Performance Settings + + ### Speed Adjustment + - Default speed setting: 0.9 + - 1.0 seems faster than reference audio + - Compensates for faster audio generation + - Adjustable in Default Settings tab + - Affected by: + - Sample length + - Text punctuation + - Reference text accuracy + """ + + HELP_PAGE2 = """ + ### Best Practices + 1. **Voice Samples** + - Keep under 15 seconds + - Use clear recordings + - Ensure consistent audio quality + - Avoid background noise + + 2. **Reference Text** + - Match spoken content exactly + - Include proper punctuation + - Verify text accuracy + - Consider natural speech patterns + + 3. **Model Selection** + - F5-TTS: When quality is priority + - E2-TTS: When speed is priority + - Consider system resources + - Test both for optimal results + + ## 📁 File Management + + - **Models**: `/alltalk_tts/models/f5tts/` + - **Voice Samples**: `/alltalk_tts/voices/` + - **Reference Texts**: `.reference.txt` files alongside WAVs + - **Outputs**: `/alltalk_tts/outputs/` + - Automatic cleanup available + - Configurable retention period + - Set to 'Disabled' to keep all files + + ## 🔄 Reference Text Management + + ### Using the Manager + - Access via Reference Text/Sample Manager tab + - Color-coded file status: + - 🔴 Red: Needs reference text + - 🟢 Green: Has reference text + - Built-in audio playback + - Whisper transcription available + + ### Whisper Transcription Options + - **base** (200MB download): Fast, basic quality + - **medium** (1.5GB download): Better quality + - **large-v3** (3GB download): Best quality + - Automatic model download + - Helps create initial reference text + + ## 🔧 System Integration + + - Click "Refresh Server Settings" after adding new voices + - Monitor system resources for optimal performance + - Consider storage space for models and samples + - Regular maintenance of output files recommended + + ## ⚠️ Important Notes + + - Language Support: English and Chinese only + - Reference text accuracy is crucial + - Punctuation affects voice reproduction + - Sample length impacts playback speed + - Model selection based on use case + - System resources affect performance + - Regular updates may improve functionality + """ + REFERENCE_MANAGER = """ + ## 🎯 Reference Text/Sample Manager Help + + The Reference Text/Sample Manager is a crucial tool for F5-TTS voice cloning, helping you manage and create accurate reference text files for your voice samples. + """ + REFERENCE_MANAGER1 = """ + ## 📝 Core Functionality + + ### File Status Indicators + - 🔴 Red: Files needing a reference text file + - 🟢 Green: Files with existing reference text file + - Visual system helps track incomplete samples + + ### Basic Operations + - **Preview Voice Sample**: Listen to selected audio + - **Refresh List**: Update file listing + - **Save Reference Text**: Create/update reference file + - **Delete Reference Text**: Remove existing reference + - **Auto-Transcribe**: Use Whisper for automatic transcription + + ## 📒 Creating Reference Text Files + + ### Manual Process + 1. Select voice file from dropdown + 2. Play audio using Preview button + 3. Type exact spoken text in editor + 4. Add appropriate punctuation + 5. Save reference text + 6. Click "Refresh Server Settings" on main page + + ### Punctuation Guidelines + - **Period (.)**: End of sentences + - **Comma (,)**: Natural pauses + - **Semi-colon (;)**: Longer pauses + - **Apostrophe (')**: Contractions like: + - "I'm" + - "don't" + - "can't" + - "we're" + - Where necessary use phonetics + - This is a test of TTS (vs) + - This is a test of tea tea ess + + ## 🎤 Voice Sample Requirements + + ### Audio Guidelines + - Maximum length: 15 seconds + - Format: WAV files + - Quality: Clear, minimal noise + - Location: `/alltalk_tts/voices/` + + ### Important Notes + - Longer samples may affect playback speed + - Only English and Chinese languages supported + - Exact text matching is crucial + - Include natural speech punctuation + """ + + REFERENCE_MANAGER2 = """ + ## 🤖 Whisper Transcription + + ### Available Models + 1. **base (1GB VRAM)** + - Fast processing + - Basic quality + - Good for most uses + - Smallest disk space + + 2. **medium (4GB VRAM)** + - Better quality + - Slower processing + - Moderate disk space + + 3. **large-v3 (10GB VRAM)** + - Best quality + - Slowest processing + - Largest disk space + + ### Using Whisper + - Select desired model from dropdown + - Click "Auto-Transcribe Missing Files" + - Review and edit transcriptions + - Add punctuation as needed + - Save corrected text + + ### Storage Notes + - Models download automatically + - Verify available disk space + - One-time download per model + - Models persist between sessions + + ## 💡 Best Practices + + 1. **Text Accuracy** + - Listen to audio multiple times + - Match text exactly + - Include all spoken words + - Maintain speech patterns + + 2. **Punctuation Usage** + - Mark natural pauses + - Use appropriate marks + - Consider speech flow + - Be consistent + + 3. **Workflow Tips** + - Process files in batches + - Verify saved references + - Use Whisper for initial draft + - Double-check automated transcriptions + + ## ⚠️ Important Reminders + + - Always refresh server settings after changes + - Keep voice samples under 15 seconds + - Back up reference text files + - Review automated transcriptions + - Consider system storage for Whisper models + - Language support limited to English and Chinese + """ \ No newline at end of file diff --git a/system/tts_engines/parler/help_content.py b/system/tts_engines/parler/help_content.py new file mode 100644 index 0000000..f111302 --- /dev/null +++ b/system/tts_engines/parler/help_content.py @@ -0,0 +1,432 @@ +# help_content.py +# pylint: disable=no-member + +class AllTalkHelpContent: + """CSS and help content for help_content.py""" + custom_css = """ + /* Add this to your existing CSS */ + .gradio-container .prose { + max-width: none !important; + padding: 0.5rem !important; /* Reduced padding */ + margin: 0 !important; + } + + .custom-markdown div { + border: none !important; /* Remove the inner border */ + margin-top: 0 !important; /* Remove top margin */ + margin-bottom: 0 !important; /* Remove bottom margin */ + padding-top: 0 !important; /* Remove top padding */ + padding-bottom: 0 !important; /* Remove bottom padding */ + } + + /* Update the existing custom-markdown class */ + .custom-markdown { + font-size: 15px !important; + line-height: 1.6 !important; + color: var(--body-text-color) !important; + background-color: var(--background-fill-primary) !important; + border: 1px solid var(--border-color-primary) !important; + border-radius: 8px !important; + padding: 0.5rem !important; + margin: 0 !important; /* Changed from 1rem 0 to 0 */ + max-width: 100% !important; + box-sizing: border-box !important; + /* Default text color for all content (grey) */ + color: rgba(156, 163, 175, 1) !important; /* Adjust this grey value to match your interface */ + } + + /* Make headings white */ + .custom-markdown h1, + .custom-markdown h2, + .custom-markdown h3, + .custom-markdown h4, + .custom-markdown h5, + .custom-markdown h6 { + color: var(--heading-text-color, var(--body-text-color)) !important; + font-weight: 600 !important; + } + + /* Keep all other elements in the grey color */ + .custom-markdown p, + .custom-markdown li, + .custom-markdown ul, + .custom-markdown ol { + color: rgba(156, 163, 175, 1) !important; /* Same grey as the base text */ + } + + /* Additional targeting for any wrapping elements */ + .gradio-container .prose > * { + margin: 0 !important; + padding: 0 !important; + } + + /* Update the list item spacing in your existing CSS */ + .custom-markdown li { + font-size: 0.95rem !important; + margin-bottom: 0.2rem !important; /* Reduced from 0.5rem to 0.3rem */ + } + + /* Add specific styling for definition-style lists (like your Default, Recommendation, Tip items) */ + .custom-markdown li p { + margin: 0 !important; /* Remove paragraph margins within list items */ + } + + /* If you need even tighter spacing for specific types of lists */ + .custom-markdown li + li { + margin-top: 0.2rem !important; /* Space between consecutive list items */ + } + + /* Ensure nested lists maintain proper spacing */ + .custom-markdown li > ul, + .custom-markdown li > ol { + margin: 0.2rem 0 0.2rem 1rem !important; /* Reduced from 0.5rem */ + } + + /* Consistent heading styles */ + .custom-markdown h2 { + font-size: 1.2rem !important; /* Fixed size relative to root */ + font-weight: 600 !important; + margin: 1.5rem 0 1rem !important; + color: var(--heading-text-color, var(--body-text-color)) !important; + border-bottom: 1px solid var(--border-color-primary) !important; + padding-bottom: 0.5rem !important; + } + + /* First paragraph styling */ + .custom-markdown p:first-of-type { + font-size: 0.95rem !important; /* Match base size */ + font-weight: 400 !important; + margin-bottom: 1rem !important; + } + + /* Regular paragraphs */ + .custom-markdown p { + font-size: 0.95rem !important; + margin: 0.8rem 0 !important; + } + + /* List styling */ + .custom-markdown ul, + .custom-markdown ol { + margin: 0.8rem 0 !important; + padding-left: 1.5rem !important; + } + + /* Code styling */ + .custom-markdown code { + font-family: ui-monospace, monospace !important; + background-color: var(--background-fill-secondary) !important; + padding: 0.2rem 0.4rem !important; + border-radius: 4px !important; + font-size: 0.8rem !important; /* Slightly smaller than regular text */ + color: var(--body-text-color) !important; + } + + /* Add styles for bold and emphasis */ + .custom-markdown strong { + font-weight: 600 !important; + color: var(--body-text-color) !important; + } + + .custom-markdown em { + font-style: italic !important; + } + + /* Ensure both columns take equal width */ + .gradio-row > div { + flex: 1 1 50% !important; + min-width: 0 !important; /* Prevents flex items from overflowing */ + } + """ + + ENGINE_INFORMATION = """ + ## 🔊 TTS Engine Capabilities Help + + This guide explains the various capabilities that **may** be available in different TTS engines and models. Each capability affects how the TTS engine processes and generates speech output. + """ + + ENGINE_INFORMATION1 = """ + ## 🚀 Performance Features + + - **DeepSpeed Capable**: Enables GPU acceleration using the DeepSpeed optimization library. + - **Requires**: NVIDIA GPU with CUDA support + - **Benefit**: Significantly faster text-to-speech generation + - **Note**: Model must specifically support DeepSpeed inference + + - **Low VRAM Capable**: Optimized for systems with limited GPU memory. + - **Benefit**: Efficient memory management between CPU and GPU + - **Use Case**: Ideal when running alongside other GPU-intensive applications like LLMs + - **Note**: May trade speed for memory efficiency + + - **Streaming Capable**: Enables real-time speech generation. + - **Benefit**: Immediate playback without generating entire audio first + - **Use Case**: Interactive applications and real-time responses + - **Note**: Not all output formats support streaming + + ## 🎵 Voice Control Features + + - **Pitch Capable**: Allows adjustment of voice pitch. + - **Benefit**: Creates more expressive and varied speech output + - **Use Case**: Customizing voice characteristics + - **Note**: Adjustment range varies by model + + - **Generation Speed Capable**: Controls speech rate. + - **Benefit**: Adjustable playback speed during generation + - **Use Case**: Creating faster or slower speech output + - **Note**: May affect audio quality at extreme settings + + - **Temperature Capable**: Controls output randomness. + - **Benefit**: Adjustable speech variation and creativity + - **Use Case**: Balancing consistency vs. naturality + - **Note**: Higher values increase variation but may reduce quality + """ + + ENGINE_INFORMATION2 = """ + ## 🎯 Quality Enhancement Features + + - **Repetition Penalty Capable**: Prevents unnatural speech patterns. + - **Benefit**: Reduces repeated sounds and phrases + - **Use Case**: Improving natural flow of longer texts + - **Note**: Strength of penalty may be adjustable + + ## 🌐 Multi-Feature Support + + - **Multi-Languages Capable Models**: Each model Supports multiple languages. + - **Benefit**: Generate speech in different languages + - **Note**: Quality may vary between languages + - **Tip**: Check model-specific language support + + - **Multi-Voice Capable**: Supports multiple speaking voices. + - **Benefit**: Different voices or speaking styles + - **Types**: Pre-trained voices or voice cloning + - **Note**: Voice quality may vary by model + + - **Multi-Model Capable Engine**: Supports multiple TTS models. + - **Benefit**: Flexibility in model selection + - **Use Case**: Switching between models for different needs + - **Note**: Each model may have different capabilities + + ## 📁 Technical Features + + - **Default Audio Output Format**: Specifies output file format. + - **Common Formats**: WAV, MP3, FLAC, Opus, AAC, PCM + - **Note**: Transcoding to different formats adds processing time + - **Warning**: Not all formats support streaming + + - **Platform Support**: Operating system compatibility. + - **Platforms**: Windows, Linux, macOS + - **Note**: Additional setup may be required + - **Warning**: Support level may vary by platform + """ + + DEFAULT_SETTINGS = """ + ## 🎯 TTS Engine Settings Help + + This guide explains the settings and configuration options available for individual Text-to-Speech engines within AllTalk. + """ + + DEFAULT_SETTINGS1 = """ + ## 🎚️ Engine Capabilities & Controls + + - **Low VRAM Mode** + - Optimizes memory usage for systems with limited GPU memory + - Efficiently manages resources by moving data between CPU and GPU + - Recommended for systems with less than 4GB VRAM or when running alongside other GPU-intensive applications + + - **DeepSpeed Capability** + - Accelerates TTS generation using optimized inference + - Only available for engines and models that support DeepSpeed + - Requires NVIDIA GPU with CUDA support + + - **Temperature Control** + - Adjusts the variability in speech generation + - **Range**: 0.0 to 1.0 + - **Lower values** (0.1-0.5): More consistent, stable output + - **Higher values** (0.6-1.0): More variable, potentially more natural-sounding output + + - **Repetition Penalty** + - Helps prevent repetitive speech patterns + - **Range**: 1.0 to 15.0 + - Higher values more strongly discourage repetition + - Typically most effective between 1.0-3.0 + + - **Pitch Adjustment** + - Modifies the voice pitch when supported + - **Range**: -20 to +20 + - Use subtle adjustments for most natural results + + - **Generation Speed** + - Controls the pace of generated speech + - **Range**: 0.5 to 2.0 + - 1.0 represents normal speed + """ + + DEFAULT_SETTINGS2 = """ + + ## 🗣️ Voice Configuration + + ### OpenAI Voice Mappings + - Only relevant when using the OpenAI-compatible API endpoint + - Maps OpenAI's six standard voices to equivalent voices in the current engine: + - `alloy` + - `echo` + - `fable` + - `nova` + - `onyx` + - `shimmer` + - Essential for maintaining compatibility with OpenAI API calls + - Each mapping can be customized to any available voice in the current engine + + Information on the OpenAI Endpoint is available in the [Github Wiki](https://github.com/erew123/alltalk_tts/wiki) + + ### Default Voices + - **Default/Character Voice**: Used when no specific voice is specified in API calls + - **Narrator Voice**: Separate default for narrator-specific text + - These defaults are engine-specific and won't affect other TTS engines + - Can be overridden by explicitly specifying voices in API calls + + ## ⚙️ Important Notes + + - Settings availability is determined by engine capabilities + - Grayed-out options indicate features not supported by the current engine + - Changes only affect the currently selected TTS engine + - Settings here act as defaults but can be overridden via API parameters + - All changes require clicking "Update Settings" to take effect + - Some settings require an engine reload to take effect + """ + + HELP_PAGE = """ + ## 🎯 Parler TTS Engine Settings Help + + Parler TTS is a unique text-to-speech system that generates voices based on descriptive prompts, similar to how image generation AI works with text descriptions. + """ + + HELP_PAGE1 = """ + ## 📁 File Locations & Management + + - **Model Storage** + - Models are stored in `/alltalk_tts/models/parler/{model name}` + + ## 🎚️ Engine Capabilities & Controls + + - **Low VRAM Mode** + - Enabled by default for optimized memory management + - Helps balance resource usage when running alongside other applications + - Recommended for most setups + + - **Multi-Model Support** + - Supports multiple internal models for voice generation + - Allows for diverse voice characteristics and styles + - Enables both native and custom voice descriptions + + ## 🗣️ Voice System + + ### Native Voices + - 34 built-in consistent voices including: + - Aaron, Alisa, Anna, Barbara, Bill, etc. + - Each native voice provides more consistent results + - Use these when you need reproducible voice characteristics + + ### Custom Voice Descriptions + - Create voices by describing desired characteristics: + - Gender and age + - Speaking style and pace + - Accent and tone + - Audio quality characteristics + - **Example**: "A female speaker with an enthusiastic and lively voice. Her tone is bright and energetic, with a fast pace and a lot of inflection." + + ### Voice Editor + - Add and manage custom voice descriptions + - Save frequently used voice configurations + - Edit existing voice descriptions + - Voice settings are stored in `system/tts_engines/parler/parler_voices.json` + """ + + HELP_PAGE2 = """ + ## 📝 Important Notes + + - **Voice Consistency** + - Unlike traditional TTS engines, each generation may sound slightly different + - Native voices provide more consistent results + - Custom voice descriptions may vary between generations + + - **Audio Quality Control** + - Include "very clear audio" in description for highest quality + - Use "very noisy audio" for intentional background noise + - Control reverb and recording proximity through descriptions + + - **Best Practices** + - Use punctuation to control speech rhythm + - Be specific in voice descriptions + - Test different description combinations for desired results + - Consider using native voices for consistent output + + ## ⚠️ Known Limitations + + - No support for: + - DeepSpeed acceleration + - Pitch adjustment + - Generation speed control + - Repetition penalty + - Temperature adjustment + - Streaming generation + - Multi-language capabilities + + ## 🔧 System Requirements + + - Runs on all major operating systems: + - Windows + - Linux + - macOS + - Outputs in WAV format + - May show flash attention warnings on Windows (normal behavior) + """ + + VOICE_EDITOR = """ + ## 🎙️ Parler Voice Editor Help + + The Voice Editor allows you to create, manage, and customize voice descriptions for Parler TTS. Each voice consists of a name and a detailed description that defines its characteristics. + """ + + VOICE_EDITOR1 = """ + ## 📝 Creating Voice Descriptions + + ### Basic Structure + - **Voice Name**: A unique identifier for your voice (e.g., "Soothing female", "Angry Tom") + - **Voice Description**: Detailed description of voice characteristics + + ### Description Components + Include these elements for best results: + - Gender and age characteristics + - Speaking style and pace + - Tone and emotional quality + - Audio environment/quality + - Accent or language characteristics (if desired) + + ### Example Descriptions + `A female speaker with a soft, soothing voice. Her tone is gentle and calming, with a slight reverb as if she is in a small, cozy room.` + + `A male speaker with a strong, authoritative voice. His tone is firm and commanding, with a slight bass boost that adds to the depth. He speaks at a moderate pace.` + """ + + VOICE_EDITOR2 = """ + ## 💡 Best Practices + + - **Be Specific**: The more detailed your description, the better the results + - **Include Audio Quality**: Mention desired audio characteristics (clear, reverb, etc.) + - **Consider Pacing**: Specify speaking speed (slow, moderate, fast) + - **Define Personality**: Include emotional and stylistic elements + + ## 🔧 Managing Voices + + - Use **Add/Save Voice** to create new or update existing voices + - **Delete Selected Voice** removes the currently selected voice + - **Clear Text Boxes** resets the input fields + - Select any voice from the dropdown to edit its properties + + ## ⚠️ Important Notes + + - Each generation may sound slightly different even with the same description + - Native voices tend to be more consistent than custom voices + - Changes are saved to the voice configuration file automatically + """ \ No newline at end of file diff --git a/system/tts_engines/parler/parler_settings_page.py b/system/tts_engines/parler/parler_settings_page.py index 41109f5..e2b1fc8 100644 --- a/system/tts_engines/parler/parler_settings_page.py +++ b/system/tts_engines/parler/parler_settings_page.py @@ -4,6 +4,7 @@ import requests import gradio as gr from tqdm import tqdm from pathlib import Path +from .help_content import AllTalkHelpContent 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 @@ -127,11 +128,17 @@ def parler_model_alltalk_settings(model_config_data): 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.") + details_text = gr.Textbox(label="Details", show_label=False, lines=5, interactive=False, value="Configure default settings and voice mappings for the selected TTS engine. Unavailable options are grayed out based on engine capabilities. See the Help section below for detailed information about each setting.") with gr.Row(): submit_button = gr.Button("Update Settings") output_message = gr.Textbox(label="Output Message", interactive=False, show_label=False) submit_button.click(parler_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) + with gr.Accordion("HELP - 🔊 Understanding TTS Engine Default Settings Page", open=False): + with gr.Row(): + gr.Markdown(AllTalkHelpContent.DEFAULT_SETTINGS, elem_classes="custom-markdown") + with gr.Row(): + gr.Markdown(AllTalkHelpContent.DEFAULT_SETTINGS1, elem_classes="custom-markdown") + gr.Markdown(AllTalkHelpContent.DEFAULT_SETTINGS2, elem_classes="custom-markdown") ########################################################################################### # Do not change this section apart from "TTS Engine Name" value to match your engine name # @@ -164,34 +171,12 @@ def parler_model_alltalk_settings(model_config_data): 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.Accordion("HELP - 🔊 Understanding TTS Engine Capabilities", open=False): + with gr.Row(): + gr.Markdown(AllTalkHelpContent.ENGINE_INFORMATION, elem_classes="custom-markdown") + with gr.Row(): + gr.Markdown(AllTalkHelpContent.ENGINE_INFORMATION1, elem_classes="custom-markdown") + gr.Markdown(AllTalkHelpContent.ENGINE_INFORMATION2, elem_classes="custom-markdown") ####################################################################################################################################################################################################### # REQUIRED CHANGE # @@ -347,6 +332,12 @@ def parler_model_alltalk_settings(model_config_data): add_update_button.click(add_update_voice, inputs=[voice_name, description, selected_index, button_label], outputs=[voice_list, voice_name, description, selected_index, voice_name, button_label]) delete_button.click(delete_selected_voice, inputs=selected_index, outputs=[voice_list, voice_name, description, selected_index, voice_name, button_label]) clear_button.click(clear_text_boxes, outputs=[voice_name, description, selected_index, voice_name, button_label]) + with gr.Accordion("HELP - 🎙️ Parler Voice Editor Help", open=False): + with gr.Row(): + gr.Markdown(AllTalkHelpContent.VOICE_EDITOR, elem_classes="custom-markdown") + with gr.Row(): + gr.Markdown(AllTalkHelpContent.VOICE_EDITOR1, elem_classes="custom-markdown") + gr.Markdown(AllTalkHelpContent.VOICE_EDITOR2, elem_classes="custom-markdown") ################################################################################################### # REQUIRED CHANGE # @@ -355,54 +346,10 @@ def parler_model_alltalk_settings(model_config_data): ################################################################################################### with gr.Tab("Engine Help"): with gr.Row(): - gr.Markdown(""" - ### 🟧 Where are the Parler models stored? - This extension will download the models to `/alltalk_tts/models/parler/` folder.
- - ### 🟧 Where are the voices stored for parler models? - Parler is unlike any other TTS engine. It is actually mode like Stable Diffusion, where you write a text based description of what you want your image to look like, but in this case, you describe what you want the voice to sound like. For example `A female speaker with an enthusiastic and lively voice. Her tone is bright and energetic, with a fast pace and a lot of inflection. The audio is clear and vibrant.` - - This means you can have pretty much any voice you want, however, the downside to this is, there is little consistency of the voices, meaning that no 2x TTS generations will sound exactly like the same voice. You can however use one of the `Native` built in voices to give a level of consistency. Please refer to the Parler website for more infomation https://github.com/huggingface/parler-tts.
- - ### 🟧 So how do I create my voices for Parler? - On the settings page for Parler, there is a Voice Editor tab. In there you can add/remove/ammend voices as you wish, or edit the `parler_voices.json` file stored in `system/tts_engines/parler/`. - - ### 🟧 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.
- - ### 🟧 Skipped text/speech - At time of writing, this model is 1x day old and I could see reports of issues with some TTS not being generated, some longer text missing the middle portion of the text etc. You can confirm what AllTalk sent to the Parler TTS engine by looking at the command prompt/terminal window. If your text shows up there, it was sent to the Parler TTS engine. Please refer to the Parler website for more details https://github.com/huggingface/parler-tts. - """) - gr.Markdown(""" - ### 🟧 How do I use Parler's voice generation system? - To create a voice with Parler: - - Describe the voice characteristics in your prompt. For example: `Jon's voice is monotone yet slightly fast in delivery, with a very close recording that almost has no background noise.` - - Use one of the 34 inbuilt `Native` speaker names for a level of consistency: `Aaron, Alisa, Anna, Barbara, Bill, Brenda, Bruce, Carol, David, Eileen, Emily, Eric, Gary, James, Jason, Jenna, Jerry, Jon, Jordan, Joy, Karen, Laura, Lauren, Lea, Mike, Naomie, Patrick, Rebecca, Rick, Rose, Tina, Tom, Will, Yann` - - Control audio quality in the voice characteristics prompt: - - > Include `very clear audio` for highest quality
- > Use `very noisy audio` for high levels of background noise - - Adjust other features in your voice characteristics prompt: - - > Gender
- > Speaking rate
- > Pitch
- > Reverberation - - Remember, while you can create diverse voices, each generation may sound slightly different even with the same description. - - In the text sent for TTS generation, you can use punctuation to control speech rhythm (e.g. use `,` commas for small breaks) - - ### 🟧 UserWarning: 1Torch was not compiled with flash attention - This is a limitation of Windows Pytorch not yet having full flash attention support on later builds of Pytorch (is my understanding at this time). - """) + gr.Markdown(AllTalkHelpContent.HELP_PAGE, elem_classes="custom-markdown") + with gr.Row(): + gr.Markdown(AllTalkHelpContent.HELP_PAGE1, elem_classes="custom-markdown") + gr.Markdown(AllTalkHelpContent.HELP_PAGE2, elem_classes="custom-markdown") return app diff --git a/system/tts_engines/template-tts-engine/available_models.json b/system/tts_engines/template-tts-engine/available_models.json index 42c8c96..e647926 100644 --- a/system/tts_engines/template-tts-engine/available_models.json +++ b/system/tts_engines/template-tts-engine/available_models.json @@ -1,40 +1,33 @@ -{ - "first_start_model": "en_GB-jenny_dioco-medium", - "models": [ - { - "model_name": "ar_JO-kareem-low", - "files_to_download": [ - "https://huggingface.co/rhasspy/piper-voices/resolve/main/ar/ar_JO/kareem/low/ar_JO-kareem-low.onnx?download=true", - "https://huggingface.co/rhasspy/piper-voices/resolve/main/ar/ar_JO/kareem/low/ar_JO-kareem-low.onnx.json?download=true" - ] - }, - { - "model_name": "ar_JO-kareem-medium", - "files_to_download": [ - "https://huggingface.co/rhasspy/piper-voices/resolve/main/ar/ar_JO/kareem/medium/ar_JO-kareem-medium.onnx?download=true", - "https://huggingface.co/rhasspy/piper-voices/resolve/main/ar/ar_JO/kareem/medium/ar_JO-kareem-medium.onnx.json?download=true" - ] - }, - { - "model_name": "vi_VN-vivos-x_low", - "files_to_download": [ - "https://huggingface.co/rhasspy/piper-voices/resolve/main/vi/vi_VN/vivos/x_low/vi_VN-vivos-x_low.onnx?download=true", - "https://huggingface.co/rhasspy/piper-voices/resolve/main/vi/vi_VN/vivos/x_low/vi_VN-vivos-x_low.onnx.json?download=true" - ] - }, - { - "model_name": "zh_CN-huayan-medium", - "files_to_download": [ - "https://huggingface.co/rhasspy/piper-voices/resolve/main/zh/zh_CN/huayan/medium/zh_CN-huayan-medium.onnx?download=true", - "https://huggingface.co/rhasspy/piper-voices/resolve/main/zh/zh_CN/huayan/medium/zh_CN-huayan-medium.onnx.json?download=true" - ] - }, - { - "model_name": "zh_CN-huayan-x_low", - "files_to_download": [ - "https://huggingface.co/rhasspy/piper-voices/resolve/main/zh/zh_CN/huayan/x_low/zh_CN-huayan-x_low.onnx?download=true", - "https://huggingface.co/rhasspy/piper-voices/resolve/main/zh/zh_CN/huayan/x_low/zh_CN-huayan-x_low.onnx.json?download=true" - ] - } - ] -} \ No newline at end of file +{ + "first_start_model": "xttsv2_2.0.3", + "models": [ + { + "model_name": "xttsv2_2.0.3", + "folder_path": "xttsv2_2.0.3", + "files_to_download": { + "LICENSE.txt": "https://huggingface.co/coqui/XTTS-v2/resolve/v2.0.3/LICENSE.txt?download=true", + "README.md": "https://huggingface.co/coqui/XTTS-v2/resolve/v2.0.3/README.md?download=true", + "config.json": "https://huggingface.co/coqui/XTTS-v2/resolve/v2.0.3/config.json?download=true", + "model.pth": "https://huggingface.co/coqui/XTTS-v2/resolve/v2.0.3/model.pth?download=true", + "dvae.pth": "https://huggingface.co/coqui/XTTS-v2/resolve/v2.0.3/dvae.pth?download=true", + "mel_stats.pth": "https://huggingface.co/coqui/XTTS-v2/resolve/v2.0.3/mel_stats.pth?download=true", + "speakers_xtts.pth": "https://huggingface.co/coqui/XTTS-v2/resolve/v2.0.3/speakers_xtts.pth?download=true", + "vocab.json": "https://huggingface.co/coqui/XTTS-v2/resolve/v2.0.3/vocab.json?download=true" + } + }, + { + "model_name": "xttsv2_2.0.2", + "folder_path": "xttsv2_2.0.2", + "files_to_download": { + "LICENSE.txt": "https://huggingface.co/coqui/XTTS-v2/resolve/v2.0.2/LICENSE.txt?download=true", + "README.md": "https://huggingface.co/coqui/XTTS-v2/resolve/v2.0.2/README.md?download=true", + "config.json": "https://huggingface.co/coqui/XTTS-v2/resolve/v2.0.2/config.json?download=true", + "model.pth": "https://huggingface.co/coqui/XTTS-v2/resolve/v2.0.2/model.pth?download=true", + "dvae.pth": "https://huggingface.co/coqui/XTTS-v2/resolve/v2.0.2/dvae.pth?download=true", + "mel_stats.pth": "https://huggingface.co/coqui/XTTS-v2/resolve/v2.0.2/mel_stats.pth?download=true", + "speakers_xtts.pth": "https://huggingface.co/coqui/XTTS-v2/resolve/v2.0.2/speakers_xtts.pth?download=true", + "vocab.json": "https://huggingface.co/coqui/XTTS-v2/resolve/v2.0.2/vocab.json?download=true" + } + } + ] + } \ No newline at end of file diff --git a/system/tts_engines/template-tts-engine/help_content.py b/system/tts_engines/template-tts-engine/help_content.py new file mode 100644 index 0000000..277f974 --- /dev/null +++ b/system/tts_engines/template-tts-engine/help_content.py @@ -0,0 +1,458 @@ +# help_content.py +# pylint: disable=no-member + +class AllTalkHelpContent: + """CSS and help content for help_content.py""" + custom_css = """ + /* Add this to your existing CSS */ + .gradio-container .prose { + max-width: none !important; + padding: 0.5rem !important; /* Reduced padding */ + margin: 0 !important; + } + + .custom-markdown div { + border: none !important; /* Remove the inner border */ + margin-top: 0 !important; /* Remove top margin */ + margin-bottom: 0 !important; /* Remove bottom margin */ + padding-top: 0 !important; /* Remove top padding */ + padding-bottom: 0 !important; /* Remove bottom padding */ + } + + /* Update the existing custom-markdown class */ + .custom-markdown { + font-size: 15px !important; + line-height: 1.6 !important; + color: var(--body-text-color) !important; + background-color: var(--background-fill-primary) !important; + border: 1px solid var(--border-color-primary) !important; + border-radius: 8px !important; + padding: 0.5rem !important; + margin: 0 !important; /* Changed from 1rem 0 to 0 */ + max-width: 100% !important; + box-sizing: border-box !important; + /* Default text color for all content (grey) */ + color: rgba(156, 163, 175, 1) !important; /* Adjust this grey value to match your interface */ + } + + /* Make headings white */ + .custom-markdown h1, + .custom-markdown h2, + .custom-markdown h3, + .custom-markdown h4, + .custom-markdown h5, + .custom-markdown h6 { + color: var(--heading-text-color, var(--body-text-color)) !important; + font-weight: 600 !important; + } + + /* Keep all other elements in the grey color */ + .custom-markdown p, + .custom-markdown li, + .custom-markdown ul, + .custom-markdown ol { + color: rgba(156, 163, 175, 1) !important; /* Same grey as the base text */ + } + + /* Additional targeting for any wrapping elements */ + .gradio-container .prose > * { + margin: 0 !important; + padding: 0 !important; + } + + /* Update the list item spacing in your existing CSS */ + .custom-markdown li { + font-size: 0.95rem !important; + margin-bottom: 0.2rem !important; /* Reduced from 0.5rem to 0.3rem */ + } + + /* Add specific styling for definition-style lists (like your Default, Recommendation, Tip items) */ + .custom-markdown li p { + margin: 0 !important; /* Remove paragraph margins within list items */ + } + + /* If you need even tighter spacing for specific types of lists */ + .custom-markdown li + li { + margin-top: 0.2rem !important; /* Space between consecutive list items */ + } + + /* Ensure nested lists maintain proper spacing */ + .custom-markdown li > ul, + .custom-markdown li > ol { + margin: 0.2rem 0 0.2rem 1rem !important; /* Reduced from 0.5rem */ + } + + /* Consistent heading styles */ + .custom-markdown h2 { + font-size: 1.2rem !important; /* Fixed size relative to root */ + font-weight: 600 !important; + margin: 1.5rem 0 1rem !important; + color: var(--heading-text-color, var(--body-text-color)) !important; + border-bottom: 1px solid var(--border-color-primary) !important; + padding-bottom: 0.5rem !important; + } + + /* First paragraph styling */ + .custom-markdown p:first-of-type { + font-size: 0.95rem !important; /* Match base size */ + font-weight: 400 !important; + margin-bottom: 1rem !important; + } + + /* Regular paragraphs */ + .custom-markdown p { + font-size: 0.95rem !important; + margin: 0.8rem 0 !important; + } + + /* List styling */ + .custom-markdown ul, + .custom-markdown ol { + margin: 0.8rem 0 !important; + padding-left: 1.5rem !important; + } + + /* Code styling */ + .custom-markdown code { + font-family: ui-monospace, monospace !important; + background-color: var(--background-fill-secondary) !important; + padding: 0.2rem 0.4rem !important; + border-radius: 4px !important; + font-size: 0.8rem !important; /* Slightly smaller than regular text */ + color: var(--body-text-color) !important; + } + + /* Add styles for bold and emphasis */ + .custom-markdown strong { + font-weight: 600 !important; + color: var(--body-text-color) !important; + } + + .custom-markdown em { + font-style: italic !important; + } + + /* Ensure both columns take equal width */ + .gradio-row > div { + flex: 1 1 50% !important; + min-width: 0 !important; /* Prevents flex items from overflowing */ + } + """ + + ENGINE_INFORMATION = """ + ## 🔊 TTS Engine Capabilities Help + + This guide explains the various capabilities that **may** be available in different TTS engines and models. Each capability affects how the TTS engine processes and generates speech output. + """ + + ENGINE_INFORMATION1 = """ + ## 🚀 Performance Features + + - **DeepSpeed Capable**: Enables GPU acceleration using the DeepSpeed optimization library. + - **Requires**: NVIDIA GPU with CUDA support + - **Benefit**: Significantly faster text-to-speech generation + - **Note**: Model must specifically support DeepSpeed inference + + - **Low VRAM Capable**: Optimized for systems with limited GPU memory. + - **Benefit**: Efficient memory management between CPU and GPU + - **Use Case**: Ideal when running alongside other GPU-intensive applications like LLMs + - **Note**: May trade speed for memory efficiency + + - **Streaming Capable**: Enables real-time speech generation. + - **Benefit**: Immediate playback without generating entire audio first + - **Use Case**: Interactive applications and real-time responses + - **Note**: Not all output formats support streaming + + ## 🎵 Voice Control Features + + - **Pitch Capable**: Allows adjustment of voice pitch. + - **Benefit**: Creates more expressive and varied speech output + - **Use Case**: Customizing voice characteristics + - **Note**: Adjustment range varies by model + + - **Generation Speed Capable**: Controls speech rate. + - **Benefit**: Adjustable playback speed during generation + - **Use Case**: Creating faster or slower speech output + - **Note**: May affect audio quality at extreme settings + + - **Temperature Capable**: Controls output randomness. + - **Benefit**: Adjustable speech variation and creativity + - **Use Case**: Balancing consistency vs. naturality + - **Note**: Higher values increase variation but may reduce quality + """ + + ENGINE_INFORMATION2 = """ + ## 🎯 Quality Enhancement Features + + - **Repetition Penalty Capable**: Prevents unnatural speech patterns. + - **Benefit**: Reduces repeated sounds and phrases + - **Use Case**: Improving natural flow of longer texts + - **Note**: Strength of penalty may be adjustable + + ## 🌐 Multi-Feature Support + + - **Multi-Languages Capable Models**: Each model Supports multiple languages. + - **Benefit**: Generate speech in different languages + - **Note**: Quality may vary between languages + - **Tip**: Check model-specific language support + + - **Multi-Voice Capable**: Supports multiple speaking voices. + - **Benefit**: Different voices or speaking styles + - **Types**: Pre-trained voices or voice cloning + - **Note**: Voice quality may vary by model + + - **Multi-Model Capable Engine**: Supports multiple TTS models. + - **Benefit**: Flexibility in model selection + - **Use Case**: Switching between models for different needs + - **Note**: Each model may have different capabilities + + ## 📁 Technical Features + + - **Default Audio Output Format**: Specifies output file format. + - **Common Formats**: WAV, MP3, FLAC, Opus, AAC, PCM + - **Note**: Transcoding to different formats adds processing time + - **Warning**: Not all formats support streaming + + - **Platform Support**: Operating system compatibility. + - **Platforms**: Windows, Linux, macOS + - **Note**: Additional setup may be required + - **Warning**: Support level may vary by platform + """ + + DEFAULT_SETTINGS = """ + ## 🎯 TTS Engine Settings Help + + This guide explains the settings and configuration options available for individual Text-to-Speech engines within AllTalk. + """ + + DEFAULT_SETTINGS1 = """ + ## 🎚️ Engine Capabilities & Controls + + - **Low VRAM Mode** + - Optimizes memory usage for systems with limited GPU memory + - Efficiently manages resources by moving data between CPU and GPU + - Recommended for systems with less than 4GB VRAM or when running alongside other GPU-intensive applications + + - **DeepSpeed Capability** + - Accelerates TTS generation using optimized inference + - Only available for engines and models that support DeepSpeed + - Requires NVIDIA GPU with CUDA support + + - **Temperature Control** + - Adjusts the variability in speech generation + - **Range**: 0.0 to 1.0 + - **Lower values** (0.1-0.5): More consistent, stable output + - **Higher values** (0.6-1.0): More variable, potentially more natural-sounding output + + - **Repetition Penalty** + - Helps prevent repetitive speech patterns + - **Range**: 1.0 to 15.0 + - Higher values more strongly discourage repetition + - Typically most effective between 1.0-3.0 + + - **Pitch Adjustment** + - Modifies the voice pitch when supported + - **Range**: -20 to +20 + - Use subtle adjustments for most natural results + + - **Generation Speed** + - Controls the pace of generated speech + - **Range**: 0.5 to 2.0 + - 1.0 represents normal speed + """ + + DEFAULT_SETTINGS2 = """ + + ## 🗣️ Voice Configuration + + ### OpenAI Voice Mappings + - Only relevant when using the OpenAI-compatible API endpoint + - Maps OpenAI's six standard voices to equivalent voices in the current engine: + - `alloy` + - `echo` + - `fable` + - `nova` + - `onyx` + - `shimmer` + - Essential for maintaining compatibility with OpenAI API calls + - Each mapping can be customized to any available voice in the current engine + + Information on the OpenAI Endpoint is available in the [Github Wiki](https://github.com/erew123/alltalk_tts/wiki) + + ### Default Voices + - **Default/Character Voice**: Used when no specific voice is specified in API calls + - **Narrator Voice**: Separate default for narrator-specific text + - These defaults are engine-specific and won't affect other TTS engines + - Can be overridden by explicitly specifying voices in API calls + + ## ⚙️ Important Notes + + - Settings availability is determined by engine capabilities + - Grayed-out options indicate features not supported by the current engine + - Changes only affect the currently selected TTS engine + - Settings here act as defaults but can be overridden via API parameters + - All changes require clicking "Update Settings" to take effect + - Some settings require an engine reload to take effect + """ + + HELP_PAGE = """ + ## 🎯 XTTS Voice Cloning Engine Help + + XTTS is a powerful multi-speaker, voice cloning model that can generate speech in multiple languages using short audio samples for voice cloning. + """ + + HELP_PAGE1 = """ + ## 🎚️ Engine Capabilities & Controls + + - **DeepSpeed Support** + - Accelerates generation using optimized inference + - Requires NVIDIA GPU with CUDA support + - 2-3x speed improvement in generation + - Recommended when available + + - **Multi-Language Support** + - Clone voices across multiple languages + - No need for language-specific training + - Full Hindi support in v2.0.3 (API Local method only) + - Natural-sounding output in target language + + - **Generation Methods** + - API Local: Standard generation method + - XTTSv2 Local: Enhanced method with DeepSpeed support + - Each method may produce slightly different results + - Try both methods if voice quality isn't optimal + + ## 🎤 Voice Sample Creation Guide + + ### Audio Requirements + - Format: WAV or MP3 + - Sample Rate: 22050Hz + - Channels: Mono + - Bit Depth: 16-bit + - Duration: 6-30 seconds (optimal: 8-10 seconds) + + ### Recording Tips + 1. **Audio Quality** + - Choose clear, clean recordings + - Avoid background noise, music, or echoes + - Watch for audio artifacts like hiss or hum + - Ensure consistent volume levels + + 2. **Speech Content** + - Use flowing, natural speech + - Avoid long pauses or gaps + - Include some emotional range + - Avoid breathing sounds at start/end + - Choose samples with good vocal variety + + ### Using Audacity + 1. **Basic Editing** + - Open your audio file in Audacity + - Select the best 6-30 second segment + - Remove unwanted sounds or silence + + 2. **Processing Steps** + ``` + 1. Clean up audio if needed (noise reduction, normalization) + 2. Tracks > Resample to 22050Hz + 3. Tracks > Mix > Stereo to Mono + 4. File > Export Audio as WAV + ``` + + ## 🗣️ Voice Management + + ### Single Voice Setup + - Place WAV/MP3 files in `/alltalk_tts/voices/` + - Use descriptive filenames (e.g., `broadcaster_male.wav`) + - Files appear as individual voices in interface + - Best for quick testing or simple use cases + + ### Multi-Voice Sets + - Create folders in `/alltalk_tts/voices/xtts_multi_voice_sets/` + - Add multiple samples per voice + - System randomly selects up to 5 samples + - Better for consistent voice reproduction + - Example: `/voices/xtts_multi_voice_sets/broadcaster_voice/` + + ### Pre-computed Latents + - Stored in `/voices/xtts_latents/` + - Faster generation times + - Reduced memory usage + - Recommended for frequently used voices + """ + + HELP_PAGE2 = """ + ## 📝 Model Versions + + ### v2.0.3 (Recommended) + - Latest stable release + - Full language support including Hindi + - Best overall performance + - Complete feature set + + ### v2.0.2 (Alternative) + - Stable fallback option + - Good performance + - Use if v2.0.3 has issues + + ### Required Files + - `config.json`: Model configuration + - `model.pth`: Core model weights + - `mel_stats.pth`: Audio statistics + - `speakers_xtts.pth`: Speaker embeddings + - `vocab.json`: Tokenizer vocabulary + - `dvae.pth`: Discrete VAE weights + + ## 🔍 Troubleshooting Voice Quality + + If voice cloning results aren't satisfactory: + + 1. **Audio Quality Issues** + - Verify sample meets technical requirements + - Check for background noise + - Ensure proper resampling + - Try processing audio to improve clarity + + 2. **Generation Options** + - Try both API Local and XTTSv2 Local methods + - Adjust temperature settings + - Use multiple voice samples + - Consider creating latents + + 3. **Advanced Solutions** + - Try model finetuning (see Github Wiki) + - Use RVC pipeline for additional processing + - Test different voice samples + - Remember: 100% match isn't possible + + ## 📁 File Management + + - **Models**: `/alltalk_tts/models/xtts/` + - **Voice Samples**: `/alltalk_tts/voices/` + - **Multi-Voice Sets**: `/alltalk_tts/voices/xtts_multi_voice_sets/` + - **Latents**: `/alltalk_tts/voices/xtts_latents/` + - **Outputs**: `/alltalk_tts/outputs/` + - Automatic cleanup available + - Configurable retention period + - Set to 'Disabled' to keep all files + + ## 💡 Best Practices + + 1. **Sample Selection** + - Use high-quality recordings + - Choose emotionally varied speech + - Avoid background noise + - Test multiple samples + + 2. **Performance** + - Enable DeepSpeed when available + - Use pre-computed latents + - Consider multi-voice sets + - Monitor system resources + + 3. **Workflow** + - Process audio before use + - Test both generation methods + - Keep original samples organized + - Regular cleanup of output files + """ \ No newline at end of file diff --git a/system/tts_engines/template-tts-engine/model_settings.json b/system/tts_engines/template-tts-engine/model_settings.json index a8674dc..a770754 100644 --- a/system/tts_engines/template-tts-engine/model_settings.json +++ b/system/tts_engines/template-tts-engine/model_settings.json @@ -1,42 +1,42 @@ -{ - "model_details": { - "manufacturer_name": "MyTTSEngine", - "manufacturer_website": "https://github.com/MyTTSEngine", - "model_description": "A description and detail about the TTS Engine" - }, - "model_capabilties": { - "audio_format": "wav", - "deepspeed_capable": false, - "generationspeed_capable": false, - "languages_capable": false, - "lowvram_capable": false, - "multimodel_capable": false, - "repetitionpenalty_capable": false, - "streaming_capable": false, - "temperature_capable": false, - "multivoice_capable": false, - "pitch_capable": false, - "windows_capable": true, - "linux_capable": true, - "mac_capable": true - }, - "settings": { - "def_character_voice": "defaultvoice.wav", - "def_narrator_voice": "defaultvoice.wav", - "deepspeed_enabled": false, - "engine_installed": true, - "generationspeed_set": 1, - "lowvram_enabled": false, - "pitch_set": 0, - "repetitionpenalty_set": 10, - "temperature_set": 0.75 - }, - "openai_voices": { - "alloy": "defaultvoice.wav", - "echo": "defaultvoice.wav", - "fable": "defaultvoice.wav", - "nova": "defaultvoice.wav", - "onyx": "defaultvoice.wav", - "shimmer": "defaultvoice.wav" - } -} +{ + "model_details": { + "manufacturer_name": "MYNEWTTS", + "manufacturer_website": "https://something here", + "model_description": "A bit of blurb about the manufacturer/TTS engine" + }, + "model_capabilties": { + "audio_format": "wav", + "deepspeed_capable": true, + "generationspeed_capable": true, + "languages_capable": true, + "lowvram_capable": true, + "multimodel_capable": true, + "repetitionpenalty_capable": true, + "streaming_capable": true, + "temperature_capable": true, + "multivoice_capable": true, + "pitch_capable": false, + "windows_capable": true, + "linux_capable": true, + "mac_capable": true + }, + "settings": { + "def_character_voice": "female_01.wav", + "def_narrator_voice": "male_01.wav", + "deepspeed_enabled": true, + "engine_installed": true, + "generationspeed_set": 1, + "lowvram_enabled": false, + "pitch_set": 0, + "repetitionpenalty_set": 10, + "temperature_set": 0.75 + }, + "openai_voices": { + "alloy": "female_01.wav", + "echo": "female_01.wav", + "fable": "female_01.wav", + "nova": "female_01.wav", + "onyx": "female_01.wav", + "shimmer": "female_01.wav" + } +} \ No newline at end of file diff --git a/system/tts_engines/template-tts-engine/modelname_settings_page.py b/system/tts_engines/template-tts-engine/modelname_settings_page.py index 7d5fb28..8fb2b4f 100644 --- a/system/tts_engines/template-tts-engine/modelname_settings_page.py +++ b/system/tts_engines/template-tts-engine/modelname_settings_page.py @@ -1,308 +1,290 @@ -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 find/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 can pretty much find/replace "myttsengine" with the name of your TTS engine (about 14 entries in here) -# -# 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 myttsengine_voices_file_list(): - voices = [] - models_dir = main_dir / "models" / "myttsengine" - - # Basically the same way you are finding/reporting available voices in your model_engine.py file - - return voices - -###################################################### -# 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 myttsengine_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 myttsengine_model_alltalk_settings(model_config_data): - features_list = model_config_data['model_capabilties'] - voice_list = myttsengine_voices_file_list() - with gr.Blocks(title="myttsengine 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(myttsengine_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="myttsengine", 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. - """) - - ####################################################################################################################################################################################################### - # REQUIRED CHANGE # - # You will need to build a custom method to identify if your models are installed and download them. Store your models list in the available_models.json file, alongside your model_settings.json etc # - ####################################################################################################################################################################################################### - 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") - - def download_model(model_name, force_download=False): - # 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", "myttsengine") - 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, os.path.basename(url.split('?')[0]))) for url in files_to_download) - if all_files_exists and not force_download: - 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 url in files_to_download: - file_name = os.path.basename(url.split('?')[0]) - file_path = os.path.join(folder_path, file_name) - if not os.path.exists(file_path) or force_download: - print(f"Downloading {file_name}...") - 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!" - - with gr.Row(): - download_status = gr.Textbox(label="Download Status") - - 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", "myttsengine", os.path.basename(file.split('?')[0]))) 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_status_text = download_model(model_name, force_download=True) - return [gr.update(visible=False), gr.update(visible=True), gr.update(visible=False), download_status_text] - - def cancel_download(): - 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, inputs=model_dropdown, outputs=[confirm_button, download_button, cancel_button]) - confirm_button.click(confirm_download, inputs=model_dropdown, outputs=[confirm_button, download_button, cancel_button, download_status]) - cancel_button.click(cancel_download, inputs=None, outputs=[confirm_button, download_button, cancel_button, download_status]) - - ################################################################################################### - # 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(): - using_voice_samples = """ - - - IF YOU WANT TO ADD HELP INSTRUCTIONS FOR THE MODEL, ADD THEM IN HERE USING MARKDOWN - - - """ - gr.Markdown(using_voice_samples) - - 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 myttsengine_at_gradio_settings_page(model_config_data): - app = myttsengine_model_alltalk_settings(model_config_data) - return app -def myttsengine_at_gradio_settings_page(model_config_data): - app = myttsengine_model_alltalk_settings(model_config_data) - return app \ No newline at end of file +import os +import json +import requests +import gradio as gr +from tqdm import tqdm +from pathlib import Path +from .help_content import AllTalkHelpContent +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", analytics_enabled=False) 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="Configure default settings and voice mappings for the selected TTS engine. Unavailable options are grayed out based on engine capabilities. See the Help section below for detailed information about each setting.") + with gr.Row(): + submit_button = gr.Button("Update Settings") + output_message = gr.Textbox(label="Output Message", interactive=False, show_label=False) + with gr.Accordion("HELP - 🔊 Understanding TTS Engine Default Settings Page", open=False): + with gr.Row(): + gr.Markdown(AllTalkHelpContent.DEFAULT_SETTINGS, elem_classes="custom-markdown") + with gr.Row(): + gr.Markdown(AllTalkHelpContent.DEFAULT_SETTINGS1, elem_classes="custom-markdown") + gr.Markdown(AllTalkHelpContent.DEFAULT_SETTINGS2, elem_classes="custom-markdown") + 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(): + with gr.Accordion("HELP - 🔊 Understanding TTS Engine Capabilities", open=False): + with gr.Row(): + gr.Markdown(AllTalkHelpContent.ENGINE_INFORMATION, elem_classes="custom-markdown") + with gr.Row(): + gr.Markdown(AllTalkHelpContent.ENGINE_INFORMATION1, elem_classes="custom-markdown") + gr.Markdown(AllTalkHelpContent.ENGINE_INFORMATION2, elem_classes="custom-markdown") + + + 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(AllTalkHelpContent.HELP_PAGE, elem_classes="custom-markdown") + with gr.Row(): + gr.Markdown(AllTalkHelpContent.HELP_PAGE1, elem_classes="custom-markdown") + gr.Markdown(AllTalkHelpContent.HELP_PAGE2, elem_classes="custom-markdown") + + 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 diff --git a/system/tts_engines/template-tts-engine/template_engine.py b/system/tts_engines/template-tts-engine/template_engine.py new file mode 100644 index 0000000..f95c5a9 --- /dev/null +++ b/system/tts_engines/template-tts-engine/template_engine.py @@ -0,0 +1,887 @@ +""" +Version: 1.0 +Last Updated: 2024 + +Implementation Notes: +- All core system variables must be maintained +- All functions prefixed with "DONT CHANGE" must remain unmodified +- Code additions should be placed between the marked sections in each function +- Debug messages use self.print_message() with appropriate message types +- All functions must implement self.debug_func_entry() for trace logging + +Note: Text between "↑↑↑ Keep everything above this line ↑↑↑" and "↓↓↓ Keep everything below this line ↓↓↓" +markers **TYPICALLY** must remain unchanged as it contains critical system integration code. + +Note: You can add new functions, just DONT remove the functions that are already there, even if they +are doing nothing as `tts_server.py` will still look for their existance and fail if they are missing. +""" + +######################################## +# Default imports # Do not change this # +######################################## +import os +import gc +import sys +import glob +import json +import time +import inspect +import torch +import logging +from pathlib import Path +from fastapi import (HTTPException) +logging.disable(logging.WARNING) + +# Confguration file management for confignew.json +try: + from .config import AlltalkConfig, AlltalkTTSEnginesConfig, AlltalkNewEnginesConfig # TGWUI import +except ImportError: + from config import AlltalkConfig, AlltalkTTSEnginesConfig, AlltalkNewEnginesConfig # Standalone import + +def initialize_configs(): + """Initialize all configuration instances""" + config = AlltalkConfig.get_instance() + tts_engines_config = AlltalkTTSEnginesConfig.get_instance() + new_engines_config = AlltalkNewEnginesConfig.get_instance() + return config, tts_engines_config, new_engines_config + +# Load in the central config management +config, tts_engines_config, new_engines_config = initialize_configs() + +###################################################### +# Get Pytorch & Python versions # Do not change this # +###################################################### +pytorch_version = torch.__version__ +cuda_version = torch.version.cuda +major, minor, micro = sys.version_info[:3] +python_version = f"{major}.{minor}.{micro}" + +############################################ +# DeepSpeed imports # POSSIBLY change this # +############################################ +""" +If the new TTS engine you are importing doesnt support DeepSpeed, then +you can simply change `model_supports_deepspeed_true_or_false` to `False` +This will be much faster when starting up the engine. This is seperate +from what is stored in your `model_settings.json` file. +""" +# ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ +# ↓↓↓ MODIFY THIS LINE ↓↓↓ +# ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ + +model_supports_deepspeed_true_or_false = False + +# ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ +# ↑↑↑ MODIFY THIS LINE ↑↑↑ +# ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ + +if model_supports_deepspeed_true_or_false: + try: + import deepspeed + deepspeed_available = True + except ImportError: + deepspeed_available = False + pass + +####################################################### +# TTS Engine-Specific Imports and Setup # Change this # +####################################################### +""" +This section is for TTS engine specific imports and global variable setup. + +Guidelines: +1. Import all required modules for your TTS engine +2. Handle import errors with appropriate error messages +3. Set up any global variables or configurations needed by your engine +4. Use try/except blocks to gracefully handle missing dependencies +5. Ensure all error messages follow the AllTalk format for print messages. + At this stage the `def print_messages` is not available, so please use + standard `print("[AllTalk ENG] some message here")` messages. + +Example structure: + +try: + # Import your TTS engine's required modules + from your_tts_engine import required_modules +except ModuleNotFoundError: + # Handle missing dependencies with helpful error messages + print("Missing required modules. Please install...") + raise + +""" +try: + import torchaudio +except ModuleNotFoundError: + brand = "[AllTalk ENG]" + print(f"{brand} \033[91mError\033[0m Could not find the XXX TTS modules.") + print(f"{brand} \033[91mError\033[0m Please re-install the requirements.") + raise + + +######################################################### +# Class setup # Change the relevant functions as needed # +######################################################### +class tts_class: + """ + TTS Engine Implementation Class + + This class provides the interface between `tts_server.py` and whatever TTS engine you install. + It handles model loading, voice management, and TTS generation in both streaming + and non-streaming modes. Streaming will only be supported if the underlying TTS engine + actually supports streaming. + + Key Responsibilities: + 1. Model Management: + - Loading/unloading models + - Managing model state between CPU and GPU + - Handling DeepSpeed integration + + 2. Voice Management: + - Managing voice samples or model files + + 3. TTS Generation: + - Converting text to speech + - Supporting streaming output (If the engine supports it) + - Managing generation parameters + + 4. System Integration: + - Implementing standard AllTalk interfaces + - Managing engine state and configuration + - Handling resource allocation + """ + + ############################################### + # Central print function # Do not change this # + ############################################### + def print_message(self, message, message_type="standard", component="ENG"): + """ + Centralized print function for messages. Use this for print output to console. + As this is the model Engine, all `component` printouts are set to ENG as default. + + Args: + message (str): The message to print + message_type (str): Type of message (standard/warning/error/debug_*/debug) + component (str): Component identifier (TTS/ENG/GEN/API/etc.) + + Example Use: + self.print_message("This is a standard print out mesage to a user) + self.print_message("This is a debug_tts message, message_type="debug_tts") + self.print_message("This is an error message to a user, message_type="error") + self.print_message("This is an warning message to a user, message_type="warning") + + Debug Types: + debug_func: Tracks function entry + debug_tts: Enable TTS process debugging + debug_tts_variables: Enable variable state debugging + + WARNING: This is a core system function. Do not modify its implementation + as it provides standardized version reporting across all engines. + """ + # ANSI color codes + BLUE = "\033[94m" + MAGENTA = "\033[95m" + YELLOW = "\033[93m" + RED = "\033[91m" + GREEN = "\033[92m" + RESET = "\033[0m" + prefix = f"[{config.branding}{component}] " + if message_type.startswith("debug_"): + debug_flag = getattr(config.debugging, message_type, False) + if not debug_flag: + return + if message_type == "debug_func" and "Function entry:" in message: + message_parts = message.split("Function entry:", 1) + print(f"{prefix}{BLUE}Debug{RESET} {YELLOW}{message_type}{RESET} Function entry:{GREEN}{message_parts[1]}{RESET} in model_engine") + else: + print(f"{prefix}{BLUE}Debug{RESET} {YELLOW}{message_type}{RESET} {message}") + elif message_type == "debug": + print(f"{prefix}{BLUE}Debug{RESET} {message}") + elif message_type == "warning": + print(f"{prefix}{YELLOW}Warning{RESET} {message}") + elif message_type == "error": + print(f"{prefix}{RED}Error{RESET} {message}") + else: + print(f"{prefix}{message}") + + def debug_func_entry(self): + """Log function entry if debug_func is enabled.""" + if config.debugging.debug_func: + current_func = inspect.currentframe().f_back.f_code.co_name + self.print_message(f"Function entry: {current_func}", "debug_func") + + ############################################# + # Script initalisation # Do not change this # + ############################################# + def __init__(self): + """ + Initialize the TTS engine instance. + + WARNING: This class requires specific variables to interface with AllTalk's main system (tts_server.py). + Do not remove or rename any of the predefined variables as they are required for proper system integration. + + Required System Interface Variables: + + 1. Core System Variables (DO NOT MODIFY): + Base Configuration: + - self.this_dir: Engine directory path (where this script is located) + - self.main_dir: AllTalk root directory + - self.device: Processing device ("cuda" or "cpu") + - self.cuda_is_available: Whether GPU/CUDA is available + + State Tracking: + - self.tts_generating_lock: Prevents concurrent generation requests + - self.tts_stop_generation: Signals generation stop request + - self.tts_narrator_generatingtts: Tracks narrator mode for optimization + - self.model: Active TTS model instance + - self.is_tts_model_loaded: Whether a model is currently loaded + - self.current_model_loaded: Name of currently loaded model + - self.available_models: List of models found by scan_models_folder + - self.setup_has_run: Tracks if setup() has completed + + 2. Engine Configuration Variables (DO NOT MODIFY): + - self.engines_available: List of all available TTS engines + - self.engine_loaded: Currently selected TTS engine + - self.selected_model: Currently selected model name + + 3. Model Settings (SET VIA model_settings.json): + Capability Flags: + - self.audio_format: Output audio format (wav, mp3, etc.) + - self.deepspeed_capable: DeepSpeed acceleration support + - self.generationspeed_capable: Speed adjustment support + - self.languages_capable: Multi-language support + - self.lowvram_capable: Low VRAM mode support + - self.multimodel_capable: Multiple model support + - self.repetitionpenalty_capable: Repetition penalty support + - self.streaming_capable: Audio streaming support + - self.temperature_capable: Temperature adjustment support + - self.multivoice_capable: Multiple voice support + - self.pitch_capable: Pitch adjustment support + + Engine Settings: + - self.def_character_voice: Default character voice + - self.def_narrator_voice: Default narrator voice + - self.deepspeed_enabled: DeepSpeed status + - self.engine_installed: Engine installation status + - self.generationspeed_set: Current speed setting + - self.lowvram_enabled: Low VRAM mode status + - self.repetitionpenalty_set: Current repetition penalty + - self.temperature_set: Current temperature setting + - self.pitch_set: Current pitch setting + + OpenAI Voice Mappings: + - self.openai_alloy: Alloy voice mapping + - self.openai_echo: Echo voice mapping + - self.openai_fable: Fable voice mapping + - self.openai_nova: Nova voice mapping + - self.openai_onyx: Onyx voice mapping + - self.openai_shimmer: Shimmer voice mapping + + Integration Requirements: + - All variables must be present even if unused by your engine + - Capability flags should accurately reflect engine features + - Settings should have sensible defaults even if not used + - OpenAI mappings should be set even if not supporting OpenAI compatibility + + Note: Variables marked (DO NOT MODIFY) are critical system integration points. + Other variables should be configured through their respective JSON files or + you can add new central variables in the section provided down below. + """ + # DO NOT MODIFY - Sets up the base variables required for any tts engine # + self.this_dir = Path(__file__).parent.resolve() + self.main_dir = Path(__file__).parent.parent.parent.parent.resolve() + self.device = "cuda" if torch.cuda.is_available() else "cpu" + self.cuda_is_available = torch.cuda.is_available() + self.tts_generating_lock = False + self.tts_stop_generation = False + self.tts_narrator_generatingtts = False + self.model = None + self.is_tts_model_loaded = False + self.current_model_loaded = None + self.available_models = None + self.setup_has_run = False + self.engines_available = tts_engines_config.get_engine_names_available() + self.engine_loaded = tts_engines_config.engine_loaded + self.selected_model = tts_engines_config.selected_model + + # DO NOT MODIFY - Load in the current TTS Engines model_settings.json file + with open(os.path.join(self.this_dir, "model_settings.json"), "r") as f: + model_settings_file = json.load(f) + + # DO NOT MODIFY - Model details from model_settings.json + self.manufacturer_name = model_settings_file["model_details"]["manufacturer_name"] + self.manufacturer_website = model_settings_file["model_details"]["manufacturer_website"] + + # DO NOT MODIFY - Model capabilities from model_settings.json + self.audio_format = model_settings_file["model_capabilties"]["audio_format"] + self.deepspeed_capable = model_settings_file["model_capabilties"]["deepspeed_capable"] + self.deepspeed_available = 'deepspeed' in globals() + self.generationspeed_capable = model_settings_file["model_capabilties"]["generationspeed_capable"] + self.languages_capable = model_settings_file["model_capabilties"]["languages_capable"] + self.lowvram_capable = model_settings_file["model_capabilties"]["lowvram_capable"] + self.multimodel_capable = model_settings_file["model_capabilties"]["multimodel_capable"] + self.repetitionpenalty_capable = model_settings_file["model_capabilties"]["repetitionpenalty_capable"] + self.streaming_capable = model_settings_file["model_capabilties"]["streaming_capable"] + self.temperature_capable = model_settings_file["model_capabilties"]["temperature_capable"] + self.multivoice_capable = model_settings_file["model_capabilties"]["multivoice_capable"] + self.pitch_capable = model_settings_file["model_capabilties"]["pitch_capable"] + + # DO NOT MODIFY - Engine settings from model_settings.json + self.def_character_voice = model_settings_file["settings"]["def_character_voice"] + self.def_narrator_voice = model_settings_file["settings"]["def_narrator_voice"] + self.deepspeed_enabled = model_settings_file["settings"]["deepspeed_enabled"] + self.engine_installed = model_settings_file["settings"]["engine_installed"] + self.generationspeed_set = model_settings_file["settings"]["generationspeed_set"] + self.lowvram_enabled = model_settings_file["settings"]["lowvram_enabled"] + self.lowvram_enabled = False if not torch.cuda.is_available() else self.lowvram_enabled + self.repetitionpenalty_set = model_settings_file["settings"]["repetitionpenalty_set"] + self.temperature_set = model_settings_file["settings"]["temperature_set"] + self.pitch_set = model_settings_file["settings"]["pitch_set"] + + # DO NOT MODIFY - OpenAI voice mappings from model_settings.json + self.openai_alloy = model_settings_file["openai_voices"]["alloy"] + self.openai_echo = model_settings_file["openai_voices"]["echo"] + self.openai_fable = model_settings_file["openai_voices"]["fable"] + self.openai_nova = model_settings_file["openai_voices"]["nova"] + self.openai_onyx = model_settings_file["openai_voices"]["onyx"] + self.openai_shimmer = model_settings_file["openai_voices"]["shimmer"] + + """ + Below is the name of the folder that will be created-used under `/models/{folder}` + And is further used by the base functions of this script. Change the name stored in + `self.model_folder_name` to the model folder name you will be using. Ensure to use + the correct CAPS/Non-CAPS spelling as Linux OS is CAPS specific on folder names. + """ + # ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ + # ↓↓↓ MODIFY THIS LINE ↓↓↓ + # ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ + + self.model_folder_name = "MYNEWTTSENGINE" + + # ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ + # ↑↑↑ MODIFY THIS LINE ↑↑↑ + # ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ + + # ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ + # ↓↓↓ Add your own central `self.myvariable` variables in here if needed for your engine ↓↓↓ + # ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ + """ + If you need globally accessable variables of your own for your own purposes, you can put + them in here as self.myvariable = "whatever". + + """ + # ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ + # ↑↑↑ Add your own central `self.myvariable` variables in here if needed for your engine ↑↑↑ + # ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ + + # DO NOT MODIFY - log the function call to this function + self.debug_func_entry() + + ##################################################### + # Printout engine loading bits # Do not change this # + ##################################################### + def printout_versions(self): + """ + Print Python, DeepSpeed, Pytorch and CUDA version on start-up. + + WARNING: This is a core system function. Do not modify its implementation + as it provides standardized version reporting across all engines. + """ + self.debug_func_entry() + if not model_supports_deepspeed_true_or_false: + self.print_message(f"\033[92mDeepSpeed version :\033[93m Not supported on {self.model_folder_name}\033[0m", message_type="standard") + else: + if deepspeed_available: + self.print_message("\033[92mDeepSpeed version :\033[93m " + str(deepspeed.__version__) + "\033[0m", message_type="standard") + else: + self.print_message("\033[92mDeepSpeed version :\033[93m Not available\033[0m", message_type="standard") + self.print_message(f"\033[92mPython Version :\033[93m {python_version}\033[0m", message_type="standard") + self.print_message(f"\033[92mPyTorch Version :\033[93m {pytorch_version}\033[0m", message_type="standard") + if cuda_version is None: + self.print_message("\033[92mCUDA Version :\033[91m Not available\033[0m", message_type="standard") + else: + self.print_message(f"\033[92mCUDA Version :\033[93m {cuda_version}\033[0m", message_type="standard") + + self.print_message("", message_type="standard") + return + + ################################################################ + # Handle low VRAM change between CUDA/CPU # Do not change this # + ################################################################ + async def handle_lowvram_change(self): + """ + Manage model location between CPU and GPU memory for low VRAM operation. + + This function handles the movement of models between CPU and GPU memory + to support systems with limited VRAM. It's called automatically during + generation when low VRAM mode is enabled. + + Operation: + 1. Checks CUDA availability + 2. Moves model between devices based on current location: + - GPU (cuda) -> CPU + - CPU -> GPU (cuda) + 3. Manages CUDA cache to optimize memory usage + + States Affected: + - self.device: Updated to reflect current processing device + - self.model.device: Model's current memory location + + Requirements: + - CUDA must be available for GPU operations + - Model must be loaded (self.model is not None) + - lowvram_enabled must be `True` in the `model_settings.json` file + + Note: This function is only called when self.lowvram_enabled is True + meaning the engine does or doesnt support the call, hence if its not + True, then this function would never be called anyway, so doesnt need + changing. + """ + self.debug_func_entry() + + # Initial validation + if not self.is_tts_model_loaded: + self.print_message("No model is currently loaded. Please select a model to load.", message_type="error") + raise HTTPException(status_code=400, detail="No model is currently loaded. Please select a model to load.") + + if torch.cuda.is_available(): + if self.device == "cuda": + self.print_message("Moving model to CPU", message_type="debug_tts") + self.device = "cpu" + self.model.to(self.device) + torch.cuda.empty_cache() + gc.collect() + else: + self.device = "cuda" + self.print_message("Moving model to GPU", message_type="debug_tts") + self.model.to(self.device) + gc.collect() + + ################################################ + # Handle DeepSpeed change # Do not change this # + ################################################ + async def handle_deepspeed_change(self, value): + """ + Handle enabling/disabling of DeepSpeed acceleration. + + This function manages the process of reloading the model with or without + DeepSpeed acceleration. DeepSpeed can significantly improve performance on + supported hardware. + + Args: + value (bool): True to enable DeepSpeed, False to disable + + Operation: + 1. Unloads current model + 2. Updates DeepSpeed settings + 3. Reloads model with new configuration + + States Affected: + - self.deepspeed_enabled: Updated to reflect new state + - self.model: Reloaded with new configuration + + Returns: + bool: The new DeepSpeed state (same as input value) + + Note: DeepSpeed must be installed and available in the system for + this functionality to work. `deepspeed_capable` must be set `True` + in the `model_settings.json` file + """ + self.debug_func_entry() + # Initial validation + if not self.is_tts_model_loaded: + self.print_message("No model is currently loaded. Please select a model to load.", message_type="error") + raise HTTPException(status_code=400, detail="No model is currently loaded. Please select a model to load.") + + if value: + self.print_message("\033[93mDeepSpeed Activating\033[0m", message_type="standard") + await self.unload_model() + self.deepspeed_enabled = True + await self.setup() + else: + self.print_message("\033[93mDeepSpeed De-Activating\033[0m", message_type="standard") + self.deepspeed_enabled = False + await self.unload_model() + await self.setup() + return value + + + ################################################################ + # Unload models from VRAM/RAM if possible # Do not change this # + ################################################################ + async def unload_model(self): + """ + Unload the current model and free associated resources. + + This function handles the cleanup of model resources, including: + 1. Setting model loaded flag to False + 2. Deleting the model instance + 3. Clearing CUDA cache if available + + Operation: + 1. Updates model loading status + 2. Logs unloading process if a model is loaded + 3. Removes model from memory + 4. Cleans up CUDA cache if using GPU + + States Affected: + - self.is_tts_model_loaded: Set to False + - self.model: Set to None after unloading + """ + self.debug_func_entry() + + self.is_tts_model_loaded = False + if not self.current_model_loaded == None: + self.print_message("Unloading model", message_type="debug_tts") + if hasattr(self, 'model'): + del self.model + if torch.cuda.is_available(): + torch.cuda.empty_cache() + return None + + ############################################################ + # On start-up, perform these actions # Change as necessary # + ############################################################ + async def setup(self): + """ + Initialize the TTS engine and load initial model configuration. + + This function is called during system startup and handles: + 1. Version information display + 2. Model scanning and availability check + 3. Initial model loading if specified + + The setup sequence ensures: + - Proper version reporting + - Model availability verification + - Graceful handling of missing models + - Correct initial model loading state + + States Set: + - self.available_models: Updated with found models + - self.current_model_loaded: Set to loaded model name or None + - self.setup_has_run: Set True when complete + + Returns: + None + + Note: Custom initialization code should be placed between the marked sections. + """ + self.debug_func_entry() + self.print_message("Initializing TTS engine", message_type="debug_tts") + self.printout_versions() + self.available_models = self.scan_models_folder() + # ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ + # ↑↑↑ Keep everything above this line ↑↑↑ + # ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ + + if self.selected_model: + tts_model = f"{self.selected_model}" + if tts_model in self.available_models: + self.print_message(f"Loading selected model: {tts_model}", message_type="debug_tts") + await self.handle_tts_method_change(tts_model) + self.current_model_loaded = tts_model + + # ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ + # ↓↓↓ Keep everything below this line ↓↓↓ + # ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ + else: + self.current_model_loaded = "No Models Available" + self.print_message(f"Selected model '{self.selected_model}' not found in models folder.", message_type="error") + self.print_message(f"Please download a model or select a different model file.", message_type="error") + self.setup_has_run = True + + + ########################################################################### + # Scan your models folder for models OR voice files # Change as necessary # + ########################################################################### + def scan_models_folder(self): + """ + Scan for available TTS models in the models directory. + + This function searches the models directory for valid TTS model installations. + Each model must contain all required files to be considered valid. + + Required Files for Each Model: + - Whatever your TTS engine needs/supports + + Operation: + 1. Scans the models/{folder} directory + 2. Checks each subfolder for required files + 3. Registers valid models + + States Affected: + - self.available_models: Updated with found models + + Returns: + dict: Dictionary of available models in format: + {model_identifier: engine_type} + + Note: If no valid models are found, returns {"No Models Available": "TTS Engine Name"} + """ + self.debug_func_entry() + + models_folder = self.main_dir / "models" / self.model_folder_name + self.available_models = {} + + # ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ + # ↑↑↑ Keep everything above this line ↑↑↑ + # ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ + + required_files = ["config.json", "model.pth"] + model_name = "DO SOMETHING HERE" + + + # ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ + # ↓↓↓ Keep everything below this line ↓↓↓ + # ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ + else: + self.available_models = {'No Models Available': self.model_folder_name} + self.print_message(f"Model folder '{model_name}' is missing required files", message_type="warning") + self.print_message("Required files or folder does not exist", message_type="warning") + self.print_message("Please download some models/voices for this engine", message_type="warning") + return self.available_models + + ################################################################ + # Scan your voice folder for voice files # Change as necessary # + ################################################################ + def voices_file_list(self): + """ + Scan and compile a list of available voices + + This function scans multiple directories to find voice models/samples in different formats: + 1. Individual WAV/MP3 etc files in the main voices directory + + Directory Structure: + - voices/: Individual audio files + + Returns: + list: Available voices with appropriate prefixes: + - Standard WAV: filename.wav + + Note: Returns ["No Voices Found"] if no valid voices are detected + """ + self.debug_func_entry() + + try: + voices = [] # An empy variable for the list of voices to be put into. + directory = self.main_dir / "voices" # Base directory that voices are stored in. + # ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ + # ↑↑↑ Keep everything above this line ↑↑↑ + # ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ + + + + # ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ + # ↓↓↓ Keep everything below this line ↓↓↓ + # ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ + # Sort voices by type alphabetically + voices.sort(key=lambda x: (x.startswith("voiceset:"), x.startswith("latent:"), x)) + if not voices: + return ["No Voices Found"] + return voices + + except Exception as e: + self.print_message(f"Error scanning for voices: {str(e)}", message_type="error") + return ["No Voices Found"] + + ############################################ + # Load in your model # Change as necessary # + ############################################ + async def load_model(self, model_name): + """ + Load a model using the your TTS API interface. + + Args: + model_name (str): Name of the model to load + + Operation: + 1. Validates model availability + 2. Constructs model and config paths + 3. Initializes model using TTS API + 4. Moves model to appropriate device (CPU/GPU) + + States Affected: + - self.model: Updated with loaded model + - self.is_tts_model_loaded: Set to True on success + + Returns: + The loaded model instance + + Raises: + HTTPException: If no models are available to load + """ + self.debug_func_entry() + if "No Models Available" in self.available_models: + self.print_message("No models for this TTS engine were found to load", message_type="error") + return + model_path = self.main_dir / "models" / self.model_folder_name / model_name # You may need to edit/modfy depending on how your TTS engine works with models + self.print_message(f"Loading model from: {model_path}", message_type="debug_tts") + # ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ + # ↑↑↑ Keep everything above this line ↑↑↑ + # ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ + + self.model = TTS( + model_path=model_path, + config_path=model_path / "config.json", + ).to(self.device) + + + # ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ + # ↓↓↓ Keep everything below this line ↓↓↓ + # ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ + self.is_tts_model_loaded = True + return self.model + + async def handle_tts_method_change(self, tts_method): + """ + Handle switching between different TTS models/voices. + + This function manages actual model loading process. + + Args: + tts_method (str): Format "type - modelname" where type is either + + Operation: + 1. Validates model availability + 2. Unloads current model if any + 3. Parses method string to determine loader type + 4. Calls appropriate model loader + 5. Updates current model tracking + + States Affected: + - self.current_model_loaded: Updated to new model identifier + - self.model: Updated with newly loaded model + + Returns: + bool: True if model loaded successfully, False otherwise + + Timing: + Records and reports model loading time + """ + self.debug_func_entry() + + # Track loading time + generate_start_time = time.time() + + # Validate model availability + if "No Models Available" in self.available_models: + self.print_message("No models for this TTS engine were found to load", message_type="error") + return False + + # Unload current model + await self.unload_model() + + # ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ + # ↑↑↑ Keep everything above this line ↑↑↑ + # ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ + + + + # ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ + # ↓↓↓ Keep everything below this line ↓↓↓ + # ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ + + else: + self.print_message(f"Unknown model type in: {tts_method}", message_type="error") + self.current_model_loaded = None + return False + + # Report loading time + generate_end_time = time.time() + generate_elapsed_time = generate_end_time - generate_start_time + self.print_message(f"\033[94mModel Loadtime: \033[93m{generate_elapsed_time:.2f}\033[94m seconds\033[0m") + return True + + async def generate_tts(self, text, voice, language, temperature, repetition_penalty, speed, pitch, output_file, streaming): + """ + Generate speech from text using the TTS model. + + This core function handles all TTS generation, supporting both streaming and + non-streaming output, multiple voice input types, + and various generation parameters. + + Args: + text (str): Text to convert to speech + voice (str): Voice identifier (WAV/MP3 file, voice set, or latent) + language (str): Target language code + temperature (float): Generation temperature (0.0-1.0) + repetition_penalty (float): Penalty for repetitive generation + speed (float): Speech speed multiplier + pitch (float): Voice pitch adjustment + output_file (str): Path for output audio file + streaming (bool): Whether to stream audio chunks + + Returns: + For streaming=True: Generator yielding audio chunks + For streaming=False: None (saves to output_file) + + States Used: + - self.model: Active TTS model + - self.device: Current processing device + - self.lowvram_enabled: Low VRAM mode status + - self.current_model_loaded: Current model type + """ + self.debug_func_entry() + + # Initial validation + if not self.is_tts_model_loaded: + self.print_message("No TTS model loaded", message_type="error") + raise HTTPException(status_code=400, detail="You currently have no TTS model loaded.") + + # Lock generation and track start time + self.tts_generating_lock = True + self.print_message("Starting TTS generation process", message_type="debug_tts") + self.print_message(f"Generation parameters: temperature={temperature}, speed={speed}, streaming={streaming}", + message_type="debug_tts_variables") + + # Handle low VRAM mode if needed + if self.lowvram_enabled and self.device == "cpu": + self.print_message("Low VRAM mode: Moving model to GPU", message_type="debug_tts") + await self.handle_lowvram_change() + + generate_start_time = time.time() + + try: + # Voice input processing + self.print_message(f"Processing voice input: {voice}", message_type="debug_tts") + + # ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ + # ↑↑↑ Keep everything above this line ↑↑↑ + # ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ + + # BUILD ANY SETTINGS NEEED FOR TTS HERE + # DO ANY LOGIC TESTS E.G. CHECK IF THE AUDIO FILE/MODEL EXISTS + + # ALWAYS CHECK IF STREAMING FIRST THEN DO OTHER METHODS + if streaming: + # PUT YOUR TTS ENGINE STREAMING GENERATION LOGIC IN HERE + yield + else: + self.print_message("Starting non-streaming generation", message_type="debug_tts") + + # PUT YOUR TTS ENGINE STANDARD GENERATION LOGIC IN HERE + + self.print_message(f"Saved audio to: {output_file}", message_type="debug_tts") + + + # ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ + # ↓↓↓ Keep everything below this line ↓↓↓ + # ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ + + finally: + # Generation complete + generate_end_time = time.time() + generate_elapsed_time = generate_end_time - generate_start_time + + # Standard output message (not debug) + self.print_message( + f"\033[94mTTS Generate: \033[93m{generate_elapsed_time:.2f} seconds. \033[94mLowVRAM: \033[33m{self.lowvram_enabled} \033[94mDeepSpeed: \033[33m{self.deepspeed_enabled}\033[0m", + message_type="standard" + ) + + # Handle low VRAM cleanup + if self.lowvram_enabled and self.device == "cuda" and not self.tts_narrator_generatingtts: + self.print_message("Low VRAM mode: Moving model back to CPU", message_type="debug_tts") + await self.handle_lowvram_change() + + self.tts_generating_lock = False diff --git a/system/tts_engines/vits/available_models.json b/system/tts_engines/vits/available_models.json index 6736a95..11c97b2 100644 --- a/system/tts_engines/vits/available_models.json +++ b/system/tts_engines/vits/available_models.json @@ -1,190 +1,190 @@ -{ - "first_start_model": "tts_models--en--vctk--vits", - "models": [ - { - "model_name": "tts_models--bg--cv--vits", - "folder_path": "tts_models--bg--cv--vits", - "github_rls_url": "https://coqui.gateway.scarf.sh/v0.8.0_models/tts_models--bg--cv--vits.zip" - }, - { - "model_name": "tts_models--cs--cv--vits", - "folder_path": "tts_models--cs--cv--vits", - "github_rls_url": "https://coqui.gateway.scarf.sh/v0.8.0_models/tts_models--cs--cv--vits.zip" - }, - { - "model_name": "tts_models--da--cv--vits", - "folder_path": "tts_models--da--cv--vits", - "github_rls_url": "https://coqui.gateway.scarf.sh/v0.8.0_models/tts_models--da--cv--vits.zip" - }, - { - "model_name": "tts_models--et--cv--vits", - "folder_path": "tts_models--et--cv--vits", - "github_rls_url": "https://coqui.gateway.scarf.sh/v0.8.0_models/tts_models--et--cv--vits.zip" - }, - { - "model_name": "tts_models--ga--cv--vits", - "folder_path": "tts_models--ga--cv--vits", - "github_rls_url": "https://coqui.gateway.scarf.sh/v0.8.0_models/tts_models--ga--cv--vits.zip" - }, - { - "model_name": "tts_models--en--vctk--vits", - "folder_path": "tts_models--en--vctk--vits", - "github_rls_url": "https://coqui.gateway.scarf.sh/v0.6.1_models/tts_models--en--vctk--vits.zip" - }, - { - "model_name": "tts_models--es--css10--vits", - "folder_path": "tts_models--es--css10--vits", - "github_rls_url": "https://coqui.gateway.scarf.sh/v0.8.0_models/tts_models--es--css10--vits.zip" - }, - { - "model_name": "tts_models--fr--css10--vits", - "folder_path": "tts_models--fr--css10--vits", - "github_rls_url": "https://coqui.gateway.scarf.sh/v0.8.0_models/tts_models--fr--css10--vits.zip" - }, - { - "model_name": "tts_models--uk--mai--vits", - "folder_path": "tts_models--uk--mai--vits", - "github_rls_url": "https://coqui.gateway.scarf.sh/v0.8.0_models/tts_models--uk--mai--vits.zip" - }, - { - "model_name": "tts_models--nl--css10--vits", - "folder_path": "tts_models--nl--css10--vits", - "github_rls_url": "https://coqui.gateway.scarf.sh/v0.8.0_models/tts_models--nl--css10--vits.zip" - }, - { - "model_name": "tts_models--de--thorsten--vits", - "folder_path": "tts_models--de--thorsten--vits", - "github_rls_url": "https://coqui.gateway.scarf.sh/v0.7.0_models/tts_models--de--thorsten--vits.zip" - }, - { - "model_name": "tts_models--de--css10--vits-neon", - "folder_path": "tts_models--de--css10--vits-neon", - "github_rls_url": "https://coqui.gateway.scarf.sh/v0.8.0_models/tts_models--de--css10--vits.zip" - }, - { - "model_name": "tts_models--it--mai_female--vits", - "folder_path": "tts_models--it--mai_female--vits", - "github_rls_url": "https://coqui.gateway.scarf.sh/v0.6.1_models/tts_models--it--mai_female--vits.zip" - }, - { - "model_name": "tts_models--it--mai_male--vits", - "folder_path": "tts_models--it--mai_male--vits", - "github_rls_url": "https://coqui.gateway.scarf.sh/v0.6.1_models/tts_models--it--mai_male--vits.zip" - }, - { - "model_name": "tts_models--ewe--openbible--vits", - "folder_path": "tts_models--ewe--openbible--vits", - "github_rls_url": "https://coqui.gateway.scarf.sh/v0.6.2_models/tts_models--ewe--openbible--vits.zip" - }, - { - "model_name": "tts_models--hau--openbible--vits", - "folder_path": "tts_models--hau--openbible--vits", - "github_rls_url": "https://coqui.gateway.scarf.sh/v0.6.2_models/tts_models--hau--openbible--vits.zip" - }, - { - "model_name": "tts_models--lin--openbible--vits", - "folder_path": "tts_models--lin--openbible--vits", - "github_rls_url": "https://coqui.gateway.scarf.sh/v0.6.2_models/tts_models--lin--openbible--vits.zip" - }, - { - "model_name": "tts_models--tw_akuapem--openbible--vits", - "folder_path": "tts_models--tw_akuapem--openbible--vits", - "github_rls_url": "https://coqui.gateway.scarf.sh/v0.6.2_models/tts_models--tw_akuapem--openbible--vits.zip" - }, - { - "model_name": "tts_models--tw_asante--openbible--vits", - "folder_path": "tts_models--tw_asante--openbible--vits", - "github_rls_url": "https://coqui.gateway.scarf.sh/v0.6.2_models/tts_models--tw_asante--openbible--vits.zip" - }, - { - "model_name": "tts_models--yor--openbible--vits", - "folder_path": "tts_models--yor--openbible--vits", - "github_rls_url": "https://coqui.gateway.scarf.sh/v0.6.2_models/tts_models--yor--openbible--vits.zip" - }, - { - "model_name": "tts_models--hu--css10--vits", - "folder_path": "tts_models--hu--css10--vits", - "github_rls_url": "https://coqui.gateway.scarf.sh/v0.8.0_models/tts_models--hu--css10--vits.zip" - }, - { - "model_name": "tts_models--el--cv--vits", - "folder_path": "tts_models--el--cv--vits", - "github_rls_url": "https://coqui.gateway.scarf.sh/v0.8.0_models/tts_models--el--cv--vits.zip" - }, - { - "model_name": "tts_models--fi--css10--vits", - "folder_path": "tts_models--fi--css10--vits", - "github_rls_url": "https://coqui.gateway.scarf.sh/v0.8.0_models/tts_models--fi--css10--vits.zip" - }, - { - "model_name": "tts_models--hr--cv--vits", - "folder_path": "tts_models--hr--cv--vits", - "github_rls_url": "https://coqui.gateway.scarf.sh/v0.8.0_models/tts_models--hr--cv--vits.zip" - }, - { - "model_name": "tts_models--lt--cv--vits", - "folder_path": "tts_models--lt--cv--vits", - "github_rls_url": "https://coqui.gateway.scarf.sh/v0.8.0_models/tts_models--lt--cv--vits.zip" - }, - { - "model_name": "tts_models--lv--cv--vits", - "folder_path": "tts_models--lv--cv--vits", - "github_rls_url": "https://coqui.gateway.scarf.sh/v0.8.0_models/tts_models--lv--cv--vits.zip" - }, - { - "model_name": "tts_models--mt--cv--vits", - "folder_path": "tts_models--mt--cv--vits", - "github_rls_url": "https://coqui.gateway.scarf.sh/v0.8.0_models/tts_models--mt--cv--vits.zip" - }, - { - "model_name": "tts_models--pl--mai_female--vits", - "folder_path": "tts_models--pl--mai_female--vits", - "github_rls_url": "https://coqui.gateway.scarf.sh/v0.8.0_models/tts_models--pl--mai_female--vits.zip" - }, - { - "model_name": "tts_models--pt--cv--vits", - "folder_path": "tts_models--pt--cv--vits", - "github_rls_url": "https://coqui.gateway.scarf.sh/v0.8.0_models/tts_models--pt--cv--vits.zip" - }, - { - "model_name": "tts_models--ro--cv--vits", - "folder_path": "tts_models--ro--cv--vits", - "github_rls_url": "https://coqui.gateway.scarf.sh/v0.8.0_models/tts_models--ro--cv--vits.zip" - }, - { - "model_name": "tts_models--sk--cv--vits", - "folder_path": "tts_models--sk--cv--vits", - "github_rls_url": "https://coqui.gateway.scarf.sh/v0.8.0_models/tts_models--sk--cv--vits.zip" - }, - { - "model_name": "tts_models--sl--cv--vits", - "folder_path": "tts_models--sl--cv--vits", - "github_rls_url": "https://coqui.gateway.scarf.sh/v0.8.0_models/tts_models--sl--cv--vits.zip" - }, - { - "model_name": "tts_models--sv--cv--vits", - "folder_path": "tts_models--sv--cv--vits", - "github_rls_url": "https://coqui.gateway.scarf.sh/v0.8.0_models/tts_models--sv--cv--vits.zip" - }, - { - "model_name": "tts_models--ca--custom--vits", - "folder_path": "tts_models--ca--custom--vits", - "github_rls_url": "https://coqui.gateway.scarf.sh/v0.10.1_models/tts_models--ca--custom--vits.zip" - }, - { - "model_name": "tts_models--en--jenny--jenny", - "folder_path": "tts_models--en--jenny--jenny", - "github_rls_url": "https://coqui.gateway.scarf.sh/v0.14.0_models/tts_models--en--jenny--jenny.zip" - }, - { - "model_name": "tts_models--bn--custom--vits-male", - "folder_path": "tts_models--bn--custom--vits-male", - "github_rls_url": "https://coqui.gateway.scarf.sh/v0.13.3_models/tts_models--bn--custom--vits_male.zip" - }, - { - "model_name": "tts_models--bn--custom--vits-female", - "folder_path": "tts_models--bn--custom--vits-female", - "github_rls_url": "https://coqui.gateway.scarf.sh/v0.13.3_models/tts_models--bn--custom--vits_female.zip" - } - ] +{ + "first_start_model": "tts_models--en--vctk--vits", + "models": [ + { + "model_name": "tts_models--bg--cv--vits", + "folder_path": "tts_models--bg--cv--vits", + "github_rls_url": "https://coqui.gateway.scarf.sh/v0.8.0_models/tts_models--bg--cv--vits.zip" + }, + { + "model_name": "tts_models--cs--cv--vits", + "folder_path": "tts_models--cs--cv--vits", + "github_rls_url": "https://coqui.gateway.scarf.sh/v0.8.0_models/tts_models--cs--cv--vits.zip" + }, + { + "model_name": "tts_models--da--cv--vits", + "folder_path": "tts_models--da--cv--vits", + "github_rls_url": "https://coqui.gateway.scarf.sh/v0.8.0_models/tts_models--da--cv--vits.zip" + }, + { + "model_name": "tts_models--et--cv--vits", + "folder_path": "tts_models--et--cv--vits", + "github_rls_url": "https://coqui.gateway.scarf.sh/v0.8.0_models/tts_models--et--cv--vits.zip" + }, + { + "model_name": "tts_models--ga--cv--vits", + "folder_path": "tts_models--ga--cv--vits", + "github_rls_url": "https://coqui.gateway.scarf.sh/v0.8.0_models/tts_models--ga--cv--vits.zip" + }, + { + "model_name": "tts_models--en--vctk--vits", + "folder_path": "tts_models--en--vctk--vits", + "github_rls_url": "https://coqui.gateway.scarf.sh/v0.6.1_models/tts_models--en--vctk--vits.zip" + }, + { + "model_name": "tts_models--es--css10--vits", + "folder_path": "tts_models--es--css10--vits", + "github_rls_url": "https://coqui.gateway.scarf.sh/v0.8.0_models/tts_models--es--css10--vits.zip" + }, + { + "model_name": "tts_models--fr--css10--vits", + "folder_path": "tts_models--fr--css10--vits", + "github_rls_url": "https://coqui.gateway.scarf.sh/v0.8.0_models/tts_models--fr--css10--vits.zip" + }, + { + "model_name": "tts_models--uk--mai--vits", + "folder_path": "tts_models--uk--mai--vits", + "github_rls_url": "https://coqui.gateway.scarf.sh/v0.8.0_models/tts_models--uk--mai--vits.zip" + }, + { + "model_name": "tts_models--nl--css10--vits", + "folder_path": "tts_models--nl--css10--vits", + "github_rls_url": "https://coqui.gateway.scarf.sh/v0.8.0_models/tts_models--nl--css10--vits.zip" + }, + { + "model_name": "tts_models--de--thorsten--vits", + "folder_path": "tts_models--de--thorsten--vits", + "github_rls_url": "https://coqui.gateway.scarf.sh/v0.7.0_models/tts_models--de--thorsten--vits.zip" + }, + { + "model_name": "tts_models--de--css10--vits-neon", + "folder_path": "tts_models--de--css10--vits-neon", + "github_rls_url": "https://coqui.gateway.scarf.sh/v0.8.0_models/tts_models--de--css10--vits.zip" + }, + { + "model_name": "tts_models--it--mai_female--vits", + "folder_path": "tts_models--it--mai_female--vits", + "github_rls_url": "https://coqui.gateway.scarf.sh/v0.6.1_models/tts_models--it--mai_female--vits.zip" + }, + { + "model_name": "tts_models--it--mai_male--vits", + "folder_path": "tts_models--it--mai_male--vits", + "github_rls_url": "https://coqui.gateway.scarf.sh/v0.6.1_models/tts_models--it--mai_male--vits.zip" + }, + { + "model_name": "tts_models--ewe--openbible--vits", + "folder_path": "tts_models--ewe--openbible--vits", + "github_rls_url": "https://coqui.gateway.scarf.sh/v0.6.2_models/tts_models--ewe--openbible--vits.zip" + }, + { + "model_name": "tts_models--hau--openbible--vits", + "folder_path": "tts_models--hau--openbible--vits", + "github_rls_url": "https://coqui.gateway.scarf.sh/v0.6.2_models/tts_models--hau--openbible--vits.zip" + }, + { + "model_name": "tts_models--lin--openbible--vits", + "folder_path": "tts_models--lin--openbible--vits", + "github_rls_url": "https://coqui.gateway.scarf.sh/v0.6.2_models/tts_models--lin--openbible--vits.zip" + }, + { + "model_name": "tts_models--tw_akuapem--openbible--vits", + "folder_path": "tts_models--tw_akuapem--openbible--vits", + "github_rls_url": "https://coqui.gateway.scarf.sh/v0.6.2_models/tts_models--tw_akuapem--openbible--vits.zip" + }, + { + "model_name": "tts_models--tw_asante--openbible--vits", + "folder_path": "tts_models--tw_asante--openbible--vits", + "github_rls_url": "https://coqui.gateway.scarf.sh/v0.6.2_models/tts_models--tw_asante--openbible--vits.zip" + }, + { + "model_name": "tts_models--yor--openbible--vits", + "folder_path": "tts_models--yor--openbible--vits", + "github_rls_url": "https://coqui.gateway.scarf.sh/v0.6.2_models/tts_models--yor--openbible--vits.zip" + }, + { + "model_name": "tts_models--hu--css10--vits", + "folder_path": "tts_models--hu--css10--vits", + "github_rls_url": "https://coqui.gateway.scarf.sh/v0.8.0_models/tts_models--hu--css10--vits.zip" + }, + { + "model_name": "tts_models--el--cv--vits", + "folder_path": "tts_models--el--cv--vits", + "github_rls_url": "https://coqui.gateway.scarf.sh/v0.8.0_models/tts_models--el--cv--vits.zip" + }, + { + "model_name": "tts_models--fi--css10--vits", + "folder_path": "tts_models--fi--css10--vits", + "github_rls_url": "https://coqui.gateway.scarf.sh/v0.8.0_models/tts_models--fi--css10--vits.zip" + }, + { + "model_name": "tts_models--hr--cv--vits", + "folder_path": "tts_models--hr--cv--vits", + "github_rls_url": "https://coqui.gateway.scarf.sh/v0.8.0_models/tts_models--hr--cv--vits.zip" + }, + { + "model_name": "tts_models--lt--cv--vits", + "folder_path": "tts_models--lt--cv--vits", + "github_rls_url": "https://coqui.gateway.scarf.sh/v0.8.0_models/tts_models--lt--cv--vits.zip" + }, + { + "model_name": "tts_models--lv--cv--vits", + "folder_path": "tts_models--lv--cv--vits", + "github_rls_url": "https://coqui.gateway.scarf.sh/v0.8.0_models/tts_models--lv--cv--vits.zip" + }, + { + "model_name": "tts_models--mt--cv--vits", + "folder_path": "tts_models--mt--cv--vits", + "github_rls_url": "https://coqui.gateway.scarf.sh/v0.8.0_models/tts_models--mt--cv--vits.zip" + }, + { + "model_name": "tts_models--pl--mai_female--vits", + "folder_path": "tts_models--pl--mai_female--vits", + "github_rls_url": "https://coqui.gateway.scarf.sh/v0.8.0_models/tts_models--pl--mai_female--vits.zip" + }, + { + "model_name": "tts_models--pt--cv--vits", + "folder_path": "tts_models--pt--cv--vits", + "github_rls_url": "https://coqui.gateway.scarf.sh/v0.8.0_models/tts_models--pt--cv--vits.zip" + }, + { + "model_name": "tts_models--ro--cv--vits", + "folder_path": "tts_models--ro--cv--vits", + "github_rls_url": "https://coqui.gateway.scarf.sh/v0.8.0_models/tts_models--ro--cv--vits.zip" + }, + { + "model_name": "tts_models--sk--cv--vits", + "folder_path": "tts_models--sk--cv--vits", + "github_rls_url": "https://coqui.gateway.scarf.sh/v0.8.0_models/tts_models--sk--cv--vits.zip" + }, + { + "model_name": "tts_models--sl--cv--vits", + "folder_path": "tts_models--sl--cv--vits", + "github_rls_url": "https://coqui.gateway.scarf.sh/v0.8.0_models/tts_models--sl--cv--vits.zip" + }, + { + "model_name": "tts_models--sv--cv--vits", + "folder_path": "tts_models--sv--cv--vits", + "github_rls_url": "https://coqui.gateway.scarf.sh/v0.8.0_models/tts_models--sv--cv--vits.zip" + }, + { + "model_name": "tts_models--ca--custom--vits", + "folder_path": "tts_models--ca--custom--vits", + "github_rls_url": "https://coqui.gateway.scarf.sh/v0.10.1_models/tts_models--ca--custom--vits.zip" + }, + { + "model_name": "tts_models--en--jenny--jenny", + "folder_path": "tts_models--en--jenny--jenny", + "github_rls_url": "https://coqui.gateway.scarf.sh/v0.14.0_models/tts_models--en--jenny--jenny.zip" + }, + { + "model_name": "tts_models--bn--custom--vits-male", + "folder_path": "tts_models--bn--custom--vits-male", + "github_rls_url": "https://coqui.gateway.scarf.sh/v0.13.3_models/tts_models--bn--custom--vits_male.zip" + }, + { + "model_name": "tts_models--bn--custom--vits-female", + "folder_path": "tts_models--bn--custom--vits-female", + "github_rls_url": "https://coqui.gateway.scarf.sh/v0.13.3_models/tts_models--bn--custom--vits_female.zip" + } + ] } \ No newline at end of file diff --git a/system/tts_engines/vits/help_content.py b/system/tts_engines/vits/help_content.py new file mode 100644 index 0000000..21d48c6 --- /dev/null +++ b/system/tts_engines/vits/help_content.py @@ -0,0 +1,378 @@ +# help_content.py +# pylint: disable=no-member + +class AllTalkHelpContent: + """CSS and help content for help_content.py""" + custom_css = """ + /* Add this to your existing CSS */ + .gradio-container .prose { + max-width: none !important; + padding: 0.5rem !important; /* Reduced padding */ + margin: 0 !important; + } + + .custom-markdown div { + border: none !important; /* Remove the inner border */ + margin-top: 0 !important; /* Remove top margin */ + margin-bottom: 0 !important; /* Remove bottom margin */ + padding-top: 0 !important; /* Remove top padding */ + padding-bottom: 0 !important; /* Remove bottom padding */ + } + + /* Update the existing custom-markdown class */ + .custom-markdown { + font-size: 15px !important; + line-height: 1.6 !important; + color: var(--body-text-color) !important; + background-color: var(--background-fill-primary) !important; + border: 1px solid var(--border-color-primary) !important; + border-radius: 8px !important; + padding: 0.5rem !important; + margin: 0 !important; /* Changed from 1rem 0 to 0 */ + max-width: 100% !important; + box-sizing: border-box !important; + /* Default text color for all content (grey) */ + color: rgba(156, 163, 175, 1) !important; /* Adjust this grey value to match your interface */ + } + + /* Make headings white */ + .custom-markdown h1, + .custom-markdown h2, + .custom-markdown h3, + .custom-markdown h4, + .custom-markdown h5, + .custom-markdown h6 { + color: var(--heading-text-color, var(--body-text-color)) !important; + font-weight: 600 !important; + } + + /* Keep all other elements in the grey color */ + .custom-markdown p, + .custom-markdown li, + .custom-markdown ul, + .custom-markdown ol { + color: rgba(156, 163, 175, 1) !important; /* Same grey as the base text */ + } + + /* Additional targeting for any wrapping elements */ + .gradio-container .prose > * { + margin: 0 !important; + padding: 0 !important; + } + + /* Update the list item spacing in your existing CSS */ + .custom-markdown li { + font-size: 0.95rem !important; + margin-bottom: 0.2rem !important; /* Reduced from 0.5rem to 0.3rem */ + } + + /* Add specific styling for definition-style lists (like your Default, Recommendation, Tip items) */ + .custom-markdown li p { + margin: 0 !important; /* Remove paragraph margins within list items */ + } + + /* If you need even tighter spacing for specific types of lists */ + .custom-markdown li + li { + margin-top: 0.2rem !important; /* Space between consecutive list items */ + } + + /* Ensure nested lists maintain proper spacing */ + .custom-markdown li > ul, + .custom-markdown li > ol { + margin: 0.2rem 0 0.2rem 1rem !important; /* Reduced from 0.5rem */ + } + + /* Consistent heading styles */ + .custom-markdown h2 { + font-size: 1.2rem !important; /* Fixed size relative to root */ + font-weight: 600 !important; + margin: 1.5rem 0 1rem !important; + color: var(--heading-text-color, var(--body-text-color)) !important; + border-bottom: 1px solid var(--border-color-primary) !important; + padding-bottom: 0.5rem !important; + } + + /* First paragraph styling */ + .custom-markdown p:first-of-type { + font-size: 0.95rem !important; /* Match base size */ + font-weight: 400 !important; + margin-bottom: 1rem !important; + } + + /* Regular paragraphs */ + .custom-markdown p { + font-size: 0.95rem !important; + margin: 0.8rem 0 !important; + } + + /* List styling */ + .custom-markdown ul, + .custom-markdown ol { + margin: 0.8rem 0 !important; + padding-left: 1.5rem !important; + } + + /* Code styling */ + .custom-markdown code { + font-family: ui-monospace, monospace !important; + background-color: var(--background-fill-secondary) !important; + padding: 0.2rem 0.4rem !important; + border-radius: 4px !important; + font-size: 0.8rem !important; /* Slightly smaller than regular text */ + color: var(--body-text-color) !important; + } + + /* Add styles for bold and emphasis */ + .custom-markdown strong { + font-weight: 600 !important; + color: var(--body-text-color) !important; + } + + .custom-markdown em { + font-style: italic !important; + } + + /* Ensure both columns take equal width */ + .gradio-row > div { + flex: 1 1 50% !important; + min-width: 0 !important; /* Prevents flex items from overflowing */ + } + """ + + ENGINE_INFORMATION = """ + ## 🔊 TTS Engine Capabilities Help + + This guide explains the various capabilities that **may** be available in different TTS engines and models. Each capability affects how the TTS engine processes and generates speech output. + """ + + ENGINE_INFORMATION1 = """ + ## 🚀 Performance Features + + - **DeepSpeed Capable**: Enables GPU acceleration using the DeepSpeed optimization library. + - **Requires**: NVIDIA GPU with CUDA support + - **Benefit**: Significantly faster text-to-speech generation + - **Note**: Model must specifically support DeepSpeed inference + + - **Low VRAM Capable**: Optimized for systems with limited GPU memory. + - **Benefit**: Efficient memory management between CPU and GPU + - **Use Case**: Ideal when running alongside other GPU-intensive applications like LLMs + - **Note**: May trade speed for memory efficiency + + - **Streaming Capable**: Enables real-time speech generation. + - **Benefit**: Immediate playback without generating entire audio first + - **Use Case**: Interactive applications and real-time responses + - **Note**: Not all output formats support streaming + + ## 🎵 Voice Control Features + + - **Pitch Capable**: Allows adjustment of voice pitch. + - **Benefit**: Creates more expressive and varied speech output + - **Use Case**: Customizing voice characteristics + - **Note**: Adjustment range varies by model + + - **Generation Speed Capable**: Controls speech rate. + - **Benefit**: Adjustable playback speed during generation + - **Use Case**: Creating faster or slower speech output + - **Note**: May affect audio quality at extreme settings + + - **Temperature Capable**: Controls output randomness. + - **Benefit**: Adjustable speech variation and creativity + - **Use Case**: Balancing consistency vs. naturality + - **Note**: Higher values increase variation but may reduce quality + """ + + ENGINE_INFORMATION2 = """ + ## 🎯 Quality Enhancement Features + + - **Repetition Penalty Capable**: Prevents unnatural speech patterns. + - **Benefit**: Reduces repeated sounds and phrases + - **Use Case**: Improving natural flow of longer texts + - **Note**: Strength of penalty may be adjustable + + ## 🌐 Multi-Feature Support + + - **Multi-Languages Capable Models**: Each model Supports multiple languages. + - **Benefit**: Generate speech in different languages + - **Note**: Quality may vary between languages + - **Tip**: Check model-specific language support + + - **Multi-Voice Capable**: Supports multiple speaking voices. + - **Benefit**: Different voices or speaking styles + - **Types**: Pre-trained voices or voice cloning + - **Note**: Voice quality may vary by model + + - **Multi-Model Capable Engine**: Supports multiple TTS models. + - **Benefit**: Flexibility in model selection + - **Use Case**: Switching between models for different needs + - **Note**: Each model may have different capabilities + + ## 📁 Technical Features + + - **Default Audio Output Format**: Specifies output file format. + - **Common Formats**: WAV, MP3, FLAC, Opus, AAC, PCM + - **Note**: Transcoding to different formats adds processing time + - **Warning**: Not all formats support streaming + + - **Platform Support**: Operating system compatibility. + - **Platforms**: Windows, Linux, macOS + - **Note**: Additional setup may be required + - **Warning**: Support level may vary by platform + """ + + DEFAULT_SETTINGS = """ + ## 🎯 TTS Engine Settings Help + + This guide explains the settings and configuration options available for individual Text-to-Speech engines within AllTalk. + """ + + DEFAULT_SETTINGS1 = """ + ## 🎚️ Engine Capabilities & Controls + + - **Low VRAM Mode** + - Optimizes memory usage for systems with limited GPU memory + - Efficiently manages resources by moving data between CPU and GPU + - Recommended for systems with less than 4GB VRAM or when running alongside other GPU-intensive applications + + - **DeepSpeed Capability** + - Accelerates TTS generation using optimized inference + - Only available for engines and models that support DeepSpeed + - Requires NVIDIA GPU with CUDA support + + - **Temperature Control** + - Adjusts the variability in speech generation + - **Range**: 0.0 to 1.0 + - **Lower values** (0.1-0.5): More consistent, stable output + - **Higher values** (0.6-1.0): More variable, potentially more natural-sounding output + + - **Repetition Penalty** + - Helps prevent repetitive speech patterns + - **Range**: 1.0 to 15.0 + - Higher values more strongly discourage repetition + - Typically most effective between 1.0-3.0 + + - **Pitch Adjustment** + - Modifies the voice pitch when supported + - **Range**: -20 to +20 + - Use subtle adjustments for most natural results + + - **Generation Speed** + - Controls the pace of generated speech + - **Range**: 0.5 to 2.0 + - 1.0 represents normal speed + """ + + DEFAULT_SETTINGS2 = """ + + ## 🗣️ Voice Configuration + + ### OpenAI Voice Mappings + - Only relevant when using the OpenAI-compatible API endpoint + - Maps OpenAI's six standard voices to equivalent voices in the current engine: + - `alloy` + - `echo` + - `fable` + - `nova` + - `onyx` + - `shimmer` + - Essential for maintaining compatibility with OpenAI API calls + - Each mapping can be customized to any available voice in the current engine + + Information on the OpenAI Endpoint is available in the [Github Wiki](https://github.com/erew123/alltalk_tts/wiki) + + ### Default Voices + - **Default/Character Voice**: Used when no specific voice is specified in API calls + - **Narrator Voice**: Separate default for narrator-specific text + - These defaults are engine-specific and won't affect other TTS engines + - Can be overridden by explicitly specifying voices in API calls + + ## ⚙️ Important Notes + + - Settings availability is determined by engine capabilities + - Grayed-out options indicate features not supported by the current engine + - Changes only affect the currently selected TTS engine + - Settings here act as defaults but can be overridden via API parameters + - All changes require clicking "Update Settings" to take effect + - Some settings require an engine reload to take effect + """ + + HELP_PAGE = """ + ## 🎯 VITS TTS Engine Help + + VITS (Conditional Variational Autoencoder with Adversarial Learning) is an end-to-end text-to-speech model that combines advanced techniques for high-quality voice synthesis. + """ + + HELP_PAGE1 = """ + ## 🎚️ Engine Capabilities & Controls + + - **Low VRAM Mode** + - Enabled for optimized memory usage + - Efficiently manages resources between CPU and GPU + - Recommended when running alongside other GPU applications + + - **Multi-Model Support** + - Supports multiple model types: + - Single-speaker models + - Multi-speaker models e.g. `tts_models--en--vctk--vits` + - Multi-language models + - Models can be switched without restarting + + ## 🗣️ Voice System + + ### Model Types + - **Single-Speaker Models** + - Contains one voice + - Uses `default` as the speaker name + - Requires `config.json` and `model_file.pth` + + - **Multi-Speaker Models** + - Contains multiple voices in one model + - Uses `speaker_ids.json` to define available voices + - Requires `config.json`, `model_file.pth`, and `speaker_ids.json` + + - **Multi-Language Models** + - Supports multiple languages and speakers e.g. `tts_models--en--vctk--vits` + - Requires `config.json`, `model_file.pth.tar`, `speaker_ids.json`, and `language_ids.json` + - Allows language switching within the same model + + ### Language Support + - Wide range of languages available: + - English (VCTK dataset) + - German (Thorsten, CSS10) + - French (CSS10) + - Spanish (CSS10) + - Italian (Mai) + - And many more specialized models + """ + + HELP_PAGE2 = """ + ## 📝 Model Management + + - **Model Storage** + - Models stored in `/alltalk_tts/models/vits/` + - Each model has its own subfolder + - Required files vary by model type + + - **Output Files** + - Generated audio saved to `/alltalk_tts/outputs/` + - Automatic cleanup available via `Del WAV's older than` + - Set to `Disabled` to preserve all outputs + + ## ⚠️ Known Limitations + + - No support for: + - DeepSpeed acceleration + - Pitch adjustment + - Generation speed control + - Repetition penalty + - Temperature adjustment + - Streaming generation + + ## 🔧 System Requirements + + - Runs on all major operating systems: + - Windows + - Linux + - macOS + - Outputs in WAV format + - GPU recommended for optimal performance + """ \ No newline at end of file diff --git a/system/tts_engines/vits/model_engine.py b/system/tts_engines/vits/model_engine.py index cc020af..9e514ba 100644 --- a/system/tts_engines/vits/model_engine.py +++ b/system/tts_engines/vits/model_engine.py @@ -1,548 +1,648 @@ -############################################### -# DONT CHANGE # These are base imports needed # -############################################### -import os -import sys -import json -import time -import torch -import logging -from pathlib import Path -from fastapi import (HTTPException) -logging.disable(logging.WARNING) -############################################### -# DONT CHANGE # Get Pytorch & Python versions # -############################################### -pytorch_version = torch.__version__ -cuda_version = torch.version.cuda -major, minor, micro = sys.version_info[:3] -python_version = f"{major}.{minor}.{micro}" -try: - import deepspeed - deepspeed_available = True -except ImportError: - deepspeed_available = False - pass - -############################################################################################################# -############################################################################################################# -# CHANGE ME # Run any specifc imports, requirements or setup any global vaiables needed for this TTS Engine # -############################################################################################################# -############################################################################################################# -# In this section you will import any imports that your specific TTS Engine will use. You will provide any -# start-up errors for those bits, as if you were starting up a normal Python script. Note the logging.disable -# a few lines up from here, you may want to # that out while debugging! -try: - from TTS.utils.synthesizer import Synthesizer -except ModuleNotFoundError: - print( - f"[Startup] \033[91mWarning\033[0m Could not find the TTS module. Make sure to install the requirements for the alltalk_tts extension.", - f"[Startup] \033[91mWarning\033[0m Linux / Mac:\npip install -r extensions/alltalk_tts/requirements.txt\n", - f"[Startup] \033[91mWarning\033[0m Windows:\npip install -r extensions\\alltalk_tts\\requirements.txt\n", - f"[Startup] \033[91mWarning\033[0m If you used the one-click installer, paste the command above in the terminal window launched after running the cmd_ script. On Windows, that's cmd_windows.bat." - ) - raise - -############################################################# -# DONT CHANGE # Do not change the Class name from tts_class # -############################################################# -class tts_class: - def __init__(self): - ######################################################################## - # DONT CHANGE # Sets up the base variables required for any tts engine # - ######################################################################## - self.branding = None - self.this_dir = Path(__file__).parent.resolve() # Sets up self.this_dir as a variable for the folder THIS script is running in. - self.main_dir = Path(__file__).parent.parent.parent.parent.resolve() # Sets up self.main_dir as a variable for the folder AllTalk is running in - self.device = "cuda" if torch.cuda.is_available() else "cpu" # Sets up self.device to cuda if torch exists with Nvidia/CUDA, otherwise sets to cpu - self.cuda_is_available = torch.cuda.is_available() # Sets up cuda_is_available as a True/False to track if Nvidia/CUDA was found on the system - self.tts_generating_lock = False # Used to lock and unlock the tts generation process at the start/end of tts generation. - self.tts_stop_generation = False # Used in conjunction with tts_generating_lock to call for a stop to the current generation. If called (set True) it needs to be set back to False when generation has been stopped. - self.tts_narrator_generatingtts = False # Used to track if the current tts processes is narrator based. This can be used in conjunction with lowvram and device to avoid moving model between GPU(CUDA)<>RAM(CPU) each chunk of narrated text generated. - self.model = None # If loading a model into CUDA/VRAM/RAM "model" is used as the variable name to load and interact with (see the XTTS model_engine script for examples.) - self.is_tts_model_loaded = False # Used to track if a model is actually loaded in and error/fail things like TTS generation if its False - self.current_model_loaded = None # Stores the name of the currenly loaded in model - self.available_models = None # List of available models found by "def scan_models_folder" - self.setup_has_run = False # Tracks if async def setup(self) has run, by setting to True, so that the /api/ready endpoint can provide a "Ready" status - ############################################################################################## - # DONT CHANGE # Load in a list of the available TTS engines and the currently set TTS engine # - ############################################################################################## - tts_engines_file = os.path.join(self.main_dir, "system", "tts_engines", "tts_engines.json") - with open(tts_engines_file, "r") as f: - tts_engines_data = json.load(f) - self.engines_available = [engine["name"] for engine in tts_engines_data["engines_available"]] # A list of ALL the TTS engines available to be loaded by AllTalk - self.engine_loaded = tts_engines_data["engine_loaded"] # In "tts_engines.json" what is the currently set TTS engine loading into AllTalk - self.selected_model = tts_engines_data["selected_model"] # In "tts_engines.json" what is the currently set TTS model loading into AllTalk - ############################################################################ - # DONT CHANGE # Pull out all the settings for the currently set TTS engine # - ############################################################################ - with open(os.path.join(self.this_dir, "model_settings.json"), "r") as f: - tts_model_loaded = json.load(f) - # Access the model details - self.manufacturer_name = tts_model_loaded["model_details"]["manufacturer_name"] # The company/person/body that generated the TTS engine/models etc - self.manufacturer_website = tts_model_loaded["model_details"]["manufacturer_website"] # The website of the company/person/body where people can find more information - # Access the features the model is capable of: - self.audio_format = tts_model_loaded["model_capabilties"]["audio_format"] # This details the audio format your TTS engine is set to generate TTS in e.g. wav, mp3, flac, opus, acc, pcm. Please use only 1x format. - self.deepspeed_capable = tts_model_loaded["model_capabilties"]["deepspeed_capable"] # Is your model capable of DeepSpeed - self.deepspeed_available = 'deepspeed' in globals() # When we did the import earlier, at the top of this script, was DeepSpeed available for use - self.generationspeed_capable = tts_model_loaded["model_capabilties"]["generationspeed_capable"] # Does this TTS engine support changing the speed of the generated TTS - self.languages_capable = tts_model_loaded["model_capabilties"]["languages_capable"] # Are the actual models themselves capable of generating in multiple languages OR is each model language specific - self.lowvram_capable = tts_model_loaded["model_capabilties"]["lowvram_capable"] # Is this engine capable of using low VRAM (moving the model between CPU And GPU memory) - self.multimodel_capable = tts_model_loaded["model_capabilties"]["multimodel_capable"] # Is there just the one model or are there multiple models this engine supports. - self.repetitionpenalty_capable = tts_model_loaded["model_capabilties"]["repetitionpenalty_capable"] # Is this TTS engine capable of changing the repititon penalty - self.streaming_capable = tts_model_loaded["model_capabilties"]["streaming_capable"] # Is this TTS engine capabale of generating streaming audio - self.temperature_capable = tts_model_loaded["model_capabilties"]["temperature_capable"] # Is this TTS engine capable of changing the temperature of the models - self.multivoice_capable = tts_model_loaded["model_capabilties"]["multivoice_capable"] # Are the models multi-voice or single vocice models - self.pitch_capable = tts_model_loaded["model_capabilties"]["pitch_capable"] # Is this TTS engine capable of changing the pitch of the genrated TTS - # Access the current enginesettings - self.def_character_voice = tts_model_loaded["settings"]["def_character_voice"] # What is the current default main/character voice that will be used if no voice specified. - self.def_narrator_voice = tts_model_loaded["settings"]["def_narrator_voice"] # What is the current default narrator voice that will be used if no voice specified. - self.deepspeed_enabled = tts_model_loaded["settings"]["deepspeed_enabled"] # If its available, is DeepSpeed enabled for the TTS engine - self.engine_installed = tts_model_loaded["settings"]["engine_installed"] # Has the TTS engine been setup/installed (not curently used) - self.generationspeed_set = tts_model_loaded["settings"]["generationspeed_set"] # What is the set/stored speed for generation. - self.lowvram_enabled = tts_model_loaded["settings"]["lowvram_enabled"] # If its available, is LowVRAM enabled for the TTS engine - # Check if someone has enabled lowvram on a system that's not CUDA enabled - self.lowvram_enabled = False if not torch.cuda.is_available() else self.lowvram_enabled # If LowVRAM is mistakenly set and CUDA is not available, this will force it back off - self.repetitionpenalty_set = tts_model_loaded["settings"]["repetitionpenalty_set"] # What is the currenly set repitition policy of the model (If it support repetition) - self.temperature_set = tts_model_loaded["settings"]["temperature_set"] # What is the currenly set temperature of the model (If it support temp) - self.pitch_set = tts_model_loaded["settings"]["pitch_set"] # What is the currenly set pitch of the model (If it support temp) - # Gather the OpenAI API Voice Mappings - self.openai_alloy = tts_model_loaded["openai_voices"]["alloy"] # The TTS engine voice that will be mapped to Open AI Alloy voice - self.openai_echo = tts_model_loaded["openai_voices"]["echo"] # The TTS engine voice that will be mapped to Open AI Echo voice - self.openai_fable = tts_model_loaded["openai_voices"]["fable"] # The TTS engine voice that will be mapped to Open AI Fable voice - self.openai_nova = tts_model_loaded["openai_voices"]["nova"] # The TTS engine voice that will be mapped to Open AI Nova voice - self.openai_onyx = tts_model_loaded["openai_voices"]["onyx"] # The TTS engine voice that will be mapped to Open AI Onyx voice - self.openai_shimmer = tts_model_loaded["openai_voices"]["shimmer"] # The TTS engine voice that will be mapped to Open AI Shimmer voice - ################################################################### - # DONT CHANGE # Load params and api_defaults from confignew.json # - ################################################################### - # Define the path to the confignew.json file - configfile_path = self.main_dir / "confignew.json" - # Load config file and get settings - with open(configfile_path, "r") as configfile: - configfile_data = json.load(configfile) - self.branding = configfile_data.get("branding", "") # Sets up self.branding for outputting the name stored in the "confgnew.json" file, as used in print statements. - self.params = configfile_data # Loads in the curent "confgnew.json" file to self.params. - self.debug_tts = configfile_data.get("debugging").get("debug_tts") # Can be used within this script as a True/False flag for generally debugging the TTS generation process. - self.debug_tts_variables = configfile_data.get("debugging").get("debug_tts_variables") # Can be used within this script as a True/False flag for generally debugging variables (if you wish). - ################################################################ - # DONT CHANGE # Print out Python, CUDA, DeepSpeed versions #### - ################################################################ - def printout_versions(self): - if deepspeed_available: - print(f"[{self.branding}ENG] \033[92mDeepSpeed version :\033[93m",deepspeed.__version__,"\033[0m") - else: - print(f"[{self.branding}ENG] \033[92mDeepSpeed version :\033[93m Not available\033[0m") - print(f"[{self.branding}ENG] \033[92mPython Version :\033[93m {python_version}\033[0m") - print(f"[{self.branding}ENG] \033[92mPyTorch Version :\033[93m {pytorch_version}\033[0m") - if cuda_version is None: - print(f"[{self.branding}ENG] \033[92mCUDA Version :\033[91m Not available\033[0m") - else: - print(f"[{self.branding}ENG] \033[92mCUDA Version :\033[93m {cuda_version}\033[0m") - print(f"[{self.branding}ENG]") - return - - ################################################################################### - ################################################################################### - # CHANGE ME # Inital setup of the model and engine. Called when the script starts # - ################################################################################### - ################################################################################### - # In here you will add code to load in your model and do its inital setup. So you - # may be calling your model loader via handle_tts_method_change or if your TTS - # engine doesnt actually load a model into CUDA or System RAM, you may be doing - # Something to fake its start-up. - async def setup(self): - self.printout_versions() - self.available_models = self.scan_models_folder() - # ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ - # ↑↑↑ Keep everything above this line ↑↑↑ - # ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ - - print("in setup - self.selected_model:", self.selected_model) if self.debug_tts else None - if self.selected_model: - tts_model = f"{self.selected_model}" - print("in setup - tts_model:", tts_model) if self.debug_tts else None - if tts_model in self.available_models: - print("in setup - sending to handle_tts_method_change:") if self.debug_tts else None - await self.handle_tts_method_change(tts_model) - self.current_model_loaded = tts_model - - # ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ - # ↓↓↓ Keep everything below this line ↓↓↓ - # ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ - else: - self.current_model_loaded = "No Models Available" - print(f"[{self.branding}ENG] \033[91mError\033[0m: Selected model '{self.selected_model}' not found in the models folder.") - self.setup_has_run = True # Set to True, so that the /api/ready endpoint can provide a "Ready" status - - ################################## - ################################## - # CHANGE ME # Low VRAM Swapping # - ################################## - ################################## - # If your model does load into CUDA and you want to support LowVRAM, aka moving the model - # on the fly between CUDA and System RAM on each generation request, you will be adding - # The code in here to do it. See the XTTS tts engine for an example. Piper however, doesnt - # Load into VRAM or System RAM, also low vram is set globally disabled by the model_settings.JSON - # file, so this would never get called anywyay, however, we still need to keep an empty - # function in place. Piper TTS uses "pass" telling function to just exit out cleanly if called. - # However, its quite a simple check along the lines of "if CUDA is available and model is - # in X place, then send it to Y place (or Y to X). - async def handle_lowvram_change(self): - if torch.cuda.is_available(): - if self.device == "cuda": - self.device = "cpu" - self.model.to(self.device) - torch.cuda.empty_cache() - else: - self.device == "cpu" - self.device = "cuda" - self.model.to(self.device) - - ######################################## - ######################################## - # CHANGE ME # DeepSpeed model loading # - ######################################## - ######################################## - # If the model supports CUDA and DeepSpeed, this is where you can handle re-loading - # the model as/when DeepSpeed is enabled/selected in the user interface. If your - # TTS model doesnt support DeepSpeed, then it should be globally set in your - # model_settings.JSON and this function will never get called, however it still needs - # to exist as a function. - async def handle_deepspeed_change(self, value): - if value: - # DeepSpeed enabled - print(f"[{self.branding}ENG] \033[93mDeepSpeed Activating\033[0m") - await self.unload_model() - self.params["tts_method_api_local"] = False - self.deepspeed_enabled = True - await self.setup() - else: - # DeepSpeed disabled - print(f"[{self.branding}ENG] \033[93mDeepSpeed De-Activating\033[0m") - self.deepspeed_enabled = False - await self.unload_model() - await self.setup() - return value # Return new checkbox value - - ############################################################################################################################################## - ############################################################################################################################################## - # CHANGE ME # scan for available models/voices that are relevant to this TTS engine # XTTS is very unusal as it has 2x model loading methods # - ############################################################################################################################################## - ############################################################################################################################################## - # This function looks and reports back the list of possible models your TTS engine can - # load in. Some TTS engines have multiple models they can load and you will want to use - # code for checking if the models are in the correct location/placement within the disk - # the correct files exst per model etc (check XTTS for an example of this). Some models - # like Piper, the actual models are the voices, so in the Piper scan_models_folder - # function, we fake the only model being available as {'piper': 'piper'} aka, model name - # then engine name, then we use the voices_file_list to populate the models as available - # voices that can be selected in the interface. - # If no models are found, we return "No Models Available" and continue on with the script. - def scan_models_folder(self): - models_folder = self.main_dir / "models" / "vits" # Edit to match the name of your folder where voices are stored - self.available_models = {} - # ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ - # ↑↑↑ Keep everything above this line ↑↑↑ - # ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ - - required_files_multi_speaker = ["config.json", "model_file.pth", "speaker_ids.json"] - required_files_single_speaker = ["config.json", "model_file.pth"] - required_files_multi_language = ["config.json", "model_file.pth.tar", "speaker_ids.json", "language_ids.json"] - for subfolder in models_folder.iterdir(): - if subfolder.is_dir(): - model_name = subfolder.name - if all(subfolder.joinpath(file).exists() for file in required_files_multi_speaker): - self.available_models[f"vits - {model_name}"] = "vits_multi_speaker" - elif all(subfolder.joinpath(file).exists() for file in required_files_single_speaker): - self.available_models[f"vits - {model_name}"] = "vits_single_speaker" - elif all(subfolder.joinpath(file).exists() for file in required_files_multi_language): - self.available_models[f"vits - {model_name}"] = "vits_multi_language" - - # ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ - # ↓↓↓ Keep everything below this line ↓↓↓ - # ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ - else: - self.available_models = {'No Models Available': 'vits'} # Change the name in here to your TTS engine name - print(f"[{self.branding}ENG] \033[91mWarning\033[0m: Model folder '{model_name}' is missing required") - print(f"[{self.branding}ENG] \033[91mWarning\033[0m: files or the folder does not exist.") - return self.available_models - - ############################################################# - ############################################################# - # CHANGE ME # POPULATE FILES LIST FROM VOICES DIRECTORY #### - ############################################################# - ############################################################# - # This function looks and reports back the list of possible voice your TTS engine can - # load in. Some TTS engines the voices are wav file samples (XTTS), some are models - # (Piper) and some are text (Parler) thats stored in a JSON file. We just need to - # populate the "voices" variable somehow and if no voices are found, we return - # "No Voices Found" back to the interface/api. - def voices_file_list(self): - try: - voices = [] - # ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ - # ↑↑↑ Keep everything above this line ↑↑↑ - # ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ - - model_name = self.selected_model.replace("vits - ", "") - print("model_name", model_name) if self.debug_tts else None - model_type = self.available_models[self.selected_model] - print("model_type", model_type) if self.debug_tts else None - - if model_type == "vits_multi_speaker" or model_type == "vits_multi_language": - speaker_ids_path = self.main_dir / "models" / "vits" / model_name / "speaker_ids.json" - if speaker_ids_path.exists(): - with open(speaker_ids_path, "r") as f: - speaker_ids = json.load(f) - voices = [speaker for speaker in speaker_ids.keys() if speaker != "ED\n"] - else: - print(f"[{self.branding}ENG] \033[91mWarning\033[0m: speaker_ids.json not found for model \033[0m'{model_name}'\033[0m.") if self.debug_tts else None - - elif model_type == "vits_single_speaker": - voices = ["default"] # Return a list containing only the default voice for single-speaker models - - # ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ - # ↓↓↓ Keep everything below this line ↓↓↓ - # ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ - if not voices: # If the voices list is empty - return ["No Voices Found"] # Return a list with 'No Voices Found' if there are no voices/voice models - return voices # Return the list of voices - except Exception as e: - print(f"[{self.branding}ENG] \033[91mError\033[0m: Voices/Voice Models not found. Cannot load a list of voices.") - print(f"[{self.branding}ENG] Exception: {str(e)}") # Optional: Print the exception message for debugging - return ["No Voices Found"] - - ############################# - ############################# - # CHANGE ME # Model loading # - ############################# - ############################# - # This function will handle the loading of your model, into VRAM/CUDA, System RAM or whatever. - # In XTTS, which has 2x model loader types, there are 2x loaders. They are called by "def handle_tts_method_change" - # In Piper we fake a model loader as Piper doesnt actually load a model into CUDA/System RAM as such. So, in that - # situation, api_manual_load_model is kind of a blank function. Though we do set self.is_tts_model_loaded = True - # as this is used elsewhere in the scripts to confirm that a model is available to be used for TTS generation. - # We always check for "No Models Available" being sent as that means we are trying to load in a model that - # doesnt exist/wasnt found on script start-up e.g. someone deleted the model from the folder or something. - async def api_manual_load_model(self, model_name): - if "No Models Available" in self.available_models: - print(f"[{self.branding}ENG] \033[91mError\033[0m: No models for this TTS engine were found to load. Please download a model.") - return - model_path = self.main_dir / "models" / "vits" / model_name # change the name to the correct folder name - print("model_path is:", model_path) if self.debug_tts_variables else None - # ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ - # ↑↑↑ Keep everything above this line ↑↑↑ - # ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ - - # Load the config file - config_file_path = model_path / "config.json" - with open(config_file_path, "r") as f: - config_dict = json.load(f) - # Check if the model is single-speaker, multi-speaker, or multi-language - model_type = self.available_models[f"vits - {model_name}"] - use_speaker_embedding = config_dict["model_args"]["use_speaker_embedding"] - num_speakers = config_dict["model_args"]["num_speakers"] - if model_type == "vits_multi_speaker" or model_type == "vits_multi_language": - # Update the speakers_file path in the config dictionary - config_dict["model_args"]["speakers_file"] = str(model_path / "speaker_ids.json") - speakers_file = model_path / "speaker_ids.json" - else: - # Set speakers_file to None for single-speaker models - config_dict["model_args"]["speakers_file"] = None - speakers_file = None - # Save the updated config dictionary back to the config.json file - with open(config_file_path, "w") as f: - json.dump(config_dict, f, indent=4) - if model_type == "vits_multi_language": - # Load multi-language model - self.model = Synthesizer( - tts_checkpoint=model_path / "model_file.pth.tar", - tts_config_path=config_file_path, - tts_speakers_file=speakers_file, - tts_languages_file=model_path / "language_ids.json", - use_cuda=self.cuda_is_available, - ) - elif not use_speaker_embedding and num_speakers == 0: - # Single-speaker model without speaker embeddings - self.model = Synthesizer( - tts_checkpoint=model_path / "model_file.pth", - tts_config_path=config_file_path, - use_cuda=self.cuda_is_available, - ) - else: - # Multi-speaker model or single-speaker model with speaker embeddings - self.model = Synthesizer( - tts_checkpoint=model_path / "model_file.pth", - tts_config_path=config_file_path, - tts_speakers_file=speakers_file, - use_cuda=self.cuda_is_available, - ) - - # ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ - # ↓↓↓ Keep everything below this line ↓↓↓ - # ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ - self.is_tts_model_loaded = True - return self.model - - ############################### - ############################### - # CHANGE ME # Model unloading # - ############################### - ############################### - # This function will handle the UN-loading of your model, from VRAM/CUDA, System RAM or whatever. - # In XTTS, that model loads into CUDA/System Ram, so when we swap models, we want to unload the current model - # free up the memory and then load in the new model to VRAM/CUDA. On the flip side of that, Piper doesnt - # doesnt load into memory, so we just need to put a fake function here that doesnt really do anything - # other than set "self.is_tts_model_loaded = False", which would be set back to true by the model loader. - # So look at the Piper model_engine.py if you DONT need to unload models. - async def unload_model(self): - self.is_tts_model_loaded = False - if not self.current_model_loaded == None: - print(f"[{self.branding}ENG] \033[94mUnloading model \033[0m") if self.debug_tts else None - if hasattr(self, 'model'): - del self.model - if torch.cuda.is_available(): - torch.cuda.empty_cache() - return None - - ################################################################################################################################### - ################################################################################################################################### - # CHANGE ME # Model changing. Unload out old model and load in a new one # XTTS is very unusal as it has 2x model loading methods # - ################################################################################################################################### - ################################################################################################################################### - # This function is your central model loading/unloading handler that deals with the above functions as necesary, to call loading, unloading, - # swappng DeepSpeed, Low vram etc. This function gets called with a "engine name - model name" type call. In XTTS, because there are 2x - # model loader types, (XTTS and APILocal), we take tts_method and split the "engine name - model name" into a loader type and the model - # that it needs to load in and then we call the correct loader function. Whereas in Piper, which doesnt load models into memory at all, - # we just have a fake function that doesnt really do anything. We always check to see if the model name has "No Models Available" in the - # name thats sent over, just to catch any potential errors. We display the start load time and end load time. Thats about it. - async def handle_tts_method_change(self, tts_method): - generate_start_time = time.time() # Record the start time of loading the model - if "No Models Available" in self.available_models: - print(f"[{self.branding}ENG] \033[91mError\033[0m: No models for this TTS engine were found to load. Please download a model.") - return False - # ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ - # ↑↑↑ Keep everything above this line ↑↑↑ - # ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ - - await self.unload_model() - # Set other parameters to False - if tts_method.startswith("vits"): - model_name = tts_method.split(" - ")[1] - print(f"[{self.branding}ENG]\033[94m Model/Engine :\033[93m {model_name}\033[94m loading into\033[93m", self.device,"\033[0m") - self.model = await self.api_manual_load_model(model_name) - self.current_model_loaded = f"vits - {model_name}" - self.selected_model = tts_method # Update self.selected_model - else: - self.current_model_loaded = None - return False - - # ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ - # ↓↓↓ Keep everything below this line ↓↓↓ - # ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ - generate_end_time = time.time() # Create an end timer for calculating load times - generate_elapsed_time = generate_end_time - generate_start_time # Calculate start time minus end time - print(f"[{self.branding}ENG] \033[94mLoad time :\033[93m {generate_elapsed_time:.2f} seconds.\033[0m") # Print out the result of the load time - return True - - ########################################################################################################################################## - ########################################################################################################################################## - # CHANGE ME # Model changing. Unload out old model and load in a new one # XTTS is very unusal as it has 2x model TTS generation methods # - ########################################################################################################################################## - ########################################################################################################################################## - # In here all the possible options are sent over (text, voice to use, lanugage, speed etc etc) and its up to you how you use them, or not. - # obviously if your TTS engine doesnt support speed for example, generationspeed_capable should be set False in your model_settings.JSON file - # and a fake "generationspeed_set" value should be set. This allows a fake value to be sent over from the main script, even though it - # wouldnt actually ever be used in the generation below. Nonethless all these values, real or just whats inside the configuration file - # will be sent over for use. - # Setting the xxxxxxx_capabale in the model_settings.JSON file, will enable/disable them being selectable by the user. For example, if you - # set "generationspeed_capable" as false in the model_settings.JSON file, a user will not be able to select OR set the setting for - # generation speed. - # One thing to note is that we HAVE to have something in this generation request that is synchronous from the way its called, which means - # we have to have an option for Streaming, even if our TTS engine doesnt support streaming. So in that case, we would set streaming_capable - # as false in our model_settings.JSON file, meaning streaming will never be called. However, we have to put a fake streaming routine in our - # function below (or a real function if it does support streaming of course). Parler has an example of a fake streaming function, which is - # very clearly highlighted in its model_engine.py script. - # Piper TTS, which uses command line based calls and therefore has different ones for Windows and Linux/Mac, has an example of doing this - # within its model_engine.py file. - async def generate_tts(self, text, voice, language, temperature, repetition_penalty, speed, pitch, output_file, streaming): - print(f"[{self.branding}Debug] Entered model_engine.py generate_tts function") if self.debug_tts else None - if not self.is_tts_model_loaded: # Check if a model is loaded and error out if not. - error_message = f"[{self.branding}ENG] \033[91mError\033[0m: You currently have no TTS model loaded." - print(error_message) - raise HTTPException(status_code=400, detail="You currently have no TTS model loaded.") # Raise an exception with a meaningful HTTP status code - self.tts_generating_lock = True # Set the tts_generating lock to True, which stops other generation requests being sent into the pipeline - print(f"[{self.branding}Debug] Checking low VRAM") if self.debug_tts else None - if self.lowvram_enabled and self.device == "cpu": # If necessary, move the model out of System Ram to VRAM - print(f"[{self.branding}Debug] Switching device") if self.debug_tts else None - await self.handle_lowvram_change() - print(f"[{self.branding}Debug] Setting a generate time") if self.debug_tts else None - generate_start_time = time.time() # Record the start time of generating TTS - # ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ - # ↑↑↑ Keep everything above this line ↑↑↑ - # ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ - - if self.current_model_loaded.startswith("vits"): - print(f"[{self.branding}Debug] generate_tts Text arriving at engine {text}") if self.debug_tts else None - model_type = self.available_models[self.current_model_loaded] - if model_type == "vits_multi_speaker": - # Get the available speakers for the current model - model_name = self.current_model_loaded.replace("vits - ", "") - speaker_ids_path = self.main_dir / "models" / "vits" / model_name / "speaker_ids.json" - if speaker_ids_path.exists(): - with open(speaker_ids_path, "r") as f: - speaker_ids = json.load(f) - available_speakers = [speaker for speaker in speaker_ids.keys() if speaker != "ED\n"] - else: - print(f"[{self.branding}ENG] \033[91mWarning\033[0m: speaker_ids.json not found for model '{model_name}'.") - available_speakers = [] - - # Check if the specified voice matches any of the available speakers - if voice in available_speakers: - speaker_idx = voice - else: - # Use the first available speaker as the default - if available_speakers: - speaker_idx = available_speakers[0] - print(f"[{self.branding}ENG] Incorrect or no voice specified, using default voice: {speaker_idx}") - else: - speaker_idx = None - print(f"[{self.branding}ENG] No valid speakers found for the current model.") - - print(f"[{self.branding}Debug] generate_tts speaker_idx is:", speaker_idx) if self.debug_tts else None - print(f"[{self.branding}Debug] generate_tts output_file is:", output_file) if self.debug_tts else None - - # Generate TTS with speaker_name argument - if speaker_idx is not None: - wavs = self.model.tts(text, speaker_name=speaker_idx) - else: - wavs = None - else: - # Generate TTS without speaker_name argument for single-speaker models - wavs = self.model.tts(text) - - streaming = False - if streaming and wavs is not None: - # Streaming-specific operations - for wav_chunk in wavs: - yield wav_chunk.numpy().tobytes() - else: - if wavs is not None: - # Save the generated audio to a file - self.model.save_wav(wavs, output_file) - yield b"" # Yield an empty byte string to indicate completion - - # ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ - # ↓↓↓ Keep everything below this line ↓↓↓ - # ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ - generate_end_time = time.time() # Record the end time to generate TTS - generate_elapsed_time = generate_end_time - generate_start_time - print(f"[{self.branding}GEN] \033[94mTTS Generate: \033[93m{generate_elapsed_time:.2f} seconds. \033[94mLowVRAM: \033[33m{self.lowvram_enabled} \033[94mDeepSpeed: \033[33m{self.deepspeed_enabled}\033[0m") - if self.lowvram_enabled and self.device == "cuda" and self.tts_narrator_generatingtts == False: - await self.handle_lowvram_change() - self.tts_generating_lock = False # Unlock the TTS generation queue to allow TTS generation requests to come in again. - print(f"[{self.branding}Debug] generate_tts function completed") if self.debug_tts else None - return - - +############################################### +# DONT CHANGE # These are base imports needed # +############################################### +import os +import sys +import json +import time +import torch +import logging +import inspect +from pathlib import Path +from fastapi import (HTTPException) +logging.disable(logging.WARNING) +############################################### +# DONT CHANGE # Get Pytorch & Python versions # +############################################### +pytorch_version = torch.__version__ +cuda_version = torch.version.cuda +major, minor, micro = sys.version_info[:3] +python_version = f"{major}.{minor}.{micro}" +try: + import deepspeed + deepspeed_available = True +except ImportError: + deepspeed_available = False + pass + +############################################################################################################# +############################################################################################################# +# CHANGE ME # Run any specifc imports, requirements or setup any global vaiables needed for this TTS Engine # +############################################################################################################# +############################################################################################################# +# In this section you will import any imports that your specific TTS Engine will use. You will provide any +# start-up errors for those bits, as if you were starting up a normal Python script. Note the logging.disable +# a few lines up from here, you may want to # that out while debugging! +try: + from TTS.utils.synthesizer import Synthesizer +except ModuleNotFoundError: + print( + f"[Startup] \033[91mWarning\033[0m Could not find the TTS module. Make sure to install the requirements for the alltalk_tts extension.", + f"[Startup] \033[91mWarning\033[0m Linux / Mac:\npip install -r extensions/alltalk_tts/requirements.txt\n", + f"[Startup] \033[91mWarning\033[0m Windows:\npip install -r extensions\\alltalk_tts\\requirements.txt\n", + f"[Startup] \033[91mWarning\033[0m If you used the one-click installer, paste the command above in the terminal window launched after running the cmd_ script. On Windows, that's cmd_windows.bat." + ) + raise + +############################################################# +# DONT CHANGE # Do not change the Class name from tts_class # +############################################################# +class tts_class: + def __init__(self): + ######################################################################## + # DONT CHANGE # Sets up the base variables required for any tts engine # + ######################################################################## + self.branding = None + self.this_dir = Path(__file__).parent.resolve() # Sets up self.this_dir as a variable for the folder THIS script is running in. + self.main_dir = Path(__file__).parent.parent.parent.parent.resolve() # Sets up self.main_dir as a variable for the folder AllTalk is running in + self.device = "cuda" if torch.cuda.is_available() else "cpu" # Sets up self.device to cuda if torch exists with Nvidia/CUDA, otherwise sets to cpu + self.cuda_is_available = torch.cuda.is_available() # Sets up cuda_is_available as a True/False to track if Nvidia/CUDA was found on the system + self.tts_generating_lock = False # Used to lock and unlock the tts generation process at the start/end of tts generation. + self.tts_stop_generation = False # Used in conjunction with tts_generating_lock to call for a stop to the current generation. If called (set True) it needs to be set back to False when generation has been stopped. + self.tts_narrator_generatingtts = False # Used to track if the current tts processes is narrator based. This can be used in conjunction with lowvram and device to avoid moving model between GPU(CUDA)<>RAM(CPU) each chunk of narrated text generated. + self.model = None # If loading a model into CUDA/VRAM/RAM "model" is used as the variable name to load and interact with (see the XTTS model_engine script for examples.) + self.is_tts_model_loaded = False # Used to track if a model is actually loaded in and error/fail things like TTS generation if its False + self.current_model_loaded = None # Stores the name of the currenly loaded in model + self.available_models = None # List of available models found by "def scan_models_folder" + self.setup_has_run = False # Tracks if async def setup(self) has run, by setting to True, so that the /api/ready endpoint can provide a "Ready" status + ############################################################################################## + # DONT CHANGE # Load in a list of the available TTS engines and the currently set TTS engine # + ############################################################################################## + tts_engines_file = os.path.join(self.main_dir, "system", "tts_engines", "tts_engines.json") + with open(tts_engines_file, "r") as f: + tts_engines_data = json.load(f) + self.engines_available = [engine["name"] for engine in tts_engines_data["engines_available"]] # A list of ALL the TTS engines available to be loaded by AllTalk + self.engine_loaded = tts_engines_data["engine_loaded"] # In "tts_engines.json" what is the currently set TTS engine loading into AllTalk + self.selected_model = tts_engines_data["selected_model"] # In "tts_engines.json" what is the currently set TTS model loading into AllTalk + ############################################################################ + # DONT CHANGE # Pull out all the settings for the currently set TTS engine # + ############################################################################ + with open(os.path.join(self.this_dir, "model_settings.json"), "r") as f: + tts_model_loaded = json.load(f) + # Access the model details + self.manufacturer_name = tts_model_loaded["model_details"]["manufacturer_name"] # The company/person/body that generated the TTS engine/models etc + self.manufacturer_website = tts_model_loaded["model_details"]["manufacturer_website"] # The website of the company/person/body where people can find more information + # Access the features the model is capable of: + self.audio_format = tts_model_loaded["model_capabilties"]["audio_format"] # This details the audio format your TTS engine is set to generate TTS in e.g. wav, mp3, flac, opus, acc, pcm. Please use only 1x format. + self.deepspeed_capable = tts_model_loaded["model_capabilties"]["deepspeed_capable"] # Is your model capable of DeepSpeed + self.deepspeed_available = 'deepspeed' in globals() # When we did the import earlier, at the top of this script, was DeepSpeed available for use + self.generationspeed_capable = tts_model_loaded["model_capabilties"]["generationspeed_capable"] # Does this TTS engine support changing the speed of the generated TTS + self.languages_capable = tts_model_loaded["model_capabilties"]["languages_capable"] # Are the actual models themselves capable of generating in multiple languages OR is each model language specific + self.lowvram_capable = tts_model_loaded["model_capabilties"]["lowvram_capable"] # Is this engine capable of using low VRAM (moving the model between CPU And GPU memory) + self.multimodel_capable = tts_model_loaded["model_capabilties"]["multimodel_capable"] # Is there just the one model or are there multiple models this engine supports. + self.repetitionpenalty_capable = tts_model_loaded["model_capabilties"]["repetitionpenalty_capable"] # Is this TTS engine capable of changing the repititon penalty + self.streaming_capable = tts_model_loaded["model_capabilties"]["streaming_capable"] # Is this TTS engine capabale of generating streaming audio + self.temperature_capable = tts_model_loaded["model_capabilties"]["temperature_capable"] # Is this TTS engine capable of changing the temperature of the models + self.multivoice_capable = tts_model_loaded["model_capabilties"]["multivoice_capable"] # Are the models multi-voice or single vocice models + self.pitch_capable = tts_model_loaded["model_capabilties"]["pitch_capable"] # Is this TTS engine capable of changing the pitch of the genrated TTS + # Access the current enginesettings + self.def_character_voice = tts_model_loaded["settings"]["def_character_voice"] # What is the current default main/character voice that will be used if no voice specified. + self.def_narrator_voice = tts_model_loaded["settings"]["def_narrator_voice"] # What is the current default narrator voice that will be used if no voice specified. + self.deepspeed_enabled = tts_model_loaded["settings"]["deepspeed_enabled"] # If its available, is DeepSpeed enabled for the TTS engine + self.engine_installed = tts_model_loaded["settings"]["engine_installed"] # Has the TTS engine been setup/installed (not curently used) + self.generationspeed_set = tts_model_loaded["settings"]["generationspeed_set"] # What is the set/stored speed for generation. + self.lowvram_enabled = tts_model_loaded["settings"]["lowvram_enabled"] # If its available, is LowVRAM enabled for the TTS engine + # Check if someone has enabled lowvram on a system that's not CUDA enabled + self.lowvram_enabled = False if not torch.cuda.is_available() else self.lowvram_enabled # If LowVRAM is mistakenly set and CUDA is not available, this will force it back off + self.repetitionpenalty_set = tts_model_loaded["settings"]["repetitionpenalty_set"] # What is the currenly set repitition policy of the model (If it support repetition) + self.temperature_set = tts_model_loaded["settings"]["temperature_set"] # What is the currenly set temperature of the model (If it support temp) + self.pitch_set = tts_model_loaded["settings"]["pitch_set"] # What is the currenly set pitch of the model (If it support temp) + # Gather the OpenAI API Voice Mappings + self.openai_alloy = tts_model_loaded["openai_voices"]["alloy"] # The TTS engine voice that will be mapped to Open AI Alloy voice + self.openai_echo = tts_model_loaded["openai_voices"]["echo"] # The TTS engine voice that will be mapped to Open AI Echo voice + self.openai_fable = tts_model_loaded["openai_voices"]["fable"] # The TTS engine voice that will be mapped to Open AI Fable voice + self.openai_nova = tts_model_loaded["openai_voices"]["nova"] # The TTS engine voice that will be mapped to Open AI Nova voice + self.openai_onyx = tts_model_loaded["openai_voices"]["onyx"] # The TTS engine voice that will be mapped to Open AI Onyx voice + self.openai_shimmer = tts_model_loaded["openai_voices"]["shimmer"] # The TTS engine voice that will be mapped to Open AI Shimmer voice + ################################################################### + # DONT CHANGE # Load params and api_defaults from confignew.json # + ################################################################### + # Define the path to the confignew.json file + configfile_path = self.main_dir / "confignew.json" + # Load config file and get settings + with open(configfile_path, "r") as configfile: + configfile_data = json.load(configfile) + self.branding = configfile_data.get("branding", "") # Sets up self.branding for outputting the name stored in the "confgnew.json" file, as used in print statements. + self.params = configfile_data # Loads in the curent "confgnew.json" file to self.params. + self.debug_tts = configfile_data.get("debugging").get("debug_tts") # Can be used within this script as a True/False flag for generally debugging the TTS generation process. + self.debug_tts_variables = configfile_data.get("debugging").get("debug_tts_variables") # Can be used within this script as a True/False flag for generally debugging variables (if you wish). + ################################################################ + # DONT CHANGE # Print out Python, CUDA, DeepSpeed versions #### + ################################################################ + def printout_versions(self): + if deepspeed_available: + print(f"[{self.branding}ENG] \033[92mDeepSpeed version :\033[93m",deepspeed.__version__,"\033[0m") + else: + print(f"[{self.branding}ENG] \033[92mDeepSpeed version :\033[93m Not available\033[0m") + print(f"[{self.branding}ENG] \033[92mPython Version :\033[93m {python_version}\033[0m") + print(f"[{self.branding}ENG] \033[92mPyTorch Version :\033[93m {pytorch_version}\033[0m") + if cuda_version is None: + print(f"[{self.branding}ENG] \033[92mCUDA Version :\033[91m Not available\033[0m") + else: + print(f"[{self.branding}ENG] \033[92mCUDA Version :\033[93m {cuda_version}\033[0m") + print(f"[{self.branding}ENG]") + return + + + ############################################### + # Central print function # Do not change this # + ############################################### + def debug_func_entry(self): + """Log function entry if debug_func is enabled.""" + quick_debug = False + if quick_debug: + current_func = inspect.currentframe().f_back.f_code.co_name + print(f"Function entry: {current_func}", "debug_func") + + + ################################################################################### + ################################################################################### + # CHANGE ME # Inital setup of the model and engine. Called when the script starts # + ################################################################################### + ################################################################################### + # In here you will add code to load in your model and do its inital setup. So you + # may be calling your model loader via handle_tts_method_change or if your TTS + # engine doesnt actually load a model into CUDA or System RAM, you may be doing + # Something to fake its start-up. + async def setup(self): + self.debug_func_entry() + self.printout_versions() + self.available_models = self.scan_models_folder() + # ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ + # ↑↑↑ Keep everything above this line ↑↑↑ + # ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ + + print("in setup - self.selected_model:", self.selected_model) if self.debug_tts else None + if self.selected_model: + tts_model = f"{self.selected_model}" + print("in setup - tts_model:", tts_model) if self.debug_tts else None + if tts_model in self.available_models: + print("in setup - sending to handle_tts_method_change:") if self.debug_tts else None + await self.handle_tts_method_change(tts_model) + self.current_model_loaded = tts_model + + # ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ + # ↓↓↓ Keep everything below this line ↓↓↓ + # ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ + else: + self.current_model_loaded = "No Models Available" + print(f"[{self.branding}ENG] \033[91mError\033[0m: Selected model '{self.selected_model}' not found in the models folder.") + self.setup_has_run = True # Set to True, so that the /api/ready endpoint can provide a "Ready" status + + ################################## + ################################## + # CHANGE ME # Low VRAM Swapping # + ################################## + ################################## + # If your model does load into CUDA and you want to support LowVRAM, aka moving the model + # on the fly between CUDA and System RAM on each generation request, you will be adding + # The code in here to do it. See the XTTS tts engine for an example. Piper however, doesnt + # Load into VRAM or System RAM, also low vram is set globally disabled by the model_settings.JSON + # file, so this would never get called anywyay, however, we still need to keep an empty + # function in place. Piper TTS uses "pass" telling function to just exit out cleanly if called. + # However, its quite a simple check along the lines of "if CUDA is available and model is + # in X place, then send it to Y place (or Y to X). + async def handle_lowvram_change(self): + self.debug_func_entry() + if torch.cuda.is_available(): + if self.device == "cuda": + self.device = "cpu" + self.model.to(self.device) + torch.cuda.empty_cache() + else: + self.device == "cpu" + self.device = "cuda" + self.model.to(self.device) + + ######################################## + ######################################## + # CHANGE ME # DeepSpeed model loading # + ######################################## + ######################################## + # If the model supports CUDA and DeepSpeed, this is where you can handle re-loading + # the model as/when DeepSpeed is enabled/selected in the user interface. If your + # TTS model doesnt support DeepSpeed, then it should be globally set in your + # model_settings.JSON and this function will never get called, however it still needs + # to exist as a function. + async def handle_deepspeed_change(self, value): + self.debug_func_entry() + if value: + # DeepSpeed enabled + print(f"[{self.branding}ENG] \033[93mDeepSpeed Activating\033[0m") + await self.unload_model() + self.params["tts_method_api_local"] = False + self.deepspeed_enabled = True + await self.setup() + else: + # DeepSpeed disabled + print(f"[{self.branding}ENG] \033[93mDeepSpeed De-Activating\033[0m") + self.deepspeed_enabled = False + await self.unload_model() + await self.setup() + return value # Return new checkbox value + + ############################################################################################################################################## + ############################################################################################################################################## + # CHANGE ME # scan for available models/voices that are relevant to this TTS engine # XTTS is very unusal as it has 2x model loading methods # + ############################################################################################################################################## + ############################################################################################################################################## + # This function looks and reports back the list of possible models your TTS engine can + # load in. Some TTS engines have multiple models they can load and you will want to use + # code for checking if the models are in the correct location/placement within the disk + # the correct files exst per model etc (check XTTS for an example of this). Some models + # like Piper, the actual models are the voices, so in the Piper scan_models_folder + # function, we fake the only model being available as {'piper': 'piper'} aka, model name + # then engine name, then we use the voices_file_list to populate the models as available + # voices that can be selected in the interface. + # If no models are found, we return "No Models Available" and continue on with the script. + def scan_models_folder(self): + self.debug_func_entry() + models_folder = self.main_dir / "models" / "vits" # Edit to match the name of your folder where voices are stored + self.available_models = {} + # ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ + # ↑↑↑ Keep everything above this line ↑↑↑ + # ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ + + # Define possible model file names + model_files = ["model_file.pth", "model.pth"] + + found_any_models = False + for subfolder in models_folder.iterdir(): + if subfolder.is_dir(): + model_name = subfolder.name + + # Check if config.json exists (this is the minimum requirement) + if not (subfolder / "config.json").exists(): + print(f"[{self.branding}ENG] \033[91mWarning\033[0m: Model folder '{model_name}' missing config.json") + continue + + # Load config to check model type + try: + with open(subfolder / "config.json", "r") as f: + config = json.load(f) + use_speaker_embedding = config["model_args"].get("use_speaker_embedding", False) + num_speakers = config["model_args"].get("num_speakers", 0) + except Exception as e: + print(f"[{self.branding}ENG] \033[91mWarning\033[0m: Couldn't read config.json for '{model_name}': {str(e)}") + continue + + # Check for model file + has_model_file = any(subfolder.joinpath(model_file).exists() for model_file in model_files) + has_model_file_tar = subfolder.joinpath("model_file.pth.tar").exists() + + if not (has_model_file or has_model_file_tar): + print(f"[{self.branding}ENG] \033[91mWarning\033[0m: Model folder '{model_name}' missing model file") + continue + + # Determine model type based on files and config + if (subfolder / "language_ids.json").exists() and (subfolder / "speaker_ids.json").exists(): + self.available_models[f"vits - {model_name}"] = "vits_multi_language" + found_any_models = True + #print(f"[{self.branding}ENG] Found multi-language model: {model_name}") + + elif (subfolder / "speaker_ids.json").exists() or (use_speaker_embedding or num_speakers > 0): + self.available_models[f"vits - {model_name}"] = "vits_multi_speaker" + found_any_models = True + #print(f"[{self.branding}ENG] Found multi-speaker model: {model_name}") + + else: + self.available_models[f"vits - {model_name}"] = "vits_single_speaker" + found_any_models = True + #print(f"[{self.branding}ENG] Found single-speaker model: {model_name}") + + # ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ + # ↓↓↓ Keep everything below this line ↓↓↓ + # ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ + + if not found_any_models: + self.available_models = {'No Models Available': 'vits'} # Change the name in here to your TTS engine name + print(f"[{self.branding}ENG] \033[91mWarning\033[0m: Model folder '{model_name}' is missing required") + print(f"[{self.branding}ENG] \033[91mWarning\033[0m: files or the folder does not exist.") + + return self.available_models + + ############################################################# + ############################################################# + # CHANGE ME # POPULATE FILES LIST FROM VOICES DIRECTORY #### + ############################################################# + ############################################################# + # This function looks and reports back the list of possible voice your TTS engine can + # load in. Some TTS engines the voices are wav file samples (XTTS), some are models + # (Piper) and some are text (Parler) thats stored in a JSON file. We just need to + # populate the "voices" variable somehow and if no voices are found, we return + # "No Voices Found" back to the interface/api. + def voices_file_list(self): + self.debug_func_entry() + try: + voices = [] + + model_name = self.selected_model.replace("vits - ", "") + print("model_name", model_name) if self.debug_tts else None + model_type = self.available_models[self.selected_model] + print("model_type", model_type) if self.debug_tts else None + + # Load config to check model type + config_path = self.main_dir / "models" / "vits" / model_name / "config.json" + if config_path.exists(): + with open(config_path, "r") as f: + config = json.load(f) + use_speaker_embedding = config["model_args"].get("use_speaker_embedding", False) + num_speakers = config["model_args"].get("num_speakers", 0) + else: + print(f"[{self.branding}ENG] \033[91mWarning\033[0m: config.json not found for model '{model_name}'") + return ["No Voices Found"] + + # Check for speakers file and model configuration + if model_type in ["vits_multi_speaker", "vits_multi_language"]: + speaker_ids_path = self.main_dir / "models" / "vits" / model_name / "speaker_ids.json" + if speaker_ids_path.exists(): + with open(speaker_ids_path, "r") as f: + speaker_ids = json.load(f) + voices = [speaker for speaker in speaker_ids.keys() if speaker != "ED\n"] + print(f"[{self.branding}ENG] Found {len(voices)} voices in speaker_ids.json") if self.debug_tts else None + elif use_speaker_embedding or num_speakers > 0: + # If there's no speaker_ids.json but config indicates multiple speakers, + # create a default list of numbered speakers + voices = [f"Speaker{i}" for i in range(num_speakers)] + print(f"[{self.branding}ENG] Created {len(voices)} default voice names from config") if self.debug_tts else None + else: + print(f"[{self.branding}ENG] No speaker information found for multi-speaker model '{model_name}'") if self.debug_tts else None + + elif model_type == "vits_single_speaker": + voices = ["default"] + print(f"[{self.branding}ENG] Using default voice for single-speaker model") if self.debug_tts else None + + if not voices: # If the voices list is empty + voices = ["default"] # Fallback to default if no voices were found + print(f"[{self.branding}ENG] No voices found, using default") if self.debug_tts else None + + return voices # Return the list of voices + + except Exception as e: + print(f"[{self.branding}ENG] \033[91mError\033[0m: Voices/Voice Models not found. Cannot load a list of voices.") + print(f"[{self.branding}ENG] Exception: {str(e)}") + return ["No Voices Found"] + + ############################# + ############################# + # CHANGE ME # Model loading # + ############################# + ############################# + # This function will handle the loading of your model, into VRAM/CUDA, System RAM or whatever. + # In XTTS, which has 2x model loader types, there are 2x loaders. They are called by "def handle_tts_method_change" + # In Piper we fake a model loader as Piper doesnt actually load a model into CUDA/System RAM as such. So, in that + # situation, api_manual_load_model is kind of a blank function. Though we do set self.is_tts_model_loaded = True + # as this is used elsewhere in the scripts to confirm that a model is available to be used for TTS generation. + # We always check for "No Models Available" being sent as that means we are trying to load in a model that + # doesnt exist/wasnt found on script start-up e.g. someone deleted the model from the folder or something. + async def api_manual_load_model(self, model_name): + self.debug_func_entry() + if "No Models Available" in self.available_models: + print(f"[{self.branding}ENG] \033[91mError\033[0m: No models for this TTS engine were found to load. Please download a model.") + return + model_path = self.main_dir / "models" / "vits" / model_name # change the name to the correct folder name + print("model_path is:", model_path) if self.debug_tts_variables else None + # ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ + # ↑↑↑ Keep everything above this line ↑↑↑ + # ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ + + # Load the config file + config_file_path = model_path / "config.json" + with open(config_file_path, "r") as f: + config_dict = json.load(f) + # Check if the model is single-speaker, multi-speaker, or multi-language + model_type = self.available_models[f"vits - {model_name}"] + use_speaker_embedding = config_dict["model_args"]["use_speaker_embedding"] + num_speakers = config_dict["model_args"]["num_speakers"] + if model_type == "vits_multi_speaker" or model_type == "vits_multi_language": + # Update the speakers_file path in the config dictionary + config_dict["model_args"]["speakers_file"] = str(model_path / "speaker_ids.json") + speakers_file = model_path / "speaker_ids.json" + else: + # Set speakers_file to None for single-speaker models + config_dict["model_args"]["speakers_file"] = None + speakers_file = None + # Save the updated config dictionary back to the config.json file + with open(config_file_path, "w") as f: + json.dump(config_dict, f, indent=4) + + def get_model_path(model_dir, model_type): + """Helper function to find the correct model file""" + print(f"Searching for model file in {model_dir} for type {model_type}") if self.debug_tts else None + + if model_type == "vits_multi_language": + # Check for .pth.tar file first for multi-language models + if (model_dir / "model_file.pth.tar").exists(): + print(f"Found model_file.pth.tar") if self.debug_tts else None + return model_dir / "model_file.pth.tar" + + # Check for regular .pth files + if (model_dir / "model_file.pth").exists(): + print(f"Found model_file.pth") if self.debug_tts else None + return model_dir / "model_file.pth" + elif (model_dir / "model.pth").exists(): + print(f"Found model.pth") if self.debug_tts else None + return model_dir / "model.pth" + + print(f"No model file found in {model_dir}") if self.debug_tts else None + raise FileNotFoundError(f"No model file found in {model_dir}") + + # Then in api_manual_load_model: + model_type = self.available_models[f"vits - {model_name}"] + model_file = get_model_path(model_path, model_type) + + if model_type == "vits_multi_language": + # Load multi-language model + self.model = Synthesizer( + tts_checkpoint=model_file, # Use the detected model file + tts_config_path=config_file_path, + tts_speakers_file=speakers_file, + tts_languages_file=model_path / "language_ids.json", + use_cuda=self.cuda_is_available, + ) + elif not use_speaker_embedding and num_speakers == 0: + # Single-speaker model without speaker embeddings + self.model = Synthesizer( + tts_checkpoint=model_file, + tts_config_path=config_file_path, + use_cuda=self.cuda_is_available, + ) + else: + # Multi-speaker model or single-speaker model with speaker embeddings + self.model = Synthesizer( + tts_checkpoint=model_file, + tts_config_path=config_file_path, + tts_speakers_file=speakers_file, + use_cuda=self.cuda_is_available, + ) + + # ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ + # ↓↓↓ Keep everything below this line ↓↓↓ + # ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ + self.is_tts_model_loaded = True + return self.model + + ############################### + ############################### + # CHANGE ME # Model unloading # + ############################### + ############################### + # This function will handle the UN-loading of your model, from VRAM/CUDA, System RAM or whatever. + # In XTTS, that model loads into CUDA/System Ram, so when we swap models, we want to unload the current model + # free up the memory and then load in the new model to VRAM/CUDA. On the flip side of that, Piper doesnt + # doesnt load into memory, so we just need to put a fake function here that doesnt really do anything + # other than set "self.is_tts_model_loaded = False", which would be set back to true by the model loader. + # So look at the Piper model_engine.py if you DONT need to unload models. + async def unload_model(self): + self.debug_func_entry() + self.is_tts_model_loaded = False + if not self.current_model_loaded == None: + print(f"[{self.branding}ENG] \033[94mUnloading model \033[0m") if self.debug_tts else None + if hasattr(self, 'model'): + del self.model + if torch.cuda.is_available(): + torch.cuda.empty_cache() + return None + + ################################################################################################################################### + ################################################################################################################################### + # CHANGE ME # Model changing. Unload out old model and load in a new one # XTTS is very unusal as it has 2x model loading methods # + ################################################################################################################################### + ################################################################################################################################### + # This function is your central model loading/unloading handler that deals with the above functions as necesary, to call loading, unloading, + # swappng DeepSpeed, Low vram etc. This function gets called with a "engine name - model name" type call. In XTTS, because there are 2x + # model loader types, (XTTS and APILocal), we take tts_method and split the "engine name - model name" into a loader type and the model + # that it needs to load in and then we call the correct loader function. Whereas in Piper, which doesnt load models into memory at all, + # we just have a fake function that doesnt really do anything. We always check to see if the model name has "No Models Available" in the + # name thats sent over, just to catch any potential errors. We display the start load time and end load time. Thats about it. + async def handle_tts_method_change(self, tts_method): + self.debug_func_entry() + generate_start_time = time.time() # Record the start time of loading the model + if "No Models Available" in self.available_models: + print(f"[{self.branding}ENG] \033[91mError\033[0m: No models for this TTS engine were found to load. Please download a model.") + return False + # ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ + # ↑↑↑ Keep everything above this line ↑↑↑ + # ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ + + await self.unload_model() + # Set other parameters to False + if tts_method.startswith("vits"): + model_name = tts_method.split(" - ")[1] + print(f"[{self.branding}ENG]\033[94m Model/Engine :\033[93m {model_name}\033[94m loading into\033[93m", self.device,"\033[0m") + self.model = await self.api_manual_load_model(model_name) + self.current_model_loaded = f"vits - {model_name}" + self.selected_model = tts_method # Update self.selected_model + else: + self.current_model_loaded = None + return False + + # ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ + # ↓↓↓ Keep everything below this line ↓↓↓ + # ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ + generate_end_time = time.time() # Create an end timer for calculating load times + generate_elapsed_time = generate_end_time - generate_start_time # Calculate start time minus end time + print(f"[{self.branding}ENG] \033[94mLoad time :\033[93m {generate_elapsed_time:.2f} seconds.\033[0m") # Print out the result of the load time + return True + + ########################################################################################################################################## + ########################################################################################################################################## + # CHANGE ME # Model changing. Unload out old model and load in a new one # XTTS is very unusal as it has 2x model TTS generation methods # + ########################################################################################################################################## + ########################################################################################################################################## + # In here all the possible options are sent over (text, voice to use, lanugage, speed etc etc) and its up to you how you use them, or not. + # obviously if your TTS engine doesnt support speed for example, generationspeed_capable should be set False in your model_settings.JSON file + # and a fake "generationspeed_set" value should be set. This allows a fake value to be sent over from the main script, even though it + # wouldnt actually ever be used in the generation below. Nonethless all these values, real or just whats inside the configuration file + # will be sent over for use. + # Setting the xxxxxxx_capabale in the model_settings.JSON file, will enable/disable them being selectable by the user. For example, if you + # set "generationspeed_capable" as false in the model_settings.JSON file, a user will not be able to select OR set the setting for + # generation speed. + # One thing to note is that we HAVE to have something in this generation request that is synchronous from the way its called, which means + # we have to have an option for Streaming, even if our TTS engine doesnt support streaming. So in that case, we would set streaming_capable + # as false in our model_settings.JSON file, meaning streaming will never be called. However, we have to put a fake streaming routine in our + # function below (or a real function if it does support streaming of course). Parler has an example of a fake streaming function, which is + # very clearly highlighted in its model_engine.py script. + # Piper TTS, which uses command line based calls and therefore has different ones for Windows and Linux/Mac, has an example of doing this + # within its model_engine.py file. + async def generate_tts(self, text, voice, language, temperature, repetition_penalty, speed, pitch, output_file, streaming): + self.debug_func_entry() + print(f"[{self.branding}Debug] Entered model_engine.py generate_tts function") if self.debug_tts else None + if not self.is_tts_model_loaded: # Check if a model is loaded and error out if not. + error_message = f"[{self.branding}ENG] \033[91mError\033[0m: You currently have no TTS model loaded." + print(error_message) + raise HTTPException(status_code=400, detail="You currently have no TTS model loaded.") # Raise an exception with a meaningful HTTP status code + self.tts_generating_lock = True # Set the tts_generating lock to True, which stops other generation requests being sent into the pipeline + print(f"[{self.branding}Debug] Checking low VRAM") if self.debug_tts else None + if self.lowvram_enabled and self.device == "cpu": # If necessary, move the model out of System Ram to VRAM + print(f"[{self.branding}Debug] Switching device") if self.debug_tts else None + await self.handle_lowvram_change() + print(f"[{self.branding}Debug] Setting a generate time") if self.debug_tts else None + generate_start_time = time.time() # Record the start time of generating TTS + # ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ + # ↑↑↑ Keep everything above this line ↑↑↑ + # ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ + + if self.current_model_loaded.startswith("vits"): + print(f"[{self.branding}Debug] generate_tts Text arriving at engine {text}") if self.debug_tts else None + model_type = self.available_models[self.current_model_loaded] + if model_type == "vits_multi_speaker": + # Get the available speakers for the current model + model_name = self.current_model_loaded.replace("vits - ", "") + speaker_ids_path = self.main_dir / "models" / "vits" / model_name / "speaker_ids.json" + if speaker_ids_path.exists(): + with open(speaker_ids_path, "r") as f: + speaker_ids = json.load(f) + available_speakers = [speaker for speaker in speaker_ids.keys() if speaker != "ED\n"] + else: + print(f"[{self.branding}ENG] \033[91mWarning\033[0m: speaker_ids.json not found for model '{model_name}'.") + available_speakers = [] + + # Check if the specified voice matches any of the available speakers + if voice in available_speakers: + speaker_idx = voice + else: + # Use the first available speaker as the default + if available_speakers: + speaker_idx = available_speakers[0] + print(f"[{self.branding}ENG] Incorrect or no voice specified, using default voice: {speaker_idx}") + else: + speaker_idx = None + print(f"[{self.branding}ENG] No valid speakers found for the current model.") + + print(f"[{self.branding}Debug] generate_tts speaker_idx is:", speaker_idx) if self.debug_tts else None + print(f"[{self.branding}Debug] generate_tts output_file is:", output_file) if self.debug_tts else None + + # Generate TTS with speaker_name argument + if speaker_idx is not None: + wavs = self.model.tts(text, speaker_name=speaker_idx) + else: + wavs = None + else: + # Generate TTS without speaker_name argument for single-speaker models + wavs = self.model.tts(text) + + streaming = False + if streaming and wavs is not None: + # Streaming-specific operations + for wav_chunk in wavs: + yield wav_chunk.numpy().tobytes() + else: + if wavs is not None: + # Save the generated audio to a file + self.model.save_wav(wavs, output_file) + yield b"" # Yield an empty byte string to indicate completion + + # ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ + # ↓↓↓ Keep everything below this line ↓↓↓ + # ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ + generate_end_time = time.time() # Record the end time to generate TTS + generate_elapsed_time = generate_end_time - generate_start_time + print(f"[{self.branding}GEN] \033[94mTTS Generate: \033[93m{generate_elapsed_time:.2f} seconds. \033[94mLowVRAM: \033[33m{self.lowvram_enabled} \033[94mDeepSpeed: \033[33m{self.deepspeed_enabled}\033[0m") + if self.lowvram_enabled and self.device == "cuda" and self.tts_narrator_generatingtts == False: + await self.handle_lowvram_change() + self.tts_generating_lock = False # Unlock the TTS generation queue to allow TTS generation requests to come in again. + print(f"[{self.branding}Debug] generate_tts function completed") if self.debug_tts else None + return + + diff --git a/system/tts_engines/vits/model_settings.json b/system/tts_engines/vits/model_settings.json index 24899c5..f50609e 100644 --- a/system/tts_engines/vits/model_settings.json +++ b/system/tts_engines/vits/model_settings.json @@ -1,42 +1,42 @@ -{ - "model_details": { - "manufacturer_name": "Coqui", - "manufacturer_website": "https://github.com/coqui-ai/TTS and also updated engine https://github.com/idiap/coqui-ai-TTS/", - "model_description": "VITS models come in single and multi-speaker variants. VITS (Conditional Variational Autoencoder with Adversarial Learning for End-to-End Text-to-Speech ) is an End-to-End (encoder -> vocoder together) TTS model that takes advantage of SOTA DL techniques like GANs, VAE, Normalizing Flows. It does not require external alignment annotations and learns the text-to-audio alignment using MAS, as explained in the paper. The model architecture is a combination of GlowTTS encoder and HiFiGAN vocoder. It is a feed-forward model with x67.12 real-time factor on a GPU. \n\ntts_models--en--vctk--vits is a good example of a multi speaker VITS model." - }, - "model_capabilties": { - "audio_format": "wav", - "deepspeed_capable": false, - "generationspeed_capable": false, - "languages_capable": false, - "lowvram_capable": true, - "multimodel_capable": true, - "repetitionpenalty_capable": false, - "streaming_capable": false, - "temperature_capable": false, - "multivoice_capable": true, - "pitch_capable": false, - "windows_capable": true, - "linux_capable": true, - "mac_capable": true - }, - "settings": { - "def_character_voice": "p225", - "def_narrator_voice": "p226", - "deepspeed_enabled": false, - "engine_installed": true, - "generationspeed_set": 1, - "lowvram_enabled": true, - "pitch_set": 0, - "repetitionpenalty_set": 10, - "temperature_set": 0.75 - }, - "openai_voices": { - "alloy": "p225", - "echo": "p225", - "fable": "p225", - "nova": "p225", - "onyx": "p225", - "shimmer": "p225" - } +{ + "model_details": { + "manufacturer_name": "Coqui", + "manufacturer_website": "https://github.com/coqui-ai/TTS and also updated engine https://github.com/idiap/coqui-ai-TTS/", + "model_description": "VITS models come in single and multi-speaker variants. VITS (Conditional Variational Autoencoder with Adversarial Learning for End-to-End Text-to-Speech ) is an End-to-End (encoder -> vocoder together) TTS model that takes advantage of SOTA DL techniques like GANs, VAE, Normalizing Flows. It does not require external alignment annotations and learns the text-to-audio alignment using MAS, as explained in the paper. The model architecture is a combination of GlowTTS encoder and HiFiGAN vocoder. It is a feed-forward model with x67.12 real-time factor on a GPU. \n\ntts_models--en--vctk--vits is a good example of a multi speaker VITS model." + }, + "model_capabilties": { + "audio_format": "wav", + "deepspeed_capable": false, + "generationspeed_capable": false, + "languages_capable": false, + "lowvram_capable": true, + "multimodel_capable": true, + "repetitionpenalty_capable": false, + "streaming_capable": false, + "temperature_capable": false, + "multivoice_capable": true, + "pitch_capable": false, + "windows_capable": true, + "linux_capable": true, + "mac_capable": true + }, + "settings": { + "def_character_voice": "p225", + "def_narrator_voice": "p226", + "deepspeed_enabled": false, + "engine_installed": true, + "generationspeed_set": 1, + "lowvram_enabled": true, + "pitch_set": 0, + "repetitionpenalty_set": 10, + "temperature_set": 0.75 + }, + "openai_voices": { + "alloy": "p225", + "echo": "p225", + "fable": "p225", + "nova": "p225", + "onyx": "p225", + "shimmer": "p225" + } } \ No newline at end of file diff --git a/system/tts_engines/vits/vits_settings_page.py b/system/tts_engines/vits/vits_settings_page.py index d8e024d..3232698 100644 --- a/system/tts_engines/vits/vits_settings_page.py +++ b/system/tts_engines/vits/vits_settings_page.py @@ -1,395 +1,538 @@ -import os -import json -import shutil -import zipfile -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 vits_voices_file_list(): - models_folder = main_dir / "models" / "vits" - # Create the folder if it doesn't exist - if not models_folder.exists(): - models_folder.mkdir(parents=True, exist_ok=True) - voices = [] - available_models = {} - required_files_multi_speaker = ["config.json", "model_file.pth", "speaker_ids.json"] - required_files_single_speaker = ["config.json", "model_file.pth"] - required_files_multi_language = ["config.json", "model_file.pth.tar", "speaker_ids.json", "language_ids.json"] - for subfolder in models_folder.iterdir(): - if subfolder.is_dir(): - model_name = subfolder.name - if all(subfolder.joinpath(file).exists() for file in required_files_multi_speaker): - available_models[f"VITS - {model_name}"] = "VITS_multi_speaker" - elif all(subfolder.joinpath(file).exists() for file in required_files_single_speaker): - available_models[f"VITS - {model_name}"] = "VITS_single_speaker" - elif all(subfolder.joinpath(file).exists() for file in required_files_multi_language): - available_models[f"VITS - {model_name}"] = "VITS_multi_language" - else: - print(f"[AllTalk ENG] \033[91mWarning\033[0m: Model '{model_name}' does not match any known model type.") - model_type = available_models.get(f"VITS - {model_name}") - if model_type == "VITS_multi_speaker" or model_type == "VITS_multi_language": - speaker_ids_path = subfolder / "speaker_ids.json" - if speaker_ids_path.exists(): - with open(speaker_ids_path, "r") as f: - speaker_ids = json.load(f) - speakers = [speaker for speaker in speaker_ids.keys() if speaker != "ED\n"] - voices.extend(speakers) - else: - print(f"[AllTalk ENG] \033[91mWarning\033[0m: speaker_ids.json not found for model '{model_name}'.") - elif model_type == "VITS_single_speaker": - voices.append("default") - # Remove duplicate speaker names - voices = list(set(voices)) - # Sort the voices alphabetically - voices.sort() - # Move the "default" voice to the top of the list - if "default" in voices: - voices.remove("default") - voices.insert(0, "default") - return voices - -###################################################### -# 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 vits_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 vits_model_alltalk_settings(model_config_data): - features_list = model_config_data['model_capabilties'] - voice_list = vits_voices_file_list() - with gr.Blocks(title="vits TTS", analytics_enabled=False) 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(vits_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="VITS", 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. - """) - - ####################################################################################################################################################################################################### - # REQUIRED CHANGE # - # You will need to build a custom method to identify if your models are installed and download them. Store your models list in the available_models.json file, alongside your model_settings.json etc # - ####################################################################################################################################################################################################### - 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 ZIP file URL - vits_folder = os.path.join(main_dir, "models", "vits") - folder_path = os.path.join(vits_folder, selected_model["folder_path"]) - zip_url = selected_model["github_rls_url"] - # Check if the model is already downloaded based on the file structure - required_files = ["config.json"] - if os.path.exists(os.path.join(folder_path, "model_file.pth.tar")): - required_files.append("model_file.pth.tar") - else: - required_files.append("model_file.pth") - if os.path.exists(os.path.join(folder_path, "speaker_ids.json")): - required_files.append("speaker_ids.json") - if os.path.exists(os.path.join(folder_path, "language_ids.json")): - required_files.append("language_ids.json") - if all(os.path.exists(os.path.join(folder_path, file)) for file in required_files): - return "The model is already downloaded. No need to download again." - else: - # Download the ZIP file to the "vits" folder - print(f"Downloading {model_name}...") - response = requests.get(zip_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) - zip_file_path = os.path.join(vits_folder, f"{model_name}.zip") - with open(zip_file_path, "wb") as file: - for data in response.iter_content(block_size): - progress_bar.update(len(data)) - file.write(data) - progress_bar.close() - # Extract the ZIP file contents to the desired folder - with zipfile.ZipFile(zip_file_path, "r") as zip_ref: - zip_ref.extractall(vits_folder) - # Remove the ZIP file - os.remove(zip_file_path) - # Delete the __MACOSX folder if it exists - macosx_folder = os.path.join(vits_folder, "__MACOSX") - if os.path.exists(macosx_folder): - shutil.rmtree(macosx_folder) - return "Model downloaded successfully!" - - def show_confirm_cancel(model_name): - selected_model = next(model for model in available_models["models"] if model["model_name"] == model_name) - folder_path = os.path.join(main_dir, "models", "vits", selected_model["folder_path"]) - required_files = ["config.json"] - if os.path.exists(os.path.join(folder_path, "model_file.pth.tar")): - required_files.append("model_file.pth.tar") - else: - required_files.append("model_file.pth") - if os.path.exists(os.path.join(folder_path, "speaker_ids.json")): - required_files.append("speaker_ids.json") - if os.path.exists(os.path.join(folder_path, "language_ids.json")): - required_files.append("language_ids.json") - if all(os.path.exists(os.path.join(folder_path, file)) for file in required_files): - 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): - # 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 ZIP file URL - vits_folder = os.path.join(main_dir, "models", "vits") - folder_path = os.path.join(vits_folder, selected_model["folder_path"]) - zip_url = selected_model["github_rls_url"] - # Download the ZIP file to the "vits" folder - print(f"Downloading {model_name}...") - response = requests.get(zip_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) - zip_file_path = os.path.join(vits_folder, f"{model_name}.zip") - with open(zip_file_path, "wb") as file: - for data in response.iter_content(block_size): - progress_bar.update(len(data)) - file.write(data) - progress_bar.close() - # Extract the ZIP file contents to the desired folder - with zipfile.ZipFile(zip_file_path, "r") as zip_ref: - zip_ref.extractall(vits_folder) - # Remove the ZIP file - os.remove(zip_file_path) - # Delete the __MACOSX folder if it exists - macosx_folder = os.path.join(vits_folder, "__MACOSX") - if os.path.exists(macosx_folder): - shutil.rmtree(macosx_folder) - return [gr.update(visible=False), gr.update(visible=True), gr.update(visible=False)] - - download_button.click(download_model, inputs=model_dropdown, outputs=download_status) - - 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(""" - ### 🟧 Where are the vits models stored? - This extension will download the models to `/alltalk_tts/models/vits/` folder.
- - ### 🟧 Where are the voices stored for vits models? - Vits models themselves are the voices/have the voices built into them. Some models have mutliple voices embedded and some models only the one voice embedded in them.
- - ### 🟧 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.
- """) - gr.Markdown(""" - """) - - 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 vits_at_gradio_settings_page(model_config_data): - app = vits_model_alltalk_settings(model_config_data) - return app -def vits_at_gradio_settings_page(model_config_data): - app = vits_model_alltalk_settings(model_config_data) - return app +import os +import json +import shutil +import zipfile +import requests +import gradio as gr +from tqdm import tqdm +from pathlib import Path +from .help_content import AllTalkHelpContent +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. +# 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"]] + +def vits_voices_file_list(): + models_folder = main_dir / "models" / "vits" + # Create the folder if it doesn't exist + if not models_folder.exists(): + models_folder.mkdir(parents=True, exist_ok=True) + voices = [] + available_models = {} + + # Define possible model files + model_files = ["model_file.pth", "model.pth"] + + for subfolder in models_folder.iterdir(): + if subfolder.is_dir(): + model_name = subfolder.name + + # Check if config.json exists (minimum requirement) + if not (subfolder / "config.json").exists(): + continue + + # Load config to check model type + try: + with open(subfolder / "config.json", "r") as f: + config = json.load(f) + use_speaker_embedding = config["model_args"].get("use_speaker_embedding", False) + num_speakers = config["model_args"].get("num_speakers", 0) + except Exception: + continue + + # Check for model file + has_model_file = any(subfolder.joinpath(model_file).exists() for model_file in model_files) + has_model_file_tar = subfolder.joinpath("model_file.pth.tar").exists() + + if not (has_model_file or has_model_file_tar): + continue + + # Determine model type + if (subfolder / "language_ids.json").exists() and (subfolder / "speaker_ids.json").exists(): + available_models[f"VITS - {model_name}"] = "VITS_multi_language" + elif (subfolder / "speaker_ids.json").exists() or (use_speaker_embedding or num_speakers > 0): + available_models[f"VITS - {model_name}"] = "VITS_multi_speaker" + else: + available_models[f"VITS - {model_name}"] = "VITS_single_speaker" + + # Get voices based on model type + model_type = available_models[f"VITS - {model_name}"] + if model_type in ["VITS_multi_speaker", "VITS_multi_language"]: + speaker_ids_path = subfolder / "speaker_ids.json" + if speaker_ids_path.exists(): + with open(speaker_ids_path, "r") as f: + speaker_ids = json.load(f) + speakers = [speaker for speaker in speaker_ids.keys() if speaker != "ED\n"] + voices.extend(speakers) + elif use_speaker_embedding or num_speakers > 0: + # If no speaker_ids.json but config indicates multiple speakers + voices.extend([f"Speaker{i}" for i in range(num_speakers)]) + elif model_type == "VITS_single_speaker": + voices.append("default") + + # Remove duplicate speaker names + voices = list(set(voices)) + # Sort the voices alphabetically + voices.sort() + # Move the "default" voice to the top of the list + if "default" in voices: + voices.remove("default") + voices.insert(0, "default") + return voices + +def group_vits_models_by_language(): + """Group available VITS models by language code""" + language_groups = {} + + for model in available_models["models"]: + model_name = model["model_name"] + # Extract language code from model name (format: tts_models--{lang}--...) + parts = model_name.split('--') + if len(parts) >= 2: + lang_code = parts[1] + + if lang_code not in language_groups: + language_groups[lang_code] = { + "name": get_language_name(lang_code), + "models": [] + } + + language_groups[lang_code]["models"].append(model) + + return language_groups + +def get_language_name(lang_code): + """Convert VITS language code to full name""" + language_names = { + "en": "English", + "de": "German", + "fr": "French", + "es": "Spanish", + "it": "Italian", + "nl": "Dutch", + "pl": "Polish", + "pt": "Portuguese", + "ru": "Russian", + "cs": "Czech", + "hu": "Hungarian", + "bn": "Bengali", + "ca": "Catalan", + "el": "Greek", + "fi": "Finnish", + "hr": "Croatian", + "lt": "Lithuanian", + "lv": "Latvian", + "mt": "Maltese", + "ro": "Romanian", + "sk": "Slovak", + "sl": "Slovenian", + "sv": "Swedish", + "bg": "Bulgarian", + "da": "Danish", + "et": "Estonian", + "ga": "Irish", + "uk": "Ukrainian", + "ewe": "Ewe", + "hau": "Hausa", + "lin": "Lingala", + "tw": "Twi", + "yor": "Yoruba" + } + return language_names.get(lang_code, f"Unknown ({lang_code})") + +def download_language_pack(lang_code, progress=gr.Progress()): + """Download all models for a specific language""" + language_groups = group_vits_models_by_language() + if lang_code not in language_groups: + return f"Error: Language pack {lang_code} not found" + + models = language_groups[lang_code]["models"] + total_models = len(models) + + progress(0, desc=f"Downloading {get_language_name(lang_code)} voice pack") + + downloaded_files = [] + errors = [] + + for idx, model in enumerate(models, 1): + progress(idx/total_models, desc=f"Downloading model {idx}/{total_models}") + try: + # Create the model folder + vits_folder = os.path.join(main_dir, "models", "vits") + folder_path = os.path.join(vits_folder, model["folder_path"]) + os.makedirs(folder_path, exist_ok=True) + + # Download and extract the model + zip_url = model["github_rls_url"] + zip_file_path = os.path.join(vits_folder, f"{model['model_name']}.zip") + + response = requests.get(zip_url, stream=True) + total_size = int(response.headers.get("content-length", 0)) + + with open(zip_file_path, "wb") as file: + for chunk in response.iter_content(chunk_size=1024): + if chunk: + file.write(chunk) + + # Extract ZIP file + with zipfile.ZipFile(zip_file_path, "r") as zip_ref: + zip_ref.extractall(vits_folder) + + # Cleanup + os.remove(zip_file_path) + macosx_folder = os.path.join(vits_folder, "__MACOSX") + if os.path.exists(macosx_folder): + shutil.rmtree(macosx_folder) + + downloaded_files.append(model["model_name"]) + + except Exception as e: + errors.append(f"Error downloading {model['model_name']}: {str(e)}") + + if errors: + return f"Downloaded {len(downloaded_files)} models with {len(errors)} errors:\n" + "\n".join(errors) + else: + return f"Successfully downloaded {len(downloaded_files)} models for {get_language_name(lang_code)}" + +###################################################### +# 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 vits_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 vits_model_alltalk_settings(model_config_data): + features_list = model_config_data['model_capabilties'] + voice_list = vits_voices_file_list() + with gr.Blocks(title="vits TTS", analytics_enabled=False) 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="Configure default settings and voice mappings for the selected TTS engine. Unavailable options are grayed out based on engine capabilities. See the Help section below for detailed information about each setting.") + with gr.Row(): + submit_button = gr.Button("Update Settings") + output_message = gr.Textbox(label="Output Message", interactive=False, show_label=False) + with gr.Accordion("HELP - 🔊 Understanding TTS Engine Default Settings Page", open=False): + with gr.Row(): + gr.Markdown(AllTalkHelpContent.DEFAULT_SETTINGS, elem_classes="custom-markdown") + with gr.Row(): + gr.Markdown(AllTalkHelpContent.DEFAULT_SETTINGS1, elem_classes="custom-markdown") + gr.Markdown(AllTalkHelpContent.DEFAULT_SETTINGS2, elem_classes="custom-markdown") + submit_button.click(vits_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="VITS", 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(): + with gr.Accordion("HELP - 🔊 Understanding TTS Engine Capabilities", open=False): + with gr.Row(): + gr.Markdown(AllTalkHelpContent.ENGINE_INFORMATION, elem_classes="custom-markdown") + with gr.Row(): + gr.Markdown(AllTalkHelpContent.ENGINE_INFORMATION1, elem_classes="custom-markdown") + gr.Markdown(AllTalkHelpContent.ENGINE_INFORMATION2, elem_classes="custom-markdown") + + ####################################################################################################################################################################################################### + # REQUIRED CHANGE # + # You will need to build a custom method to identify if your models are installed and download them. Store your models list in the available_models.json file, alongside your model_settings.json etc # + ####################################################################################################################################################################################################### + with gr.Tab("Models/Voices Download"): + with gr.Tab("Language Pack Download"): + with gr.Row(): + language_groups = group_vits_models_by_language() + language_choices = [ + f"{code} - {info['name']} ({len(info['models'])} models)" + for code, info in sorted(language_groups.items()) + ] + + lang_pack_dropdown = gr.Dropdown( + choices=language_choices, + label="Select Language Pack", + value=language_choices[0] + ) + lang_pack_download_button = gr.Button("Download Language Pack") + + with gr.Row(): + lang_pack_status = gr.Textbox(label="Language Pack Download Status") + + def download_selected_language_pack(selected_pack): + lang_code = selected_pack.split(" - ")[0] + return download_language_pack(lang_code) + + lang_pack_download_button.click( + download_selected_language_pack, + inputs=lang_pack_dropdown, + outputs=lang_pack_status + ) + + with gr.Tab("Individual Voice Download"): + with gr.Row(): + # 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 ZIP file URL + vits_folder = os.path.join(main_dir, "models", "vits") + folder_path = os.path.join(vits_folder, selected_model["folder_path"]) + zip_url = selected_model["github_rls_url"] + # Check if the model is already downloaded based on the file structure + required_files = ["config.json"] + if os.path.exists(os.path.join(folder_path, "model_file.pth.tar")): + required_files.append("model_file.pth.tar") + else: + required_files.append("model_file.pth") + if os.path.exists(os.path.join(folder_path, "speaker_ids.json")): + required_files.append("speaker_ids.json") + if os.path.exists(os.path.join(folder_path, "language_ids.json")): + required_files.append("language_ids.json") + if all(os.path.exists(os.path.join(folder_path, file)) for file in required_files): + return "The model is already downloaded. No need to download again." + else: + # Download the ZIP file to the "vits" folder + print(f"Downloading {model_name}...") + response = requests.get(zip_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) + zip_file_path = os.path.join(vits_folder, f"{model_name}.zip") + with open(zip_file_path, "wb") as file: + for data in response.iter_content(block_size): + progress_bar.update(len(data)) + file.write(data) + progress_bar.close() + # Extract the ZIP file contents to the desired folder + with zipfile.ZipFile(zip_file_path, "r") as zip_ref: + zip_ref.extractall(vits_folder) + # Remove the ZIP file + os.remove(zip_file_path) + # Delete the __MACOSX folder if it exists + macosx_folder = os.path.join(vits_folder, "__MACOSX") + if os.path.exists(macosx_folder): + shutil.rmtree(macosx_folder) + return "Model downloaded successfully!" + + def show_confirm_cancel(model_name): + selected_model = next(model for model in available_models["models"] if model["model_name"] == model_name) + folder_path = os.path.join(main_dir, "models", "vits", selected_model["folder_path"]) + required_files = ["config.json"] + if os.path.exists(os.path.join(folder_path, "model_file.pth.tar")): + required_files.append("model_file.pth.tar") + else: + required_files.append("model_file.pth") + if os.path.exists(os.path.join(folder_path, "speaker_ids.json")): + required_files.append("speaker_ids.json") + if os.path.exists(os.path.join(folder_path, "language_ids.json")): + required_files.append("language_ids.json") + if all(os.path.exists(os.path.join(folder_path, file)) for file in required_files): + 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): + # 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 ZIP file URL + vits_folder = os.path.join(main_dir, "models", "vits") + folder_path = os.path.join(vits_folder, selected_model["folder_path"]) + zip_url = selected_model["github_rls_url"] + # Download the ZIP file to the "vits" folder + print(f"Downloading {model_name}...") + response = requests.get(zip_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) + zip_file_path = os.path.join(vits_folder, f"{model_name}.zip") + with open(zip_file_path, "wb") as file: + for data in response.iter_content(block_size): + progress_bar.update(len(data)) + file.write(data) + progress_bar.close() + # Extract the ZIP file contents to the desired folder + with zipfile.ZipFile(zip_file_path, "r") as zip_ref: + zip_ref.extractall(vits_folder) + # Remove the ZIP file + os.remove(zip_file_path) + # Delete the __MACOSX folder if it exists + macosx_folder = os.path.join(vits_folder, "__MACOSX") + if os.path.exists(macosx_folder): + shutil.rmtree(macosx_folder) + return [gr.update(visible=False), gr.update(visible=True), gr.update(visible=False)] + + download_button.click(download_model, inputs=model_dropdown, outputs=download_status) + + 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(AllTalkHelpContent.HELP_PAGE, elem_classes="custom-markdown") + with gr.Row(): + gr.Markdown(AllTalkHelpContent.HELP_PAGE1, elem_classes="custom-markdown") + gr.Markdown(AllTalkHelpContent.HELP_PAGE2, elem_classes="custom-markdown") + + 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 vits_at_gradio_settings_page(model_config_data): + app = vits_model_alltalk_settings(model_config_data) + return app +def vits_at_gradio_settings_page(model_config_data): + app = vits_model_alltalk_settings(model_config_data) + return app diff --git a/system/tts_engines/xtts/available_models.json b/system/tts_engines/xtts/available_models.json index 8f33b47..12569ee 100644 --- a/system/tts_engines/xtts/available_models.json +++ b/system/tts_engines/xtts/available_models.json @@ -1,61 +1,61 @@ -{ - "first_start_model": "xttsv2_2.0.3", - "models": [ - { - "model_name": "xttsv2_2.0.3", - "folder_path": "xttsv2_2.0.3", - "files_to_download": { - "LICENSE.txt": "https://huggingface.co/coqui/XTTS-v2/resolve/v2.0.3/LICENSE.txt?download=true", - "README.md": "https://huggingface.co/coqui/XTTS-v2/resolve/v2.0.3/README.md?download=true", - "config.json": "https://huggingface.co/coqui/XTTS-v2/resolve/v2.0.3/config.json?download=true", - "model.pth": "https://huggingface.co/coqui/XTTS-v2/resolve/v2.0.3/model.pth?download=true", - "dvae.pth": "https://huggingface.co/coqui/XTTS-v2/resolve/v2.0.3/dvae.pth?download=true", - "mel_stats.pth": "https://huggingface.co/coqui/XTTS-v2/resolve/v2.0.3/mel_stats.pth?download=true", - "speakers_xtts.pth": "https://huggingface.co/coqui/XTTS-v2/resolve/v2.0.3/speakers_xtts.pth?download=true", - "vocab.json": "https://huggingface.co/coqui/XTTS-v2/resolve/v2.0.3/vocab.json?download=true" - } - }, - { - "model_name": "xttsv2_2.0.2", - "folder_path": "xttsv2_2.0.2", - "files_to_download": { - "LICENSE.txt": "https://huggingface.co/coqui/XTTS-v2/resolve/v2.0.2/LICENSE.txt?download=true", - "README.md": "https://huggingface.co/coqui/XTTS-v2/resolve/v2.0.2/README.md?download=true", - "config.json": "https://huggingface.co/coqui/XTTS-v2/resolve/v2.0.2/config.json?download=true", - "model.pth": "https://huggingface.co/coqui/XTTS-v2/resolve/v2.0.2/model.pth?download=true", - "dvae.pth": "https://huggingface.co/coqui/XTTS-v2/resolve/v2.0.2/dvae.pth?download=true", - "mel_stats.pth": "https://huggingface.co/coqui/XTTS-v2/resolve/v2.0.2/mel_stats.pth?download=true", - "speakers_xtts.pth": "https://huggingface.co/coqui/XTTS-v2/resolve/v2.0.2/speakers_xtts.pth?download=true", - "vocab.json": "https://huggingface.co/coqui/XTTS-v2/resolve/v2.0.2/vocab.json?download=true" - } - }, - { - "model_name": "xttsv2_2.0.1", - "folder_path": "xttsv2_2.0.1", - "files_to_download": { - "LICENSE.txt": "https://huggingface.co/coqui/XTTS-v2/resolve/v2.0.1/LICENSE.txt?download=true", - "README.md": "https://huggingface.co/coqui/XTTS-v2/resolve/v2.0.1/README.md?download=true", - "config.json": "https://huggingface.co/coqui/XTTS-v2/resolve/v2.0.1/config.json?download=true", - "model.pth": "https://huggingface.co/coqui/XTTS-v2/resolve/v2.0.1/model.pth?download=true", - "dvae.pth": "https://huggingface.co/coqui/XTTS-v2/resolve/v2.0.1/dvae.pth?download=true", - "mel_stats.pth": "https://huggingface.co/coqui/XTTS-v2/resolve/v2.0.1/mel_stats.pth?download=true", - "speakers_xtts.pth": "https://huggingface.co/coqui/XTTS-v2/resolve/v2.0.1/speakers_xtts.pth?download=true", - "vocab.json": "https://huggingface.co/coqui/XTTS-v2/resolve/v2.0.1/vocab.json?download=true" - } - }, - { - "model_name": "xttsv2_2.0.0", - "folder_path": "xttsv2_2.0.0", - "files_to_download": { - "LICENSE.txt": "https://huggingface.co/coqui/XTTS-v2/resolve/v2.0.0/LICENSE.txt?download=true", - "README.md": "https://huggingface.co/coqui/XTTS-v2/resolve/v2.0.0/README.md?download=true", - "config.json": "https://huggingface.co/coqui/XTTS-v2/resolve/v2.0.0/config.json?download=true", - "model.pth": "https://huggingface.co/coqui/XTTS-v2/resolve/v2.0.0/model.pth?download=true", - "dvae.pth": "https://huggingface.co/coqui/XTTS-v2/resolve/v2.0.0/dvae.pth?download=true", - "mel_stats.pth": "https://huggingface.co/coqui/XTTS-v2/resolve/v2.0.0/mel_stats.pth?download=true", - "speakers_xtts.pth": "https://huggingface.co/coqui/XTTS-v2/resolve/v2.0.0/speakers_xtts.pth?download=true", - "vocab.json": "https://huggingface.co/coqui/XTTS-v2/resolve/v2.0.0/vocab.json?download=true" - } - } - ] +{ + "first_start_model": "xttsv2_2.0.3", + "models": [ + { + "model_name": "xttsv2_2.0.3", + "folder_path": "xttsv2_2.0.3", + "files_to_download": { + "LICENSE.txt": "https://huggingface.co/coqui/XTTS-v2/resolve/v2.0.3/LICENSE.txt?download=true", + "README.md": "https://huggingface.co/coqui/XTTS-v2/resolve/v2.0.3/README.md?download=true", + "config.json": "https://huggingface.co/coqui/XTTS-v2/resolve/v2.0.3/config.json?download=true", + "model.pth": "https://huggingface.co/coqui/XTTS-v2/resolve/v2.0.3/model.pth?download=true", + "dvae.pth": "https://huggingface.co/coqui/XTTS-v2/resolve/v2.0.3/dvae.pth?download=true", + "mel_stats.pth": "https://huggingface.co/coqui/XTTS-v2/resolve/v2.0.3/mel_stats.pth?download=true", + "speakers_xtts.pth": "https://huggingface.co/coqui/XTTS-v2/resolve/v2.0.3/speakers_xtts.pth?download=true", + "vocab.json": "https://huggingface.co/coqui/XTTS-v2/resolve/v2.0.3/vocab.json?download=true" + } + }, + { + "model_name": "xttsv2_2.0.2", + "folder_path": "xttsv2_2.0.2", + "files_to_download": { + "LICENSE.txt": "https://huggingface.co/coqui/XTTS-v2/resolve/v2.0.2/LICENSE.txt?download=true", + "README.md": "https://huggingface.co/coqui/XTTS-v2/resolve/v2.0.2/README.md?download=true", + "config.json": "https://huggingface.co/coqui/XTTS-v2/resolve/v2.0.2/config.json?download=true", + "model.pth": "https://huggingface.co/coqui/XTTS-v2/resolve/v2.0.2/model.pth?download=true", + "dvae.pth": "https://huggingface.co/coqui/XTTS-v2/resolve/v2.0.2/dvae.pth?download=true", + "mel_stats.pth": "https://huggingface.co/coqui/XTTS-v2/resolve/v2.0.2/mel_stats.pth?download=true", + "speakers_xtts.pth": "https://huggingface.co/coqui/XTTS-v2/resolve/v2.0.2/speakers_xtts.pth?download=true", + "vocab.json": "https://huggingface.co/coqui/XTTS-v2/resolve/v2.0.2/vocab.json?download=true" + } + }, + { + "model_name": "xttsv2_2.0.1", + "folder_path": "xttsv2_2.0.1", + "files_to_download": { + "LICENSE.txt": "https://huggingface.co/coqui/XTTS-v2/resolve/v2.0.1/LICENSE.txt?download=true", + "README.md": "https://huggingface.co/coqui/XTTS-v2/resolve/v2.0.1/README.md?download=true", + "config.json": "https://huggingface.co/coqui/XTTS-v2/resolve/v2.0.1/config.json?download=true", + "model.pth": "https://huggingface.co/coqui/XTTS-v2/resolve/v2.0.1/model.pth?download=true", + "dvae.pth": "https://huggingface.co/coqui/XTTS-v2/resolve/v2.0.1/dvae.pth?download=true", + "mel_stats.pth": "https://huggingface.co/coqui/XTTS-v2/resolve/v2.0.1/mel_stats.pth?download=true", + "speakers_xtts.pth": "https://huggingface.co/coqui/XTTS-v2/resolve/v2.0.1/speakers_xtts.pth?download=true", + "vocab.json": "https://huggingface.co/coqui/XTTS-v2/resolve/v2.0.1/vocab.json?download=true" + } + }, + { + "model_name": "xttsv2_2.0.0", + "folder_path": "xttsv2_2.0.0", + "files_to_download": { + "LICENSE.txt": "https://huggingface.co/coqui/XTTS-v2/resolve/v2.0.0/LICENSE.txt?download=true", + "README.md": "https://huggingface.co/coqui/XTTS-v2/resolve/v2.0.0/README.md?download=true", + "config.json": "https://huggingface.co/coqui/XTTS-v2/resolve/v2.0.0/config.json?download=true", + "model.pth": "https://huggingface.co/coqui/XTTS-v2/resolve/v2.0.0/model.pth?download=true", + "dvae.pth": "https://huggingface.co/coqui/XTTS-v2/resolve/v2.0.0/dvae.pth?download=true", + "mel_stats.pth": "https://huggingface.co/coqui/XTTS-v2/resolve/v2.0.0/mel_stats.pth?download=true", + "speakers_xtts.pth": "https://huggingface.co/coqui/XTTS-v2/resolve/v2.0.0/speakers_xtts.pth?download=true", + "vocab.json": "https://huggingface.co/coqui/XTTS-v2/resolve/v2.0.0/vocab.json?download=true" + } + } + ] } \ No newline at end of file diff --git a/system/tts_engines/xtts/help_content.py b/system/tts_engines/xtts/help_content.py new file mode 100644 index 0000000..277f974 --- /dev/null +++ b/system/tts_engines/xtts/help_content.py @@ -0,0 +1,458 @@ +# help_content.py +# pylint: disable=no-member + +class AllTalkHelpContent: + """CSS and help content for help_content.py""" + custom_css = """ + /* Add this to your existing CSS */ + .gradio-container .prose { + max-width: none !important; + padding: 0.5rem !important; /* Reduced padding */ + margin: 0 !important; + } + + .custom-markdown div { + border: none !important; /* Remove the inner border */ + margin-top: 0 !important; /* Remove top margin */ + margin-bottom: 0 !important; /* Remove bottom margin */ + padding-top: 0 !important; /* Remove top padding */ + padding-bottom: 0 !important; /* Remove bottom padding */ + } + + /* Update the existing custom-markdown class */ + .custom-markdown { + font-size: 15px !important; + line-height: 1.6 !important; + color: var(--body-text-color) !important; + background-color: var(--background-fill-primary) !important; + border: 1px solid var(--border-color-primary) !important; + border-radius: 8px !important; + padding: 0.5rem !important; + margin: 0 !important; /* Changed from 1rem 0 to 0 */ + max-width: 100% !important; + box-sizing: border-box !important; + /* Default text color for all content (grey) */ + color: rgba(156, 163, 175, 1) !important; /* Adjust this grey value to match your interface */ + } + + /* Make headings white */ + .custom-markdown h1, + .custom-markdown h2, + .custom-markdown h3, + .custom-markdown h4, + .custom-markdown h5, + .custom-markdown h6 { + color: var(--heading-text-color, var(--body-text-color)) !important; + font-weight: 600 !important; + } + + /* Keep all other elements in the grey color */ + .custom-markdown p, + .custom-markdown li, + .custom-markdown ul, + .custom-markdown ol { + color: rgba(156, 163, 175, 1) !important; /* Same grey as the base text */ + } + + /* Additional targeting for any wrapping elements */ + .gradio-container .prose > * { + margin: 0 !important; + padding: 0 !important; + } + + /* Update the list item spacing in your existing CSS */ + .custom-markdown li { + font-size: 0.95rem !important; + margin-bottom: 0.2rem !important; /* Reduced from 0.5rem to 0.3rem */ + } + + /* Add specific styling for definition-style lists (like your Default, Recommendation, Tip items) */ + .custom-markdown li p { + margin: 0 !important; /* Remove paragraph margins within list items */ + } + + /* If you need even tighter spacing for specific types of lists */ + .custom-markdown li + li { + margin-top: 0.2rem !important; /* Space between consecutive list items */ + } + + /* Ensure nested lists maintain proper spacing */ + .custom-markdown li > ul, + .custom-markdown li > ol { + margin: 0.2rem 0 0.2rem 1rem !important; /* Reduced from 0.5rem */ + } + + /* Consistent heading styles */ + .custom-markdown h2 { + font-size: 1.2rem !important; /* Fixed size relative to root */ + font-weight: 600 !important; + margin: 1.5rem 0 1rem !important; + color: var(--heading-text-color, var(--body-text-color)) !important; + border-bottom: 1px solid var(--border-color-primary) !important; + padding-bottom: 0.5rem !important; + } + + /* First paragraph styling */ + .custom-markdown p:first-of-type { + font-size: 0.95rem !important; /* Match base size */ + font-weight: 400 !important; + margin-bottom: 1rem !important; + } + + /* Regular paragraphs */ + .custom-markdown p { + font-size: 0.95rem !important; + margin: 0.8rem 0 !important; + } + + /* List styling */ + .custom-markdown ul, + .custom-markdown ol { + margin: 0.8rem 0 !important; + padding-left: 1.5rem !important; + } + + /* Code styling */ + .custom-markdown code { + font-family: ui-monospace, monospace !important; + background-color: var(--background-fill-secondary) !important; + padding: 0.2rem 0.4rem !important; + border-radius: 4px !important; + font-size: 0.8rem !important; /* Slightly smaller than regular text */ + color: var(--body-text-color) !important; + } + + /* Add styles for bold and emphasis */ + .custom-markdown strong { + font-weight: 600 !important; + color: var(--body-text-color) !important; + } + + .custom-markdown em { + font-style: italic !important; + } + + /* Ensure both columns take equal width */ + .gradio-row > div { + flex: 1 1 50% !important; + min-width: 0 !important; /* Prevents flex items from overflowing */ + } + """ + + ENGINE_INFORMATION = """ + ## 🔊 TTS Engine Capabilities Help + + This guide explains the various capabilities that **may** be available in different TTS engines and models. Each capability affects how the TTS engine processes and generates speech output. + """ + + ENGINE_INFORMATION1 = """ + ## 🚀 Performance Features + + - **DeepSpeed Capable**: Enables GPU acceleration using the DeepSpeed optimization library. + - **Requires**: NVIDIA GPU with CUDA support + - **Benefit**: Significantly faster text-to-speech generation + - **Note**: Model must specifically support DeepSpeed inference + + - **Low VRAM Capable**: Optimized for systems with limited GPU memory. + - **Benefit**: Efficient memory management between CPU and GPU + - **Use Case**: Ideal when running alongside other GPU-intensive applications like LLMs + - **Note**: May trade speed for memory efficiency + + - **Streaming Capable**: Enables real-time speech generation. + - **Benefit**: Immediate playback without generating entire audio first + - **Use Case**: Interactive applications and real-time responses + - **Note**: Not all output formats support streaming + + ## 🎵 Voice Control Features + + - **Pitch Capable**: Allows adjustment of voice pitch. + - **Benefit**: Creates more expressive and varied speech output + - **Use Case**: Customizing voice characteristics + - **Note**: Adjustment range varies by model + + - **Generation Speed Capable**: Controls speech rate. + - **Benefit**: Adjustable playback speed during generation + - **Use Case**: Creating faster or slower speech output + - **Note**: May affect audio quality at extreme settings + + - **Temperature Capable**: Controls output randomness. + - **Benefit**: Adjustable speech variation and creativity + - **Use Case**: Balancing consistency vs. naturality + - **Note**: Higher values increase variation but may reduce quality + """ + + ENGINE_INFORMATION2 = """ + ## 🎯 Quality Enhancement Features + + - **Repetition Penalty Capable**: Prevents unnatural speech patterns. + - **Benefit**: Reduces repeated sounds and phrases + - **Use Case**: Improving natural flow of longer texts + - **Note**: Strength of penalty may be adjustable + + ## 🌐 Multi-Feature Support + + - **Multi-Languages Capable Models**: Each model Supports multiple languages. + - **Benefit**: Generate speech in different languages + - **Note**: Quality may vary between languages + - **Tip**: Check model-specific language support + + - **Multi-Voice Capable**: Supports multiple speaking voices. + - **Benefit**: Different voices or speaking styles + - **Types**: Pre-trained voices or voice cloning + - **Note**: Voice quality may vary by model + + - **Multi-Model Capable Engine**: Supports multiple TTS models. + - **Benefit**: Flexibility in model selection + - **Use Case**: Switching between models for different needs + - **Note**: Each model may have different capabilities + + ## 📁 Technical Features + + - **Default Audio Output Format**: Specifies output file format. + - **Common Formats**: WAV, MP3, FLAC, Opus, AAC, PCM + - **Note**: Transcoding to different formats adds processing time + - **Warning**: Not all formats support streaming + + - **Platform Support**: Operating system compatibility. + - **Platforms**: Windows, Linux, macOS + - **Note**: Additional setup may be required + - **Warning**: Support level may vary by platform + """ + + DEFAULT_SETTINGS = """ + ## 🎯 TTS Engine Settings Help + + This guide explains the settings and configuration options available for individual Text-to-Speech engines within AllTalk. + """ + + DEFAULT_SETTINGS1 = """ + ## 🎚️ Engine Capabilities & Controls + + - **Low VRAM Mode** + - Optimizes memory usage for systems with limited GPU memory + - Efficiently manages resources by moving data between CPU and GPU + - Recommended for systems with less than 4GB VRAM or when running alongside other GPU-intensive applications + + - **DeepSpeed Capability** + - Accelerates TTS generation using optimized inference + - Only available for engines and models that support DeepSpeed + - Requires NVIDIA GPU with CUDA support + + - **Temperature Control** + - Adjusts the variability in speech generation + - **Range**: 0.0 to 1.0 + - **Lower values** (0.1-0.5): More consistent, stable output + - **Higher values** (0.6-1.0): More variable, potentially more natural-sounding output + + - **Repetition Penalty** + - Helps prevent repetitive speech patterns + - **Range**: 1.0 to 15.0 + - Higher values more strongly discourage repetition + - Typically most effective between 1.0-3.0 + + - **Pitch Adjustment** + - Modifies the voice pitch when supported + - **Range**: -20 to +20 + - Use subtle adjustments for most natural results + + - **Generation Speed** + - Controls the pace of generated speech + - **Range**: 0.5 to 2.0 + - 1.0 represents normal speed + """ + + DEFAULT_SETTINGS2 = """ + + ## 🗣️ Voice Configuration + + ### OpenAI Voice Mappings + - Only relevant when using the OpenAI-compatible API endpoint + - Maps OpenAI's six standard voices to equivalent voices in the current engine: + - `alloy` + - `echo` + - `fable` + - `nova` + - `onyx` + - `shimmer` + - Essential for maintaining compatibility with OpenAI API calls + - Each mapping can be customized to any available voice in the current engine + + Information on the OpenAI Endpoint is available in the [Github Wiki](https://github.com/erew123/alltalk_tts/wiki) + + ### Default Voices + - **Default/Character Voice**: Used when no specific voice is specified in API calls + - **Narrator Voice**: Separate default for narrator-specific text + - These defaults are engine-specific and won't affect other TTS engines + - Can be overridden by explicitly specifying voices in API calls + + ## ⚙️ Important Notes + + - Settings availability is determined by engine capabilities + - Grayed-out options indicate features not supported by the current engine + - Changes only affect the currently selected TTS engine + - Settings here act as defaults but can be overridden via API parameters + - All changes require clicking "Update Settings" to take effect + - Some settings require an engine reload to take effect + """ + + HELP_PAGE = """ + ## 🎯 XTTS Voice Cloning Engine Help + + XTTS is a powerful multi-speaker, voice cloning model that can generate speech in multiple languages using short audio samples for voice cloning. + """ + + HELP_PAGE1 = """ + ## 🎚️ Engine Capabilities & Controls + + - **DeepSpeed Support** + - Accelerates generation using optimized inference + - Requires NVIDIA GPU with CUDA support + - 2-3x speed improvement in generation + - Recommended when available + + - **Multi-Language Support** + - Clone voices across multiple languages + - No need for language-specific training + - Full Hindi support in v2.0.3 (API Local method only) + - Natural-sounding output in target language + + - **Generation Methods** + - API Local: Standard generation method + - XTTSv2 Local: Enhanced method with DeepSpeed support + - Each method may produce slightly different results + - Try both methods if voice quality isn't optimal + + ## 🎤 Voice Sample Creation Guide + + ### Audio Requirements + - Format: WAV or MP3 + - Sample Rate: 22050Hz + - Channels: Mono + - Bit Depth: 16-bit + - Duration: 6-30 seconds (optimal: 8-10 seconds) + + ### Recording Tips + 1. **Audio Quality** + - Choose clear, clean recordings + - Avoid background noise, music, or echoes + - Watch for audio artifacts like hiss or hum + - Ensure consistent volume levels + + 2. **Speech Content** + - Use flowing, natural speech + - Avoid long pauses or gaps + - Include some emotional range + - Avoid breathing sounds at start/end + - Choose samples with good vocal variety + + ### Using Audacity + 1. **Basic Editing** + - Open your audio file in Audacity + - Select the best 6-30 second segment + - Remove unwanted sounds or silence + + 2. **Processing Steps** + ``` + 1. Clean up audio if needed (noise reduction, normalization) + 2. Tracks > Resample to 22050Hz + 3. Tracks > Mix > Stereo to Mono + 4. File > Export Audio as WAV + ``` + + ## 🗣️ Voice Management + + ### Single Voice Setup + - Place WAV/MP3 files in `/alltalk_tts/voices/` + - Use descriptive filenames (e.g., `broadcaster_male.wav`) + - Files appear as individual voices in interface + - Best for quick testing or simple use cases + + ### Multi-Voice Sets + - Create folders in `/alltalk_tts/voices/xtts_multi_voice_sets/` + - Add multiple samples per voice + - System randomly selects up to 5 samples + - Better for consistent voice reproduction + - Example: `/voices/xtts_multi_voice_sets/broadcaster_voice/` + + ### Pre-computed Latents + - Stored in `/voices/xtts_latents/` + - Faster generation times + - Reduced memory usage + - Recommended for frequently used voices + """ + + HELP_PAGE2 = """ + ## 📝 Model Versions + + ### v2.0.3 (Recommended) + - Latest stable release + - Full language support including Hindi + - Best overall performance + - Complete feature set + + ### v2.0.2 (Alternative) + - Stable fallback option + - Good performance + - Use if v2.0.3 has issues + + ### Required Files + - `config.json`: Model configuration + - `model.pth`: Core model weights + - `mel_stats.pth`: Audio statistics + - `speakers_xtts.pth`: Speaker embeddings + - `vocab.json`: Tokenizer vocabulary + - `dvae.pth`: Discrete VAE weights + + ## 🔍 Troubleshooting Voice Quality + + If voice cloning results aren't satisfactory: + + 1. **Audio Quality Issues** + - Verify sample meets technical requirements + - Check for background noise + - Ensure proper resampling + - Try processing audio to improve clarity + + 2. **Generation Options** + - Try both API Local and XTTSv2 Local methods + - Adjust temperature settings + - Use multiple voice samples + - Consider creating latents + + 3. **Advanced Solutions** + - Try model finetuning (see Github Wiki) + - Use RVC pipeline for additional processing + - Test different voice samples + - Remember: 100% match isn't possible + + ## 📁 File Management + + - **Models**: `/alltalk_tts/models/xtts/` + - **Voice Samples**: `/alltalk_tts/voices/` + - **Multi-Voice Sets**: `/alltalk_tts/voices/xtts_multi_voice_sets/` + - **Latents**: `/alltalk_tts/voices/xtts_latents/` + - **Outputs**: `/alltalk_tts/outputs/` + - Automatic cleanup available + - Configurable retention period + - Set to 'Disabled' to keep all files + + ## 💡 Best Practices + + 1. **Sample Selection** + - Use high-quality recordings + - Choose emotionally varied speech + - Avoid background noise + - Test multiple samples + + 2. **Performance** + - Enable DeepSpeed when available + - Use pre-computed latents + - Consider multi-voice sets + - Monitor system resources + + 3. **Workflow** + - Process audio before use + - Test both generation methods + - Keep original samples organized + - Regular cleanup of output files + """ \ No newline at end of file diff --git a/system/tts_engines/xtts/model_engine.py b/system/tts_engines/xtts/model_engine.py index 111cb61..0673e92 100644 --- a/system/tts_engines/xtts/model_engine.py +++ b/system/tts_engines/xtts/model_engine.py @@ -1,611 +1,1199 @@ -############################################### -# DONT CHANGE # These are base imports needed # -############################################### -import glob -import os -import sys -import json -import time -import torch -import logging -from pathlib import Path -from fastapi import (HTTPException) -logging.disable(logging.WARNING) -############################################### -# DONT CHANGE # Get Pytorch & Python versions # -############################################### -pytorch_version = torch.__version__ -cuda_version = torch.version.cuda -major, minor, micro = sys.version_info[:3] -python_version = f"{major}.{minor}.{micro}" -try: - import deepspeed - deepspeed_available = True -except ImportError: - deepspeed_available = False - pass - -############################################################################################################# -############################################################################################################# -# CHANGE ME # Run any specifc imports, requirements or setup any global vaiables needed for this TTS Engine # -############################################################################################################# -############################################################################################################# -# In this section you will import any imports that your specific TTS Engine will use. You will provide any -# start-up errors for those bits, as if you were starting up a normal Python script. Note the logging.disable -# a few lines up from here, you may want to # that out while debugging! -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 - from TTS.api import TTS - from TTS.utils.synthesizer import Synthesizer -except ModuleNotFoundError: - print( - f"[Startup] \033[91mWarning\033[0m Could not find the TTS module. Make sure to install the requirements for the alltalk_tts extension.", - f"[Startup] \033[91mWarning\033[0m Linux / Mac:\npip install -r extensions/alltalk_tts/requirements.txt\n", - f"[Startup] \033[91mWarning\033[0m Windows:\npip install -r extensions\\alltalk_tts\\requirements.txt\n", - f"[Startup] \033[91mWarning\033[0m If you used the one-click installer, paste the command above in the terminal window launched after running the cmd_ script. On Windows, that's cmd_windows.bat." - ) - raise - -############################################################# -# DONT CHANGE # Do not change the Class name from tts_class # -############################################################# -class tts_class: - def __init__(self): - ######################################################################## - # DONT CHANGE # Sets up the base variables required for any tts engine # - ######################################################################## - self.branding = None - self.this_dir = Path(__file__).parent.resolve() # Sets up self.this_dir as a variable for the folder THIS script is running in. - self.main_dir = Path(__file__).parent.parent.parent.parent.resolve() # Sets up self.main_dir as a variable for the folder AllTalk is running in - self.device = "cuda" if torch.cuda.is_available() else "cpu" # Sets up self.device to cuda if torch exists with Nvidia/CUDA, otherwise sets to cpu - self.cuda_is_available = torch.cuda.is_available() # Sets up cuda_is_available as a True/False to track if Nvidia/CUDA was found on the system - self.tts_generating_lock = False # Used to lock and unlock the tts generation process at the start/end of tts generation. - self.tts_stop_generation = False # Used in conjunction with tts_generating_lock to call for a stop to the current generation. If called (set True) it needs to be set back to False when generation has been stopped. - self.tts_narrator_generatingtts = False # Used to track if the current tts processes is narrator based. This can be used in conjunction with lowvram and device to avoid moving model between GPU(CUDA)<>RAM(CPU) each chunk of narrated text generated. - self.model = None # If loading a model into CUDA/VRAM/RAM "model" is used as the variable name to load and interact with (see the XTTS model_engine script for examples.) - self.is_tts_model_loaded = False # Used to track if a model is actually loaded in and error/fail things like TTS generation if its False - self.current_model_loaded = None # Stores the name of the currenly loaded in model - self.available_models = None # List of available models found by "def scan_models_folder" - self.setup_has_run = False # Tracks if async def setup(self) has run, by setting to True, so that the /api/ready endpoint can provide a "Ready" status - ############################################################################################## - # DONT CHANGE # Load in a list of the available TTS engines and the currently set TTS engine # - ############################################################################################## - tts_engines_file = os.path.join(self.main_dir, "system", "tts_engines", "tts_engines.json") - with open(tts_engines_file, "r") as f: - tts_engines_data = json.load(f) - self.engines_available = [engine["name"] for engine in tts_engines_data["engines_available"]] # A list of ALL the TTS engines available to be loaded by AllTalk - self.engine_loaded = tts_engines_data["engine_loaded"] # In "tts_engines.json" what is the currently set TTS engine loading into AllTalk - self.selected_model = tts_engines_data["selected_model"] # In "tts_engines.json" what is the currently set TTS model loading into AllTalk - ############################################################################ - # DONT CHANGE # Pull out all the settings for the currently set TTS engine # - ############################################################################ - with open(os.path.join(self.this_dir, "model_settings.json"), "r") as f: - tts_model_loaded = json.load(f) - # Access the model details - self.manufacturer_name = tts_model_loaded["model_details"]["manufacturer_name"] # The company/person/body that generated the TTS engine/models etc - self.manufacturer_website = tts_model_loaded["model_details"]["manufacturer_website"] # The website of the company/person/body where people can find more information - # Access the features the model is capable of: - self.audio_format = tts_model_loaded["model_capabilties"]["audio_format"] # This details the audio format your TTS engine is set to generate TTS in e.g. wav, mp3, flac, opus, acc, pcm. Please use only 1x format. - self.deepspeed_capable = tts_model_loaded["model_capabilties"]["deepspeed_capable"] # Is your model capable of DeepSpeed - self.deepspeed_available = 'deepspeed' in globals() # When we did the import earlier, at the top of this script, was DeepSpeed available for use - self.generationspeed_capable = tts_model_loaded["model_capabilties"]["generationspeed_capable"] # Does this TTS engine support changing the speed of the generated TTS - self.languages_capable = tts_model_loaded["model_capabilties"]["languages_capable"] # Are the actual models themselves capable of generating in multiple languages OR is each model language specific - self.lowvram_capable = tts_model_loaded["model_capabilties"]["lowvram_capable"] # Is this engine capable of using low VRAM (moving the model between CPU And GPU memory) - self.multimodel_capable = tts_model_loaded["model_capabilties"]["multimodel_capable"] # Is there just the one model or are there multiple models this engine supports. - self.repetitionpenalty_capable = tts_model_loaded["model_capabilties"]["repetitionpenalty_capable"] # Is this TTS engine capable of changing the repititon penalty - self.streaming_capable = tts_model_loaded["model_capabilties"]["streaming_capable"] # Is this TTS engine capabale of generating streaming audio - self.temperature_capable = tts_model_loaded["model_capabilties"]["temperature_capable"] # Is this TTS engine capable of changing the temperature of the models - self.multivoice_capable = tts_model_loaded["model_capabilties"]["multivoice_capable"] # Are the models multi-voice or single vocice models - self.pitch_capable = tts_model_loaded["model_capabilties"]["pitch_capable"] # Is this TTS engine capable of changing the pitch of the genrated TTS - # Access the current enginesettings - self.def_character_voice = tts_model_loaded["settings"]["def_character_voice"] # What is the current default main/character voice that will be used if no voice specified. - self.def_narrator_voice = tts_model_loaded["settings"]["def_narrator_voice"] # What is the current default narrator voice that will be used if no voice specified. - self.deepspeed_enabled = tts_model_loaded["settings"]["deepspeed_enabled"] # If its available, is DeepSpeed enabled for the TTS engine - self.engine_installed = tts_model_loaded["settings"]["engine_installed"] # Has the TTS engine been setup/installed (not curently used) - self.generationspeed_set = tts_model_loaded["settings"]["generationspeed_set"] # What is the set/stored speed for generation. - self.lowvram_enabled = tts_model_loaded["settings"]["lowvram_enabled"] # If its available, is LowVRAM enabled for the TTS engine - # Check if someone has enabled lowvram on a system that's not CUDA enabled - self.lowvram_enabled = False if not torch.cuda.is_available() else self.lowvram_enabled # If LowVRAM is mistakenly set and CUDA is not available, this will force it back off - self.repetitionpenalty_set = tts_model_loaded["settings"]["repetitionpenalty_set"] # What is the currenly set repitition policy of the model (If it support repetition) - self.temperature_set = tts_model_loaded["settings"]["temperature_set"] # What is the currenly set temperature of the model (If it support temp) - self.pitch_set = tts_model_loaded["settings"]["pitch_set"] # What is the currenly set pitch of the model (If it support temp) - # Gather the OpenAI API Voice Mappings - self.openai_alloy = tts_model_loaded["openai_voices"]["alloy"] # The TTS engine voice that will be mapped to Open AI Alloy voice - self.openai_echo = tts_model_loaded["openai_voices"]["echo"] # The TTS engine voice that will be mapped to Open AI Echo voice - self.openai_fable = tts_model_loaded["openai_voices"]["fable"] # The TTS engine voice that will be mapped to Open AI Fable voice - self.openai_nova = tts_model_loaded["openai_voices"]["nova"] # The TTS engine voice that will be mapped to Open AI Nova voice - self.openai_onyx = tts_model_loaded["openai_voices"]["onyx"] # The TTS engine voice that will be mapped to Open AI Onyx voice - self.openai_shimmer = tts_model_loaded["openai_voices"]["shimmer"] # The TTS engine voice that will be mapped to Open AI Shimmer voice - ################################################################### - # DONT CHANGE # Load params and api_defaults from confignew.json # - ################################################################### - # Define the path to the confignew.json file - configfile_path = self.main_dir / "confignew.json" - # Load config file and get settings - with open(configfile_path, "r") as configfile: - configfile_data = json.load(configfile) - self.branding = configfile_data.get("branding", "") # Sets up self.branding for outputting the name stored in the "confgnew.json" file, as used in print statements. - self.params = configfile_data # Loads in the curent "confgnew.json" file to self.params. - self.debug_tts = configfile_data.get("debugging").get("debug_tts") # Can be used within this script as a True/False flag for generally debugging the TTS generation process. - self.debug_tts_variables = configfile_data.get("debugging").get("debug_tts_variables") # Can be used within this script as a True/False flag for generally debugging variables (if you wish). - ################################################################ - # DONT CHANGE # Print out Python, CUDA, DeepSpeed versions #### - ################################################################ - def printout_versions(self): - if deepspeed_available: - print(f"[{self.branding}ENG] \033[92mDeepSpeed version :\033[93m",deepspeed.__version__,"\033[0m") - else: - print(f"[{self.branding}ENG] \033[92mDeepSpeed version :\033[93m Not available\033[0m") - print(f"[{self.branding}ENG] \033[92mPython Version :\033[93m {python_version}\033[0m") - print(f"[{self.branding}ENG] \033[92mPyTorch Version :\033[93m {pytorch_version}\033[0m") - if cuda_version is None: - print(f"[{self.branding}ENG] \033[92mCUDA Version :\033[91m Not available\033[0m") - else: - print(f"[{self.branding}ENG] \033[92mCUDA Version :\033[93m {cuda_version}\033[0m") - print(f"[{self.branding}ENG]") - return - - ################################################################################### - ################################################################################### - # CHANGE ME # Inital setup of the model and engine. Called when the script starts # - ################################################################################### - ################################################################################### - # In here you will add code to load in your model and do its inital setup. So you - # may be calling your model loader via handle_tts_method_change or if your TTS - # engine doesnt actually load a model into CUDA or System RAM, you may be doing - # Something to fake its start-up. - async def setup(self): - self.printout_versions() - self.available_models = self.scan_models_folder() - # ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ - # ↑↑↑ Keep everything above this line ↑↑↑ - # ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ - - if self.selected_model: - tts_model = f"{self.selected_model}" - if tts_model in self.available_models: - await self.handle_tts_method_change(tts_model) - self.current_model_loaded = tts_model - - # ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ - # ↓↓↓ Keep everything below this line ↓↓↓ - # ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ - else: - self.current_model_loaded = "No Models Available" - print(f"[{self.branding}ENG] \033[91mError\033[0m: Selected model '{self.selected_model}' not found in the models folder.") - self.setup_has_run = True # Set to True, so that the /api/ready endpoint can provide a "Ready" status - - ################################## - ################################## - # CHANGE ME # Low VRAM Swapping # - ################################## - ################################## - # If your model does load into CUDA and you want to support LowVRAM, aka moving the model - # on the fly between CUDA and System RAM on each generation request, you will be adding - # The code in here to do it. See the XTTS tts engine for an example. Piper however, doesnt - # Load into VRAM or System RAM, also low vram is set globally disabled by the model_settings.JSON - # file, so this would never get called anywyay, however, we still need to keep an empty - # function in place. Piper TTS uses "pass" telling function to just exit out cleanly if called. - # However, its quite a simple check along the lines of "if CUDA is available and model is - # in X place, then send it to Y place (or Y to X). - async def handle_lowvram_change(self): - if torch.cuda.is_available(): - if self.device == "cuda": - self.device = "cpu" - self.model.to(self.device) - torch.cuda.empty_cache() - else: - self.device == "cpu" - self.device = "cuda" - self.model.to(self.device) - - ######################################## - ######################################## - # CHANGE ME # DeepSpeed model loading # - ######################################## - ######################################## - # If the model supports CUDA and DeepSpeed, this is where you can handle re-loading - # the model as/when DeepSpeed is enabled/selected in the user interface. If your - # TTS model doesnt support DeepSpeed, then it should be globally set in your - # model_settings.JSON and this function will never get called, however it still needs - # to exist as a function. - async def handle_deepspeed_change(self, value): - if value: - # DeepSpeed enabled - print(f"[{self.branding}ENG] \033[93mDeepSpeed Activating\033[0m") - await self.unload_model() - self.params["tts_method_api_local"] = False - self.params["tts_method_xtts_local"] = True - self.deepspeed_enabled = True - await self.setup() - else: - # DeepSpeed disabled - print(f"[{self.branding}ENG] \033[93mDeepSpeed De-Activating\033[0m") - self.deepspeed_enabled = False - await self.unload_model() - await self.setup() - return value # Return new checkbox value - - ############################################################################################################################################## - ############################################################################################################################################## - # CHANGE ME # scan for available models/voices that are relevant to this TTS engine # XTTS is very unusal as it has 2x model loading methods # - ############################################################################################################################################## - ############################################################################################################################################## - # This function looks and reports back the list of possible models your TTS engine can - # load in. Some TTS engines have multiple models they can load and you will want to use - # code for checking if the models are in the correct location/placement within the disk - # the correct files exst per model etc (check XTTS for an example of this). Some models - # like Piper, the actual models are the voices, so in the Piper scan_models_folder - # function, we fake the only model being available as {'piper': 'piper'} aka, model name - # then engine name, then we use the voices_file_list to populate the models as available - # voices that can be selected in the interface. - # If no models are found, we return "No Models Available" and continue on with the script. - def scan_models_folder(self): - models_folder = self.main_dir / "models" / "xtts" # Edit to match the name of your folder where voices are stored - self.available_models = {} - # ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ - # ↑↑↑ Keep everything above this line ↑↑↑ - # ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ - - required_files = ["config.json", "model.pth", "mel_stats.pth", "speakers_xtts.pth", "vocab.json", "dvae.pth"] - for subfolder in models_folder.iterdir(): - if subfolder.is_dir(): - model_name = subfolder.name - if all(subfolder.joinpath(file).exists() for file in required_files): - self.available_models[f"xtts - {model_name}"] = "xtts" - self.available_models[f"apitts - {model_name}"] = "apitts" - - # ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ - # ↓↓↓ Keep everything below this line ↓↓↓ - # ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ - else: - self.available_models = {'No Models Available': 'xtts'} # Edit the xtts to whatever the name of your engine is - print(f"[{self.branding}ENG] \033[91mWarning\033[0m: Model folder '{model_name}' is missing required") - print(f"[{self.branding}ENG] \033[91mWarning\033[0m: files or the folder does not exist.") - return self.available_models - - ############################################################# - ############################################################# - # CHANGE ME # POPULATE FILES LIST FROM VOICES DIRECTORY #### - ############################################################# - ############################################################# - # This function looks and reports back the list of possible voice your TTS engine can - # load in. Some TTS engines the voices are wav file samples (XTTS), some are models - # (Piper) and some are text (Parler) thats stored in a JSON file. We just need to - # populate the "voices" variable somehow and if no voices are found, we return - # "No Voices Found" back to the interface/api. - def voices_file_list(self): - try: - voices = [] - # ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ - # ↑↑↑ Keep everything above this line ↑↑↑ - # ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ - - directory = self.main_dir / "voices" - # 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 ↓↓↓ - # ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ - if not voices: - return ["No Voices Found"] # Return a list with {'No Voices Found'} if there are no voices/voice models. - return voices # Return a list of models in the format {'engine name': 'model 1', 'engine name': 'model 2", etc} - except Exception as e: - print(f"[{self.branding}ENG] \033[91mError\033[0m: Voices/Voice Models not found. Cannot load a list of voices.") - print(f"[{self.branding}ENG]") - return ["No Voices Found"] - - ###################################################################################### - ###################################################################################### - # CHANGE ME # Model loading # XTTS is very unusal as it has 2x model loading methods # - ###################################################################################### - ###################################################################################### - # This function will handle the loading of your model, into VRAM/CUDA, System RAM or whatever. - # In XTTS, which has 2x model loader types, there are 2x loaders. They are called by "def handle_tts_method_change" - # In Piper we fake a model loader as Piper doesnt actually load a model into CUDA/System RAM as such. So, in that - # situation, api_manual_load_model is kind of a blank function. Though we do set self.is_tts_model_loaded = True - # as this is used elsewhere in the scripts to confirm that a model is available to be used for TTS generation. - # We always check for "No Models Available" being sent as that means we are trying to load in a model that - # doesnt exist/wasnt found on script start-up e.g. someone deleted the model from the folder or something. - - # Model loader Method 1 - async def api_manual_load_model(self, model_name): - if "No Models Available" in self.available_models: - print(f"[{self.branding}ENG] \033[91mError\033[0m: No models for this TTS engine were found to load. Please download a model.") - return - model_path = self.main_dir / "models" / "xtts" / model_name - print("model_path is:", model_path) if self.debug_tts_variables else None - # ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ - # ↑↑↑ Keep everything above this line ↑↑↑ - # ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ - - self.model = TTS( # self.model is a global variable for the variable controlling the model in vram/cuda/system ram - model_path=model_path, - config_path=model_path / "config.json", - ).to(self.device) - print(f"[{self.branding}ENG] \033[94mModel License:\033[93m https://coqui.ai/cpml.txt\033[0m") - - # ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ - # ↓↓↓ Keep everything below this line ↓↓↓ - # ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ - self.is_tts_model_loaded = True - return self.model - - # Model loader Method 2 - async def xtts_manual_load_model(self, model_name): - if "No Models Available" in self.available_models: - print(f"[{self.branding}ENG] \033[91mError\033[0m: No models for this TTS engine were found to load. Please download a model.") - return - # ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ - # ↑↑↑ Keep everything above this line ↑↑↑ - # ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ - - config = XttsConfig() - model_path = self.main_dir / "models" / "xtts" / model_name - config_path = model_path / "config.json" - vocab_path_dir = model_path / "vocab.json" - checkpoint_dir = model_path - config.load_json(str(config_path)) - self.model = Xtts.init_from_config(config) - self.model.load_checkpoint( - config, - checkpoint_dir=str(checkpoint_dir), - vocab_path=str(vocab_path_dir), - use_deepspeed=self.deepspeed_enabled, - ) - self.model.to(self.device) - self.is_tts_model_loaded = True - print(f"[{self.branding}ENG] \033[94mModel License:\033[93m https://coqui.ai/cpml.txt\033[0m") - - # ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ - # ↓↓↓ Keep everything below this line ↓↓↓ - # ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ - return self.model - - ############################### - ############################### - # CHANGE ME # Model unloading # - ############################### - ############################### - # This function will handle the UN-loading of your model, from VRAM/CUDA, System RAM or whatever. - # In XTTS, that model loads into CUDA/System Ram, so when we swap models, we want to unload the current model - # free up the memory and then load in the new model to VRAM/CUDA. On the flip side of that, Piper doesnt - # doesnt load into memory, so we just need to put a fake function here that doesnt really do anything - # other than set "self.is_tts_model_loaded = False", which would be set back to true by the model loader. - # So look at the Piper model_engine.py if you DONT need to unload models. - async def unload_model(self): - self.is_tts_model_loaded = False - if not self.current_model_loaded == None: - print(f"[{self.branding}ENG] \033[94mUnloading model \033[0m") if self.debug_tts else None - if hasattr(self, 'model'): - del self.model - if torch.cuda.is_available(): - torch.cuda.empty_cache() - return None - - ################################################################################################################################### - ################################################################################################################################### - # CHANGE ME # Model changing. Unload out old model and load in a new one # XTTS is very unusal as it has 2x model loading methods # - ################################################################################################################################### - ################################################################################################################################### - # This function is your central model loading/unloading handler that deals with the above functions as necesary, to call loading, unloading, - # swappng DeepSpeed, Low vram etc. This function gets called with a "engine name - model name" type call. In XTTS, because there are 2x - # model loader types, (XTTS and APILocal), we take tts_method and split the "engine name - model name" into a loader type and the model - # that it needs to load in and then we call the correct loader function. Whereas in Piper, which doesnt load models into memory at all, - # we just have a fake function that doesnt really do anything. We always check to see if the model name has "No Models Available" in the - # name thats sent over, just to catch any potential errors. We display the start load time and end load time. Thats about it. - async def handle_tts_method_change(self, tts_method): - generate_start_time = time.time() # Record the start time of loading the model - if "No Models Available" in self.available_models: - print(f"[{self.branding}ENG] \033[91mError\033[0m: No models for this TTS engine were found to load. Please download a model.") - return False - # ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ - # ↑↑↑ Keep everything above this line ↑↑↑ - # ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ - - await self.unload_model() - if tts_method.startswith("xtts"): - model_name = tts_method.split(" - ")[1] - print(f"[{self.branding}ENG]\033[94m Model/Engine :\033[93m {model_name}\033[94m loading into\033[93m", self.device,"\033[0m") - self.model = await self.xtts_manual_load_model(model_name) - self.current_model_loaded = f"xtts - {model_name}" - elif tts_method.startswith("apitts"): - model_name = tts_method.split(" - ")[1] - print(f"[{self.branding}ENG]\033[94m Model/Engine :\033[93m {model_name}\033[94m loading into\033[93m", self.device,"\033[0m") - self.model = await self.api_manual_load_model(model_name) - self.current_model_loaded = f"apitts - {model_name}" - else: - self.current_model_loaded = None - return False - - # ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ - # ↓↓↓ Keep everything below this line ↓↓↓ - # ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ - generate_end_time = time.time() # Create an end timer for calculating load times - generate_elapsed_time = generate_end_time - generate_start_time # Calculate start time minus end time - print(f"[{self.branding}ENG] \033[94mLoad time :\033[93m {generate_elapsed_time:.2f} seconds.\033[0m") # Print out the result of the load time - return True - - ########################################################################################################################################## - ########################################################################################################################################## - # CHANGE ME # Model changing. Unload out old model and load in a new one # XTTS is very unusal as it has 2x model TTS generation methods # - ########################################################################################################################################## - ########################################################################################################################################## - # In here all the possible options are sent over (text, voice to use, lanugage, speed etc etc) and its up to you how you use them, or not. - # obviously if your TTS engine doesnt support speed for example, generationspeed_capable should be set False in your model_settings.JSON file - # and a fake "generationspeed_set" value should be set. This allows a fake value to be sent over from the main script, even though it - # wouldnt actually ever be used in the generation below. Nonethless all these values, real or just whats inside the configuration file - # will be sent over for use. - # Setting the xxxxxxx_capabale in the model_settings.JSON file, will enable/disable them being selectable by the user. For example, if you - # set "generationspeed_capable" as false in the model_settings.JSON file, a user will not be able to select OR set the setting for - # generation speed. - # One thing to note is that we HAVE to have something in this generation request that is synchronous from the way its called, which means - # we have to have an option for Streaming, even if our TTS engine doesnt support streaming. So in that case, we would set streaming_capable - # as false in our model_settings.JSON file, meaning streaming will never be called. However, we have to put a fake streaming routine in our - # function below (or a real function if it does support streaming of course). Parler has an example of a fake streaming function, which is - # very clearly highlighted in its model_engine.py script. - # Piper TTS, which uses command line based calls and therefore has different ones for Windows and Linux/Mac, has an example of doing this - # within its model_engine.py file. - async def generate_tts(self, text, voice, language, temperature, repetition_penalty, speed, pitch, output_file, streaming): - print(f"[{self.branding}Debug] Entered model_engine.py generate_tts function") if self.debug_tts else None - if not self.is_tts_model_loaded: # Check if a model is loaded and error out if not. - error_message = f"[{self.branding}ENG] \033[91mError\033[0m: You currently have no TTS model loaded." - print(error_message) - raise HTTPException(status_code=400, detail="You currently have no TTS model loaded.") # Raise an exception with a meaningful HTTP status code - self.tts_generating_lock = True # Set the tts_generating lock to True, which stops other generation requests being sent into the pipeline - print(f"[{self.branding}Debug] Checking low VRAM") if self.debug_tts else None - if self.lowvram_enabled and self.device == "cpu": # If necessary, move the model out of System Ram to VRAM - print(f"[{self.branding}Debug] Switching device") if self.debug_tts else None - await self.handle_lowvram_change() - print(f"[{self.branding}Debug] Setting a generate time") if self.debug_tts else None - generate_start_time = time.time() # Record the start time of generating TTS - # ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ - # ↑↑↑ Keep everything above this line ↑↑↑ - # ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ - - # 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 is:", self.current_model_loaded) if self.debug_tts else None - self.current_model_loaded - 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: - # 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 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, - max_ref_length=self.model.config.max_ref_len, - sound_norm_refs=self.model.config.sound_norm_refs, - ) - print(f"[{self.branding}Debug] Moving to common args") if self.debug_tts else None - # Common arguments for both functions - common_args = { - "text": text, - "language": language, - "gpt_cond_latent": gpt_cond_latent, - "speaker_embedding": speaker_embedding, - "temperature": float(temperature), - "length_penalty": float(self.model.config.length_penalty), - "repetition_penalty": float(repetition_penalty), - "top_k": int(self.model.config.top_k), - "top_p": float(self.model.config.top_p), - "speed": float(speed), - "enable_text_splitting": True - } - print(f"[{self.branding}Debug] Common arguments: {common_args}") if self.debug_tts_variables else None - #tts_stop_generation = False # Called to stop generation of the current text at whatever stage its at. currently only set for streaming. - # Determine the correct inference function and add streaming specific argument if needed - inference_func = self.model.inference_stream if streaming else self.model.inference - if streaming: - common_args["stream_chunk_size"] = 20 - - # Call the appropriate function - output = inference_func(**common_args) - - # Process the output based on streaming or non-streaming - if streaming: - print(f"[{self.branding}Debug] Streaming audio generation started") if self.debug_tts else None - # Streaming-specific operations - file_chunks = [] - wav_buf = io.BytesIO() - with wave.open(wav_buf, "wb") as vfout: - vfout.setnchannels(1) - vfout.setsampwidth(2) - vfout.setframerate(24000) - vfout.writeframes(b"") - wav_buf.seek(0) - yield wav_buf.read() - - for i, chunk in enumerate(output): - if self.tts_stop_generation: # This is the variable that is called by the stop generation endpoint and interrupts streaming chunk generation when set True. - print(f"[{self.branding}GEN] Stopping audio generation.") if self.debug_tts else None - file_chunks.clear() # Clear the file_chunks list - self.tts_stop_generation = False # Set the stop back to false - self.tts_generating_lock = False # Undo the generation lock - break # Exit out of the function - file_chunks.append(chunk) - if isinstance(chunk, list): - chunk = torch.cat(chunk, dim=0) - chunk = chunk.clone().detach().cpu().numpy() - chunk = chunk[None, : int(chunk.shape[0])] - chunk = np.clip(chunk, -1, 1) - chunk = (chunk * 32767).astype(np.int16) - yield chunk.tobytes() - print(f"[{self.branding}Debug] Yielded audio chunk {i}") if self.debug_tts else None - print(f"[{self.branding}Debug] Streaming audio generation completed") if self.debug_tts else None - else: - print(f"[{self.branding}Debug] Non-streaming audio generation", output_file) if self.debug_tts else None - torchaudio.save(output_file, torch.tensor(output["wav"]).unsqueeze(0), 24000) - print(f"[{self.branding}Debug] Non-streaming audio generation completed") if self.debug_tts else None - - # apitts Methods - elif self.current_model_loaded.startswith ("apitts"): - # Streaming only allowed for XTTSv2 local - if streaming: - raise ValueError("Streaming is only supported in XTTSv2 local") - - # Set the correct output path (different from the if statement) - print(f"[{self.branding}Debug] Using apitts") if self.debug_tts else None - self.model.tts_to_file( - text=text, - file_path=output_file, - speaker_wav=wavs_files, - language=language, - temperature=temperature, - length_penalty=self.model.config.length_penalty, - repetition_penalty=repetition_penalty, - top_k=self.model.config.top_k, - top_p=self.model.config.top_p, - speed=speed, - ) - - # ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ - # ↓↓↓ Keep everything below this line ↓↓↓ - # ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ - generate_end_time = time.time() # Record the end time to generate TTS - generate_elapsed_time = generate_end_time - generate_start_time - print(f"[{self.branding}GEN] \033[94mTTS Generate: \033[93m{generate_elapsed_time:.2f} seconds. \033[94mLowVRAM: \033[33m{self.lowvram_enabled} \033[94mDeepSpeed: \033[33m{self.deepspeed_enabled}\033[0m") - if self.lowvram_enabled and self.device == "cuda" and self.tts_narrator_generatingtts == False: - await self.handle_lowvram_change() - self.tts_generating_lock = False # Unlock the TTS generation queue to allow TTS generation requests to come in again. - print(f"[{self.branding}Debug] generate_tts function completed") if self.debug_tts else None - return - - +""" +XTTS (Coqui TTS) Engine Implementation for AllTalk TTS +Version: 1.0 +Last Updated: 2024 + +This implementation serves as both a functional XTTS engine and a reference example +for implementing new TTS engines in AllTalk. It demonstrates proper: +- Variable initialization and management +- Debug message handling +- Model loading/unloading patterns +- Resource management +- Generation handling with streaming support + +Implementation Notes: +- All core system variables must be maintained +- All functions prefixed with "DONT CHANGE" must remain unmodified +- Code additions should be placed between the marked sections in each function +- Debug messages use self.print_message() with appropriate message types +- All functions must implement self.debug_func_entry() for trace logging + +Note: Text between "↑↑↑ Keep everything above this line ↑↑↑" and "↓↓↓ Keep everything below this line ↓↓↓" +markers **TYPICALLY** must remain unchanged as it contains critical system integration code. + +Note: You can add new functions, just DONT remove the functions that are already there, even if they +are doing nothing as `tts_server.py` will still look for their existance and fail if they are missing. +""" + +######################################## +# Default imports # Do not change this # +######################################## +import os +import gc +import sys +import glob +import json +import time +import inspect +import torch +import logging +from pathlib import Path +from fastapi import (HTTPException) +logging.disable(logging.WARNING) + +# Confguration file management for confignew.json +try: + from .config import AlltalkConfig, AlltalkTTSEnginesConfig, AlltalkNewEnginesConfig # TGWUI import +except ImportError: + from config import AlltalkConfig, AlltalkTTSEnginesConfig, AlltalkNewEnginesConfig # Standalone import + +def initialize_configs(): + """Initialize all configuration instances""" + config = AlltalkConfig.get_instance() + tts_engines_config = AlltalkTTSEnginesConfig.get_instance() + new_engines_config = AlltalkNewEnginesConfig.get_instance() + return config, tts_engines_config, new_engines_config + +# Load in the central config management +config, tts_engines_config, new_engines_config = initialize_configs() + +###################################################### +# Get Pytorch & Python versions # Do not change this # +###################################################### +pytorch_version = torch.__version__ +cuda_version = torch.version.cuda +major, minor, micro = sys.version_info[:3] +python_version = f"{major}.{minor}.{micro}" + +############################################ +# DeepSpeed imports # POSSIBLY change this # +############################################ +""" +If the new TTS engine you are importing doesnt support DeepSpeed, then +you can simply change `model_supports_deepspeed_true_or_false` to `False` +This will be much faster when starting up the engine. This is seperate +from what is stored in your `model_settings.json` file. +""" +# ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ +# ↓↓↓ MODIFY THIS LINE ↓↓↓ +# ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ + +model_supports_deepspeed_true_or_false = True + +# ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ +# ↑↑↑ MODIFY THIS LINE ↑↑↑ +# ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ + +if model_supports_deepspeed_true_or_false: + try: + import deepspeed + deepspeed_available = True + except ImportError: + deepspeed_available = False + pass + +####################################################### +# TTS Engine-Specific Imports and Setup # Change this # +####################################################### +""" +This section is for TTS engine specific imports and global variable setup. + +Guidelines: +1. Import all required modules for your TTS engine +2. Handle import errors with appropriate error messages +3. Set up any global variables or configurations needed by your engine +4. Use try/except blocks to gracefully handle missing dependencies +5. Ensure all error messages follow the AllTalk format for print messages. + At this stage the `def print_messages` is not available, so please use + standard `print("[AllTalk ENG] some message here")` messages. + +Example structure: + +try: + # Import your TTS engine's required modules + from your_tts_engine import required_modules +except ModuleNotFoundError: + # Handle missing dependencies with helpful error messages + print("Missing required modules. Please install...") + raise + +For XTTS specifically, we need: +- torchaudio: For audio processing +- TTS modules: Core Coqui TTS functionality +- Support libraries: wave, io, random, numpy for audio handling +""" +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 + from TTS.api import TTS + from TTS.utils.synthesizer import Synthesizer +except ModuleNotFoundError: + brand = "[AllTalk ENG]" + print(f"{brand} \033[91mError\033[0m Could not find the Coqui TTS modules.") + print(f"{brand} \033[91mError\033[0m Please re-install the requirements.") + raise + + +######################################################### +# Class setup # Change the relevant functions as needed # +######################################################### +class tts_class: + """ + TTS Engine Implementation Class + + This class provides the interface between `tts_server.py` and whatever TTS engine you install. + It handles model loading, voice management, and TTS generation in both streaming + and non-streaming modes. Streaming will only be supported if the underlying TTS engine + actually supports streaming. + + Key Responsibilities: + 1. Model Management: + - Loading/unloading models + - Managing model state between CPU and GPU + - Handling DeepSpeed integration + + 2. Voice Management: + - Managing voice samples or model files + + 3. TTS Generation: + - Converting text to speech + - Supporting streaming output (If the engine supports it) + - Managing generation parameters + + 4. System Integration: + - Implementing standard AllTalk interfaces + - Managing engine state and configuration + - Handling resource allocation + """ + + ############################################### + # Central print function # Do not change this # + ############################################### + def print_message(self, message, message_type="standard", component="ENG"): + """ + Centralized print function for messages. Use this for print output to console. + As this is the model Engine, all `component` printouts are set to ENG as default. + + Args: + message (str): The message to print + message_type (str): Type of message (standard/warning/error/debug_*/debug) + component (str): Component identifier (TTS/ENG/GEN/API/etc.) + + Example Use: + self.print_message("This is a standard print out mesage to a user) + self.print_message("This is a debug_tts message, message_type="debug_tts") + self.print_message("This is an error message to a user, message_type="error") + self.print_message("This is an warning message to a user, message_type="warning") + + Debug Types: + debug_func: Tracks function entry + debug_tts: Enable TTS process debugging + debug_tts_variables: Enable variable state debugging + + WARNING: This is a core system function. Do not modify its implementation + as it provides standardized version reporting across all engines. + """ + # ANSI color codes + BLUE = "\033[94m" + MAGENTA = "\033[95m" + YELLOW = "\033[93m" + RED = "\033[91m" + GREEN = "\033[92m" + RESET = "\033[0m" + prefix = f"[{config.branding}{component}] " + if message_type.startswith("debug_"): + debug_flag = getattr(config.debugging, message_type, False) + if not debug_flag: + return + if message_type == "debug_func" and "Function entry:" in message: + message_parts = message.split("Function entry:", 1) + print(f"{prefix}{BLUE}Debug{RESET} {YELLOW}{message_type}{RESET} Function entry:{GREEN}{message_parts[1]}{RESET} in model_engine") + else: + print(f"{prefix}{BLUE}Debug{RESET} {YELLOW}{message_type}{RESET} {message}") + elif message_type == "debug": + print(f"{prefix}{BLUE}Debug{RESET} {message}") + elif message_type == "warning": + print(f"{prefix}{YELLOW}Warning{RESET} {message}") + elif message_type == "error": + print(f"{prefix}{RED}Error{RESET} {message}") + else: + print(f"{prefix}{message}") + + def debug_func_entry(self): + """Log function entry if debug_func is enabled.""" + if config.debugging.debug_func: + current_func = inspect.currentframe().f_back.f_code.co_name + self.print_message(f"Function entry: {current_func}", "debug_func") + + ############################################# + # Script initalisation # Do not change this # + ############################################# + def __init__(self): + """ + Initialize the XTTS engine instance. + + WARNING: This class requires specific variables to interface with AllTalk's main system (tts_server.py). + Do not remove or rename any of the predefined variables as they are required for proper system integration. + + Required System Interface Variables: + + 1. Core System Variables (DO NOT MODIFY): + Base Configuration: + - self.this_dir: Engine directory path (where this script is located) + - self.main_dir: AllTalk root directory + - self.device: Processing device ("cuda" or "cpu") + - self.cuda_is_available: Whether GPU/CUDA is available + + State Tracking: + - self.tts_generating_lock: Prevents concurrent generation requests + - self.tts_stop_generation: Signals generation stop request + - self.tts_narrator_generatingtts: Tracks narrator mode for optimization + - self.model: Active TTS model instance + - self.is_tts_model_loaded: Whether a model is currently loaded + - self.current_model_loaded: Name of currently loaded model + - self.available_models: List of models found by scan_models_folder + - self.setup_has_run: Tracks if setup() has completed + + 2. Engine Configuration Variables (DO NOT MODIFY): + - self.engines_available: List of all available TTS engines + - self.engine_loaded: Currently selected TTS engine + - self.selected_model: Currently selected model name + + 3. Model Settings (SET VIA model_settings.json): + Capability Flags: + - self.audio_format: Output audio format (wav, mp3, etc.) + - self.deepspeed_capable: DeepSpeed acceleration support + - self.generationspeed_capable: Speed adjustment support + - self.languages_capable: Multi-language support + - self.lowvram_capable: Low VRAM mode support + - self.multimodel_capable: Multiple model support + - self.repetitionpenalty_capable: Repetition penalty support + - self.streaming_capable: Audio streaming support + - self.temperature_capable: Temperature adjustment support + - self.multivoice_capable: Multiple voice support + - self.pitch_capable: Pitch adjustment support + + Engine Settings: + - self.def_character_voice: Default character voice + - self.def_narrator_voice: Default narrator voice + - self.deepspeed_enabled: DeepSpeed status + - self.engine_installed: Engine installation status + - self.generationspeed_set: Current speed setting + - self.lowvram_enabled: Low VRAM mode status + - self.repetitionpenalty_set: Current repetition penalty + - self.temperature_set: Current temperature setting + - self.pitch_set: Current pitch setting + + OpenAI Voice Mappings: + - self.openai_alloy: Alloy voice mapping + - self.openai_echo: Echo voice mapping + - self.openai_fable: Fable voice mapping + - self.openai_nova: Nova voice mapping + - self.openai_onyx: Onyx voice mapping + - self.openai_shimmer: Shimmer voice mapping + + Integration Requirements: + - All variables must be present even if unused by your engine + - Capability flags should accurately reflect engine features + - Settings should have sensible defaults even if not used + - OpenAI mappings should be set even if not supporting OpenAI compatibility + + Note: Variables marked (DO NOT MODIFY) are critical system integration points. + Other variables should be configured through their respective JSON files or + you can add new central variables in the section provided down below. + """ + # DO NOT MODIFY - Sets up the base variables required for any tts engine # + self.this_dir = Path(__file__).parent.resolve() + self.main_dir = Path(__file__).parent.parent.parent.parent.resolve() + self.device = "cuda" if torch.cuda.is_available() else "cpu" + self.cuda_is_available = torch.cuda.is_available() + self.tts_generating_lock = False + self.tts_stop_generation = False + self.tts_narrator_generatingtts = False + self.model = None + self.is_tts_model_loaded = False + self.current_model_loaded = None + self.available_models = None + self.setup_has_run = False + self.engines_available = tts_engines_config.get_engine_names_available() + self.engine_loaded = tts_engines_config.engine_loaded + self.selected_model = tts_engines_config.selected_model + + # DO NOT MODIFY - Load in the current TTS Engines model_settings.json file + with open(os.path.join(self.this_dir, "model_settings.json"), "r") as f: + model_settings_file = json.load(f) + + # DO NOT MODIFY - Model details from model_settings.json + self.manufacturer_name = model_settings_file["model_details"]["manufacturer_name"] + self.manufacturer_website = model_settings_file["model_details"]["manufacturer_website"] + + # DO NOT MODIFY - Model capabilities from model_settings.json + self.audio_format = model_settings_file["model_capabilties"]["audio_format"] + self.deepspeed_capable = model_settings_file["model_capabilties"]["deepspeed_capable"] + self.deepspeed_available = 'deepspeed' in globals() + self.generationspeed_capable = model_settings_file["model_capabilties"]["generationspeed_capable"] + self.languages_capable = model_settings_file["model_capabilties"]["languages_capable"] + self.lowvram_capable = model_settings_file["model_capabilties"]["lowvram_capable"] + self.multimodel_capable = model_settings_file["model_capabilties"]["multimodel_capable"] + self.repetitionpenalty_capable = model_settings_file["model_capabilties"]["repetitionpenalty_capable"] + self.streaming_capable = model_settings_file["model_capabilties"]["streaming_capable"] + self.temperature_capable = model_settings_file["model_capabilties"]["temperature_capable"] + self.multivoice_capable = model_settings_file["model_capabilties"]["multivoice_capable"] + self.pitch_capable = model_settings_file["model_capabilties"]["pitch_capable"] + + # DO NOT MODIFY - Engine settings from model_settings.json + self.def_character_voice = model_settings_file["settings"]["def_character_voice"] + self.def_narrator_voice = model_settings_file["settings"]["def_narrator_voice"] + self.deepspeed_enabled = model_settings_file["settings"]["deepspeed_enabled"] + self.engine_installed = model_settings_file["settings"]["engine_installed"] + self.generationspeed_set = model_settings_file["settings"]["generationspeed_set"] + self.lowvram_enabled = model_settings_file["settings"]["lowvram_enabled"] + self.lowvram_enabled = False if not torch.cuda.is_available() else self.lowvram_enabled + self.repetitionpenalty_set = model_settings_file["settings"]["repetitionpenalty_set"] + self.temperature_set = model_settings_file["settings"]["temperature_set"] + self.pitch_set = model_settings_file["settings"]["pitch_set"] + + # DO NOT MODIFY - OpenAI voice mappings from model_settings.json + self.openai_alloy = model_settings_file["openai_voices"]["alloy"] + self.openai_echo = model_settings_file["openai_voices"]["echo"] + self.openai_fable = model_settings_file["openai_voices"]["fable"] + self.openai_nova = model_settings_file["openai_voices"]["nova"] + self.openai_onyx = model_settings_file["openai_voices"]["onyx"] + self.openai_shimmer = model_settings_file["openai_voices"]["shimmer"] + + """ + Below is the name of the folder that will be created-used under `/models/{folder}` + And is further used by the base functions of this script. Change the name stored in + `self.model_folder_name` to the model folder name you will be using. Ensure to use + the correct CAPS/Non-CAPS spelling as Linux OS is CAPS specific on folder names. + """ + # ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ + # ↓↓↓ MODIFY THIS LINE ↓↓↓ + # ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ + + self.model_folder_name = "xtts" + + # ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ + # ↑↑↑ MODIFY THIS LINE ↑↑↑ + # ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ + + # ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ + # ↓↓↓ Add your own central `self.myvariable` variables in here if needed for your engine ↓↓↓ + # ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ + """ + If you need globally accessable variables of your own for your own purposes, you can put + them in here as self.myvariable = "whatever". + + """ + # ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ + # ↑↑↑ Add your own central `self.myvariable` variables in here if needed for your engine ↑↑↑ + # ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ + + # DO NOT MODIFY - log the function call to this function + self.debug_func_entry() + + ##################################################### + # Printout engine loading bits # Do not change this # + ##################################################### + def printout_versions(self): + """ + Print Python, DeepSpeed, Pytorch and CUDA version on start-up. + + WARNING: This is a core system function. Do not modify its implementation + as it provides standardized version reporting across all engines. + """ + self.debug_func_entry() + if not model_supports_deepspeed_true_or_false: + self.print_message(f"\033[92mDeepSpeed version :\033[93m Not supported on {self.model_folder_name}\033[0m", message_type="standard") + else: + if deepspeed_available: + self.print_message("\033[92mDeepSpeed version :\033[93m " + str(deepspeed.__version__) + "\033[0m", message_type="standard") + else: + self.print_message("\033[92mDeepSpeed version :\033[93m Not available\033[0m", message_type="standard") + self.print_message(f"\033[92mPython Version :\033[93m {python_version}\033[0m", message_type="standard") + self.print_message(f"\033[92mPyTorch Version :\033[93m {pytorch_version}\033[0m", message_type="standard") + if cuda_version is None: + self.print_message("\033[92mCUDA Version :\033[91m Not available\033[0m", message_type="standard") + else: + self.print_message(f"\033[92mCUDA Version :\033[93m {cuda_version}\033[0m", message_type="standard") + + self.print_message("", message_type="standard") + return + + ################################################################ + # Handle low VRAM change between CUDA/CPU # Do not change this # + ################################################################ + async def handle_lowvram_change(self): + """ + Manage model location between CPU and GPU memory for low VRAM operation. + + This function handles the movement of models between CPU and GPU memory + to support systems with limited VRAM. It's called automatically during + generation when low VRAM mode is enabled. + + Operation: + 1. Checks CUDA availability + 2. Moves model between devices based on current location: + - GPU (cuda) -> CPU + - CPU -> GPU (cuda) + 3. Manages CUDA cache to optimize memory usage + + States Affected: + - self.device: Updated to reflect current processing device + - self.model.device: Model's current memory location + + Requirements: + - CUDA must be available for GPU operations + - Model must be loaded (self.model is not None) + - lowvram_enabled must be `True` in the `model_settings.json` file + + Note: This function is only called when self.lowvram_enabled is True + meaning the engine does or doesnt support the call, hence if its not + True, then this function would never be called anyway, so doesnt need + changing. + """ + self.debug_func_entry() + + # Initial validation + if not self.is_tts_model_loaded: + self.print_message("No model is currently loaded. Please select a model to load.", message_type="error") + raise HTTPException(status_code=400, detail="No model is currently loaded. Please select a model to load.") + + if torch.cuda.is_available(): + if self.device == "cuda": + self.print_message("Moving model to CPU", message_type="debug_tts") + self.device = "cpu" + self.model.to(self.device) + torch.cuda.empty_cache() + gc.collect() + else: + self.device = "cuda" + self.print_message("Moving model to GPU", message_type="debug_tts") + self.model.to(self.device) + gc.collect() + + ################################################ + # Handle DeepSpeed change # Do not change this # + ################################################ + async def handle_deepspeed_change(self, value): + """ + Handle enabling/disabling of DeepSpeed acceleration. + + This function manages the process of reloading the model with or without + DeepSpeed acceleration. DeepSpeed can significantly improve performance on + supported hardware. + + Args: + value (bool): True to enable DeepSpeed, False to disable + + Operation: + 1. Unloads current model + 2. Updates DeepSpeed settings + 3. Reloads model with new configuration + + States Affected: + - self.deepspeed_enabled: Updated to reflect new state + - self.model: Reloaded with new configuration + + Returns: + bool: The new DeepSpeed state (same as input value) + + Note: DeepSpeed must be installed and available in the system for + this functionality to work. `deepspeed_capable` must be set `True` + in the `model_settings.json` file + """ + self.debug_func_entry() + # Initial validation + if self.current_model_loaded.startswith("apitts"): # Specific only to the XTTS engine + self.print_message("\033[93mDeepSpeed not supported in API mode\033[0m", message_type="error") + self.deepspeed_enabled = False + return False + + if not self.is_tts_model_loaded: + self.print_message("No model is currently loaded. Please select a model to load.", message_type="error") + raise HTTPException(status_code=400, detail="No model is currently loaded. Please select a model to load.") + + if value: + self.print_message("\033[93mDeepSpeed Activating\033[0m", message_type="standard") + await self.unload_model() + self.deepspeed_enabled = True + await self.setup() + else: + self.print_message("\033[93mDeepSpeed De-Activating\033[0m", message_type="standard") + self.deepspeed_enabled = False + await self.unload_model() + await self.setup() + return value + + + ################################################################ + # Unload models from VRAM/RAM if possible # Do not change this # + ################################################################ + async def unload_model(self): + """ + Unload the current model and free associated resources. + + This function handles the cleanup of model resources, including: + 1. Setting model loaded flag to False + 2. Deleting the model instance + 3. Clearing CUDA cache if available + + Operation: + 1. Updates model loading status + 2. Logs unloading process if a model is loaded + 3. Removes model from memory + 4. Cleans up CUDA cache if using GPU + + States Affected: + - self.is_tts_model_loaded: Set to False + - self.model: Set to None after unloading + """ + self.debug_func_entry() + + self.is_tts_model_loaded = False + if not self.current_model_loaded == None: + self.print_message("Unloading model", message_type="debug_tts") + if hasattr(self, 'model'): + del self.model + if torch.cuda.is_available(): + torch.cuda.empty_cache() + return None + + ############################################################ + # On start-up, perform these actions # Change as necessary # + ############################################################ + async def setup(self): + """ + Initialize the XTTS engine and load initial model configuration. + + This function is called during system startup and handles: + 1. Version information display + 2. Model scanning and availability check + 3. Initial model loading if specified + + The setup sequence ensures: + - Proper version reporting + - Model availability verification + - Graceful handling of missing models + - Correct initial model loading state + + States Set: + - self.available_models: Updated with found models + - self.current_model_loaded: Set to loaded model name or None + - self.setup_has_run: Set True when complete + + Returns: + None + + Note: Custom initialization code should be placed between the marked sections. + """ + self.debug_func_entry() + self.print_message("Initializing XTTS engine", message_type="debug_tts") + self.printout_versions() + self.available_models = self.scan_models_folder() + # ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ + # ↑↑↑ Keep everything above this line ↑↑↑ + # ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ + + if self.selected_model: + tts_model = f"{self.selected_model}" + if tts_model in self.available_models: + self.print_message(f"Loading selected model: {tts_model}", message_type="debug_tts") + await self.handle_tts_method_change(tts_model) + self.current_model_loaded = tts_model + + # ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ + # ↓↓↓ Keep everything below this line ↓↓↓ + # ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ + else: + self.current_model_loaded = "No Models Available" + self.print_message(f"Selected model '{self.selected_model}' not found in models folder.", message_type="error") + self.print_message(f"Please download a model or select a different model file.", message_type="error") + self.setup_has_run = True + + + ########################################################################### + # Scan your models folder for models OR voice files # Change as necessary # + ########################################################################### + def scan_models_folder(self): + """ + Scan for available XTTS models in the models directory. + + This function searches the models directory for valid XTTS model installations. + Each model must contain all required files to be considered valid. + + Required Files for Each Model: + - config.json: Model configuration + - model.pth: Model weights + - mel_stats.pth: Mel spectrogram statistics + - speakers_xtts.pth: Speaker embeddings + - vocab.json: Tokenizer vocabulary + - dvae.pth: Discrete VAE weights + + Operation: + 1. Scans the models/xtts directory + 2. Checks each subfolder for required files + 3. Registers valid models in two formats: + - "xtts - {model_name}": For local inference + - "apitts - {model_name}": For API-based inference + + States Affected: + - self.available_models: Updated with found models + + Returns: + dict: Dictionary of available models in format: + {model_identifier: engine_type} + + Note: If no valid models are found, returns {"No Models Available": "xtts"} + """ + self.debug_func_entry() + + models_folder = self.main_dir / "models" / self.model_folder_name + self.available_models = {} + + # ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ + # ↑↑↑ Keep everything above this line ↑↑↑ + # ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ + + required_files = ["config.json", "model.pth", "mel_stats.pth", + "speakers_xtts.pth", "vocab.json", "dvae.pth"] + + for subfolder in models_folder.iterdir(): + if subfolder.is_dir(): + model_name = subfolder.name + self.print_message(f"Checking model folder: {model_name}", message_type="debug_tts") + + if all(subfolder.joinpath(file).exists() for file in required_files): + self.print_message(f"Found valid model: {model_name}", message_type="debug_tts") + self.available_models[f"xtts - {model_name}"] = "xtts" + self.available_models[f"apitts - {model_name}"] = "apitts" + + # ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ + # ↓↓↓ Keep everything below this line ↓↓↓ + # ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ + else: + self.available_models = {'No Models Available': self.model_folder_name} + self.print_message(f"Model folder '{model_name}' is missing required files", message_type="warning") + self.print_message("Required files or folder does not exist", message_type="warning") + self.print_message("Please download some models/voices for this engine", message_type="warning") + return self.available_models + + ################################################################ + # Scan your voice folder for voice files # Change as necessary # + ################################################################ + def voices_file_list(self): + """ + Scan and compile a list of available voice samples and latents. + + This function scans multiple directories to find voice samples in different formats: + 1. Individual WAV files in the main voices directory + 2. Collections of WAV files in the xtts_multi_voice_sets directory + 3. Pre-computed voice latents in the xtts_latents directory + + Directory Structure: + - voices/: Individual WAV files + - voices/xtts_multi_voice_sets/: Folders containing multiple WAV files + - voices/xtts_latents/: JSON files containing pre-computed latents + + Voice Types: + - Standard: Direct WAV files + - voiceset: Multiple WAV files for one voice + - latent: Pre-computed speaker embeddings + + Returns: + list: Available voices with appropriate prefixes: + - Standard WAV: filename.wav + - Voice sets: "voiceset:foldername" + - Latents: "latent:filename.json" + + Note: Returns ["No Voices Found"] if no valid voices are detected + """ + self.debug_func_entry() + + try: + voices = [] # An empy variable for the list of voices to be put into. + directory = self.main_dir / "voices" # Base directory that voices are stored in. + # ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ + # ↑↑↑ Keep everything above this line ↑↑↑ + # ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ + + json_latents_dir = directory / "xtts_latents" # XTTS specific + multi_voice_dir = directory / "xtts_multi_voice_sets" # XTTS specific + + # Scan for individual WAV files + self.print_message("Scanning for individual voice files", message_type="debug_tts") + voices.extend([f for f in os.listdir(directory) if os.path.isfile(os.path.join(directory, f)) + and f.endswith(".wav")]) + + # Scan for voice sets + if os.path.exists(multi_voice_dir): + self.print_message("Found Multi_Voice_Sets directory", message_type="debug_tts") + for voice_set in os.listdir(multi_voice_dir): + voice_set_path = multi_voice_dir / voice_set + if os.path.isdir(voice_set_path): + if any(f.endswith(".wav") for f in os.listdir(voice_set_path)): + voices.append(f"voiceset:{voice_set}") + self.print_message(f"Added voice set: {voice_set}", message_type="debug_tts_variables") + + # Scan for JSON latents + if not self.current_model_loaded.startswith("apitts"): # APITTS doesnt support latents + if os.path.exists(json_latents_dir): + self.print_message("Found JSON_Latents directory", message_type="debug_tts") + json_files = [f for f in os.listdir(json_latents_dir) if f.endswith('.json')] + for json_file in json_files: + voices.append(f"latent:{json_file}") + if json_files: + self.print_message(f"Added {len(json_files)} JSON latent files", message_type="debug_tts") + + # ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ + # ↓↓↓ Keep everything below this line ↓↓↓ + # ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ + # Sort voices by type alphabetically + voices.sort(key=lambda x: (x.startswith("voiceset:"), x.startswith("latent:"), x)) + if not voices: + return ["No Voices Found"] + return voices + + except Exception as e: + self.print_message(f"Error scanning for voices: {str(e)}", message_type="error") + return ["No Voices Found"] + + ############################################ + # Load in your model # Change as necessary # + ############################################ + async def load_model(self, model_name): + """ + Load a model using the Coqui TTS API interface. + + This is one of two model loading methods for XTTS. This method uses the high-level + TTS API which provides a simpler interface but less control over model parameters. + + Args: + model_name (str): Name of the model to load + + Operation: + 1. Validates model availability + 2. Constructs model and config paths + 3. Initializes model using TTS API + 4. Moves model to appropriate device (CPU/GPU) + + States Affected: + - self.model: Updated with loaded model + - self.is_tts_model_loaded: Set to True on success + + Returns: + The loaded model instance + + Raises: + HTTPException: If no models are available to load + """ + self.debug_func_entry() + if "No Models Available" in self.available_models: + self.print_message("No models for this TTS engine were found to load", message_type="error") + return + model_path = self.main_dir / "models" / self.model_folder_name / model_name # You may need to edit/modfy depending on how your TTS engine works with models + self.print_message(f"Loading model from: {model_path}", message_type="debug_tts") + # ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ + # ↑↑↑ Keep everything above this line ↑↑↑ + # ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ + + self.model = TTS( + model_path=model_path, + config_path=model_path / "config.json", + ).to(self.device) + + self.print_message("\033[94mModel License : \033[93mhttps://coqui.ai/cpml.txt\033[0m") + + # ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ + # ↓↓↓ Keep everything below this line ↓↓↓ + # ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ + self.is_tts_model_loaded = True + return self.model + + ################################################################################################################### + # Load in your model # XTTS specific as XTTS has 2x loader types so you wouldnt normally have 2x loader functions # + ################################################################################################################### + async def xtts_manual_load_model(self, model_name): + """ + Load a model using the direct XTTS interface. + + This is the second model loading method for XTTS. It provides direct access + to the XTTS model interface, allowing for more detailed control over model + parameters and DeepSpeed integration. + + Args: + model_name (str): Name of the model to load + + Operation: + 1. Validates model availability + 2. Initializes XttsConfig with model settings + 3. Loads model with appropriate configurations + 4. Handles DeepSpeed integration if enabled + 5. Moves model to appropriate device + + States Affected: + - self.model: Updated with loaded model + - self.is_tts_model_loaded: Set to True on success + + Returns: + The loaded model instance + + Raises: + HTTPException: If no models are available to load + """ + self.debug_func_entry() + + if "No Models Available" in self.available_models: + self.print_message("No models for this TTS engine were found to load", message_type="error") + return + + # Enhanced debugging for paths and configuration + config = XttsConfig() + model_path = self.main_dir / "models" / self.model_folder_name / model_name + config_path = model_path / "config.json" + vocab_path_dir = model_path / "vocab.json" + checkpoint_dir = model_path + + self.print_message(f"Model path: {model_path}", message_type="debug_tts_variables") + self.print_message(f"Config path: {config_path}", message_type="debug_tts_variables") + self.print_message(f"Vocab path: {vocab_path_dir}", message_type="debug_tts_variables") + + # Load and debug configuration + config.load_json(str(config_path)) + self.print_message("Model configuration:", message_type="debug_tts_variables") + self.print_message(f"├─ Model dimension: {config.model_args.gpt_n_model_channels}", message_type="debug_tts_variables") + self.print_message(f"├─ Number of layers: {config.model_args.gpt_layers}", message_type="debug_tts_variables") + self.print_message(f"├─ Number of heads: {config.model_args.gpt_n_heads}", message_type="debug_tts_variables") + self.print_message(f"├─ Max audio tokens: {config.model_args.gpt_max_audio_tokens}", message_type="debug_tts_variables") + self.print_message(f"├─ Max text tokens: {config.model_args.gpt_max_text_tokens}", message_type="debug_tts_variables") + self.print_message(f"└─ Using DeepSpeed: {self.deepspeed_enabled}", message_type="debug_tts_variables") + + # Initialize model + self.print_message("Initializing model from config...", message_type="debug_tts") + self.model = Xtts.init_from_config(config) + + # Load checkpoint with detailed progress + self.print_message("Loading model checkpoint...", message_type="debug_tts") + self.model.load_checkpoint( + config, + checkpoint_dir=str(checkpoint_dir), + vocab_path=str(vocab_path_dir), + use_deepspeed=self.deepspeed_enabled, + ) + + # Device management debugging + self.print_message(f"Moving model to device: {self.device}", message_type="debug_tts") + self.model.to(self.device) + if self.device == "cuda": + memory_allocated = torch.cuda.memory_allocated() / 1024**2 + memory_reserved = torch.cuda.memory_reserved() / 1024**2 + self.print_message("CUDA Memory Status:", message_type="debug_tts_variables") + self.print_message(f"├─ Allocated: {memory_allocated:.2f} MB", message_type="debug_tts_variables") + self.print_message(f"└─ Reserved: {memory_reserved:.2f} MB", message_type="debug_tts_variables") + + self.is_tts_model_loaded = True + self.print_message("\033[94mModel License : \033[93mhttps://coqui.ai/cpml.txt\033[0m") + + return self.model + + + async def handle_tts_method_change(self, tts_method): + """ + Handle switching between different XTTS model types and loading methods. + + This function manages the process of changing between different model loading + methods (XTTS local vs API) and handles the actual model loading process. + + Args: + tts_method (str): Format "type - modelname" where type is either + "xtts" or "apitts" + + Operation: + 1. Validates model availability + 2. Unloads current model if any + 3. Parses method string to determine loader type + 4. Calls appropriate model loader + 5. Updates current model tracking + + States Affected: + - self.current_model_loaded: Updated to new model identifier + - self.model: Updated with newly loaded model + + Returns: + bool: True if model loaded successfully, False otherwise + + Timing: + Records and reports model loading time + """ + self.debug_func_entry() + + # Track loading time + generate_start_time = time.time() + + # Validate model availability + if "No Models Available" in self.available_models: + self.print_message("No models for this TTS engine were found to load", message_type="error") + return False + + # Unload current model + await self.unload_model() + + # Handle different loading methods + if tts_method.startswith("xtts"): + model_name = tts_method.split(" - ")[1] + self.print_message(f"\033[94mLoading XTTS model \033[93m{model_name} \033[94mon \033[93m{self.device}\033[0m") + self.model = await self.xtts_manual_load_model(model_name) + self.current_model_loaded = f"xtts - {model_name}" + + elif tts_method.startswith("apitts"): + model_name = tts_method.split(" - ")[1] + self.print_message(f"\033[94mLoading API model \033[93m{model_name} \033[94mon \033[93m{self.device}\033[0m") + self.model = await self.load_model(model_name) + self.current_model_loaded = f"apitts - {model_name}" + + else: + self.print_message(f"Unknown model type in: {tts_method}", message_type="error") + self.current_model_loaded = None + return False + + # Report loading time + generate_end_time = time.time() + generate_elapsed_time = generate_end_time - generate_start_time + self.print_message(f"\033[94mModel Loadtime: \033[93m{generate_elapsed_time:.2f}\033[94m seconds\033[0m") + return True + + async def generate_tts(self, text, voice, language, temperature, repetition_penalty, speed, pitch, output_file, streaming): + """ + Generate speech from text using the XTTS model. + + This core function handles all TTS generation, supporting both streaming and + non-streaming output, multiple voice input types (WAV, voice sets, latents), + and various generation parameters. + + Args: + text (str): Text to convert to speech + voice (str): Voice identifier (WAV file, voice set, or latent) + language (str): Target language code + temperature (float): Generation temperature (0.0-1.0) + repetition_penalty (float): Penalty for repetitive generation + speed (float): Speech speed multiplier + pitch (float): Voice pitch adjustment (not used in XTTS) + output_file (str): Path for output audio file + streaming (bool): Whether to stream audio chunks + + Returns: + For streaming=True: Generator yielding audio chunks + For streaming=False: None (saves to output_file) + + States Used: + - self.model: Active TTS model + - self.device: Current processing device + - self.lowvram_enabled: Low VRAM mode status + - self.current_model_loaded: Current model type + """ + self.debug_func_entry() + + # Initial validation + if not self.is_tts_model_loaded: + self.print_message("No TTS model loaded", message_type="error") + raise HTTPException(status_code=400, detail="You currently have no TTS model loaded.") + + # Lock generation and track start time + self.tts_generating_lock = True + self.print_message("Starting TTS generation process", message_type="debug_tts") + self.print_message(f"Generation parameters: temperature={temperature}, speed={speed}, streaming={streaming}", + message_type="debug_tts_variables") + + # Handle low VRAM mode if needed + if self.lowvram_enabled and self.device == "cpu": + self.print_message("Low VRAM mode: Moving model to GPU", message_type="debug_tts") + await self.handle_lowvram_change() + + generate_start_time = time.time() + + try: + # Voice input processing + self.print_message(f"Processing voice input: {voice}", message_type="debug_tts") + gpt_cond_latent = None + speaker_embedding = None + + # Handle different voice types + if voice.startswith('latent:'): + if self.current_model_loaded.startswith("xtts"): + gpt_cond_latent, speaker_embedding = self._load_latents(voice) + + elif voice.startswith('voiceset:'): + voice_set = voice.replace("voiceset:", "") + voice_set_path = os.path.join(self.main_dir, "voices", "xtts_multi_voice_sets", voice_set) + self.print_message(f"Processing voice set from: {voice_set_path}", message_type="debug_tts") + + wavs_files = glob.glob(os.path.join(voice_set_path, "*.wav")) + if not wavs_files: + self.print_message(f"No WAV files found in voice set: {voice_set}", message_type="error") + raise HTTPException(status_code=400, detail=f"No WAV files found in voice set: {voice_set}") + + if len(wavs_files) > 5: + wavs_files = random.sample(wavs_files, 5) + self.print_message(f"Using 5 random samples from voice set", message_type="debug_tts") + + if self.current_model_loaded.startswith("xtts"): + self.print_message("Generating conditioning latents from voice set", message_type="debug_tts") + gpt_cond_latent, speaker_embedding = self._generate_conditioning_latents(wavs_files) + + else: + normalized_path = os.path.normpath(os.path.join(self.main_dir, "voices", voice)) + wavs_files = [normalized_path] + self.print_message(f"Using single voice sample: {normalized_path}", message_type="debug_tts") + + if self.current_model_loaded.startswith("xtts"): + self.print_message("Generating conditioning latents from single sample", message_type="debug_tts") + gpt_cond_latent, speaker_embedding = self._generate_conditioning_latents(wavs_files) + + # Generate speech + if self.current_model_loaded.startswith("xtts"): + self.print_message(f"Generating speech for text: {text}", message_type="debug_tts") + + common_args = { + "text": text, + "language": language, + "gpt_cond_latent": gpt_cond_latent, + "speaker_embedding": speaker_embedding, + "temperature": float(temperature), + "length_penalty": float(self.model.config.length_penalty), + "repetition_penalty": float(repetition_penalty), + "top_k": int(self.model.config.top_k), + "top_p": float(self.model.config.top_p), + "speed": float(speed), + "enable_text_splitting": True + } + + self.print_message("Generation settings:", message_type="debug_tts_variables") + self.print_message(f"├─ Temperature: {temperature}", message_type="debug_tts_variables") + self.print_message(f"├─ Speed: {speed}", message_type="debug_tts_variables") + self.print_message(f"├─ Language: {language}", message_type="debug_tts_variables") + self.print_message(f"└─ Text length: {len(text)} characters", message_type="debug_tts_variables") + + # Handle streaming vs non-streaming + if streaming: + self.print_message("Starting streaming generation", message_type="debug_tts") + self.print_message(f"Using streaming-based generation and files {wavs_files}") + output = self.model.inference_stream(**common_args, stream_chunk_size=20) + + file_chunks = [] + wav_buf = io.BytesIO() + with wave.open(wav_buf, "wb") as vfout: + vfout.setnchannels(1) + vfout.setsampwidth(2) + vfout.setframerate(24000) + vfout.writeframes(b"") + wav_buf.seek(0) + yield wav_buf.read() + + for i, chunk in enumerate(output): + if self.tts_stop_generation: + self.print_message("Generation stopped by user", message_type="debug_tts") + self.tts_stop_generation = False + self.tts_generating_lock = False + break + + self.print_message(f"Processing chunk {i+1}", message_type="debug_tts") + file_chunks.append(chunk) + if isinstance(chunk, list): + chunk = torch.cat(chunk, dim=0) + chunk = chunk.clone().detach().cpu().numpy() + chunk = chunk[None, : int(chunk.shape[0])] + chunk = np.clip(chunk, -1, 1) + chunk = (chunk * 32767).astype(np.int16) + yield chunk.tobytes() + else: + self.print_message("Starting non-streaming generation", message_type="debug_tts") + output = self.model.inference(**common_args) + torchaudio.save(output_file, torch.tensor(output["wav"]).unsqueeze(0), 24000) + self.print_message(f"Saved audio to: {output_file}", message_type="debug_tts") + + elif self.current_model_loaded.startswith("apitts"): + if streaming: + raise ValueError("Streaming is only supported in XTTSv2 local mode") + # Common arguments for both error and normal cases + common_args = { + "file_path": output_file, + "language": language, + "temperature": temperature, + "length_penalty": self.model.config.length_penalty, + "repetition_penalty": repetition_penalty, + "top_k": self.model.config.top_k, + "top_p": self.model.config.top_p, + "speed": speed + } + if voice.startswith('latent:'): + self.print_message("API TTS method does not support latent files - Please use an audio reference file", message_type="error") + self.model.tts_to_file( + text="The API TTS method only supports audio files not latents. Please select an audio reference file instead.", + speaker="Ana Florence", + **common_args + ) + else: + self.print_message("Using API-based generation", message_type="debug_tts") + self.model.tts_to_file( + text=text, + speaker_wav=wavs_files, + **common_args + ) + + self.print_message(f"API generation completed, saved to: {output_file}", message_type="debug_tts") + + finally: + # Generation complete + generate_end_time = time.time() + generate_elapsed_time = generate_end_time - generate_start_time + + # Standard output message (not debug) + self.print_message( + f"\033[94mTTS Generate: \033[93m{generate_elapsed_time:.2f} seconds. \033[94mLowVRAM: \033[33m{self.lowvram_enabled} \033[94mDeepSpeed: \033[33m{self.deepspeed_enabled}\033[0m", + message_type="standard" + ) + + # Handle low VRAM cleanup + if self.lowvram_enabled and self.device == "cuda" and not self.tts_narrator_generatingtts: + self.print_message("Low VRAM mode: Moving model back to CPU", message_type="debug_tts") + await self.handle_lowvram_change() + + self.tts_generating_lock = False + + ############################################################################## + # Helper Functions that are specific to this script & not generically needed # + ############################################################################## + def _generate_conditioning_latents(self, audio_paths): + """Generate conditioning latents from audio files.""" + self.debug_func_entry() + self.print_message(f"Generating latents from {len(audio_paths)} audio files", message_type="debug_tts") + return self.model.get_conditioning_latents( + audio_path=audio_paths, + gpt_cond_len=self.model.config.gpt_cond_len, + max_ref_length=self.model.config.max_ref_len, + sound_norm_refs=self.model.config.sound_norm_refs, + ) + + def _load_latents(self, voice): + """Load speaker latents from JSON file.""" + self.debug_func_entry() + try: + json_file = voice.replace("latent:", "") + json_path = os.path.join(self.main_dir, "voices", "xtts_latents", json_file) + self.print_message(f"Loading latents from: {json_path}", message_type="debug_tts") + + with open(json_path) as f: + latent_data = json.load(f) + gpt_cond_latent = torch.tensor(latent_data['gpt_cond_latent']) + speaker_embedding = torch.tensor(latent_data['speaker_embedding']) + + self.print_message("Successfully loaded speaker latents", message_type="debug_tts") + return gpt_cond_latent, speaker_embedding + except Exception as e: + self.print_message(f"Failed to load speaker latents: {str(e)}", message_type="error") + raise HTTPException(status_code=400, detail=f"Failed to load voice latents: {str(e)}") diff --git a/system/tts_engines/xtts/model_settings.json b/system/tts_engines/xtts/model_settings.json index 5e909e5..b680cfa 100644 --- a/system/tts_engines/xtts/model_settings.json +++ b/system/tts_engines/xtts/model_settings.json @@ -1,42 +1,42 @@ -{ - "model_details": { - "manufacturer_name": "Coqui", - "manufacturer_website": "https://github.com/coqui-ai/TTS and also updated engine https://github.com/idiap/coqui-ai-TTS/", - "model_description": "XTTS is a multi-speaker, voice cloning model. XTTS-v2 by Coqui AI is a voice generation model that lets you clone voices into a multitude of languages by using just a mere 6+ second audio sample. It is licenced under Coqui AI's Coqui Public Model License 1.0.0 which allows for `non-commercial use of a machine learning model and its outputs.`" - }, - "model_capabilties": { - "audio_format": "wav", - "deepspeed_capable": true, - "generationspeed_capable": true, - "languages_capable": true, - "lowvram_capable": true, - "multimodel_capable": true, - "repetitionpenalty_capable": true, - "streaming_capable": true, - "temperature_capable": true, - "multivoice_capable": true, - "pitch_capable": false, - "windows_capable": true, - "linux_capable": true, - "mac_capable": true - }, - "settings": { - "def_character_voice": "female_01.wav", - "def_narrator_voice": "male_01.wav", - "deepspeed_enabled": false, - "engine_installed": true, - "generationspeed_set": 1, - "lowvram_enabled": false, - "pitch_set": 0, - "repetitionpenalty_set": 10, - "temperature_set": 0.75 - }, - "openai_voices": { - "alloy": "female_01.wav", - "echo": "female_01.wav", - "fable": "female_01.wav", - "nova": "female_01.wav", - "onyx": "female_01.wav", - "shimmer": "female_01.wav" - } +{ + "model_details": { + "manufacturer_name": "Coqui", + "manufacturer_website": "https://github.com/coqui-ai/TTS and also updated engine https://github.com/idiap/coqui-ai-TTS/", + "model_description": "XTTS is a multi-speaker, voice cloning model. XTTS-v2 by Coqui AI is a voice generation model that lets you clone voices into a multitude of languages by using just a mere 6+ second audio sample. It is licenced under Coqui AI's Coqui Public Model License 1.0.0 which allows for `non-commercial use of a machine learning model and its outputs.`" + }, + "model_capabilties": { + "audio_format": "wav", + "deepspeed_capable": true, + "generationspeed_capable": true, + "languages_capable": true, + "lowvram_capable": true, + "multimodel_capable": true, + "repetitionpenalty_capable": true, + "streaming_capable": true, + "temperature_capable": true, + "multivoice_capable": true, + "pitch_capable": false, + "windows_capable": true, + "linux_capable": true, + "mac_capable": true + }, + "settings": { + "def_character_voice": "female_01.wav", + "def_narrator_voice": "male_01.wav", + "deepspeed_enabled": false, + "engine_installed": true, + "generationspeed_set": 1, + "lowvram_enabled": false, + "pitch_set": 0, + "repetitionpenalty_set": 10, + "temperature_set": 0.75 + }, + "openai_voices": { + "alloy": "female_01.wav", + "echo": "female_01.wav", + "fable": "female_01.wav", + "nova": "female_01.wav", + "onyx": "female_01.wav", + "shimmer": "female_01.wav" + } } \ No newline at end of file diff --git a/system/tts_engines/xtts/xtts_settings_page.py b/system/tts_engines/xtts/xtts_settings_page.py index 5341372..cb3f7e7 100644 --- a/system/tts_engines/xtts/xtts_settings_page.py +++ b/system/tts_engines/xtts/xtts_settings_page.py @@ -4,6 +4,7 @@ import requests import gradio as gr from tqdm import tqdm from pathlib import Path +from .help_content import AllTalkHelpContent 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 @@ -128,11 +129,16 @@ def xtts_model_alltalk_settings(model_config_data): 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.") + details_text = gr.Textbox(label="Details", show_label=False, lines=5, interactive=False, value="Configure default settings and voice mappings for the selected TTS engine. Unavailable options are grayed out based on engine capabilities. See the Help section below for detailed information about each setting.") with gr.Row(): submit_button = gr.Button("Update Settings") output_message = gr.Textbox(label="Output Message", interactive=False, show_label=False) - + with gr.Accordion("HELP - 🔊 Understanding TTS Engine Default Settings Page", open=False): + with gr.Row(): + gr.Markdown(AllTalkHelpContent.DEFAULT_SETTINGS, elem_classes="custom-markdown") + with gr.Row(): + gr.Markdown(AllTalkHelpContent.DEFAULT_SETTINGS1, elem_classes="custom-markdown") + gr.Markdown(AllTalkHelpContent.DEFAULT_SETTINGS2, elem_classes="custom-markdown") 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) ########################################################################################### @@ -166,34 +172,12 @@ def xtts_model_alltalk_settings(model_config_data): 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.Accordion("HELP - 🔊 Understanding TTS Engine Capabilities", open=False): + with gr.Row(): + gr.Markdown(AllTalkHelpContent.ENGINE_INFORMATION, elem_classes="custom-markdown") + with gr.Row(): + gr.Markdown(AllTalkHelpContent.ENGINE_INFORMATION1, elem_classes="custom-markdown") + gr.Markdown(AllTalkHelpContent.ENGINE_INFORMATION2, elem_classes="custom-markdown") with gr.Tab("Models/Voices Download"): @@ -276,61 +260,11 @@ def xtts_model_alltalk_settings(model_config_data): ################################################################################################### 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. - - ### 🟧 Hindi Support on XTTS - Currently Hindi only works on XTTS model 2.0.3 and it has to be loaded as the API Local method. - """) - 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. - """) - + gr.Markdown(AllTalkHelpContent.HELP_PAGE, elem_classes="custom-markdown") + with gr.Row(): + gr.Markdown(AllTalkHelpContent.HELP_PAGE1, elem_classes="custom-markdown") + gr.Markdown(AllTalkHelpContent.HELP_PAGE2, elem_classes="custom-markdown") + return app ################################