diff --git a/.github/workflows/python-ci.yml b/.github/workflows/python-ci.yml
new file mode 100644
index 0000000..bbf05c2
--- /dev/null
+++ b/.github/workflows/python-ci.yml
@@ -0,0 +1,53 @@
+# This workflow will install Python dependencies, run tests and lint with a single version of Python
+# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python
+
+name: Python CI
+
+on: [push, pull_request]
+
+permissions:
+ contents: read
+
+jobs:
+ test:
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/checkout@v4
+ - name: Set up Python 3.11.9
+ uses: actions/setup-python@v3
+ if: ${{ hashFiles('./test') != '' }}
+ with:
+ python-version: "3.11.9"
+ - name: Install dependencies
+ if: ${{ hashFiles('./test') != '' }}
+ run: |
+ python -m pip install --upgrade pip
+ if [ -f system/requirements/requirements_unit_test.txt ]; then
+ pip install -r system/requirements/requirements_unit_test.txt;
+ fi
+ - name: Test with pytest
+ if: ${{ hashFiles('./test') != '' }}
+ run: |
+ pytest test --doctest-modules --junitxml=junit/test-results.xml --cov=com --cov-report=xml --cov-report=html
+ - name: Surface failing tests
+ if: always()
+ uses: pmeier/pytest-results-action@v0.6.0
+ with:
+ # A list of JUnit XML files, directories containing the former, and wildcard
+ # patterns to process.
+ # See @actions/glob for supported patterns.
+ path: junit/test-results.xml
+
+ # (Optional) Add a summary of the results at the top of the report
+ summary: true
+
+ # (Optional) Select which results should be included in the report.
+ # Follows the same syntax as `pytest -r`
+ display-options: fEX
+
+ # (Optional) Fail the workflow if no JUnit XML was found.
+ fail-on-empty: false
+
+ # (Optional) Title of the test results section in the workflow summary
+ title: Test results
\ No newline at end of file
diff --git a/atsetup.bat b/atsetup.bat
index 84f3ed9..6b32256 100644
--- a/atsetup.bat
+++ b/atsetup.bat
@@ -495,7 +495,7 @@ echo.
:update_gradio
echo ** Updating Gradio **
-pip install --upgrade gradio==4.32.2
+pip install --upgrade gradio==4.44.1
echo.
if errorlevel 1 (
echo Gradio update failed, errorlevel was %errorlevel%. There should be an error
diff --git a/atsetup.sh b/atsetup.sh
index 4b77ac3..4048be4 100644
--- a/atsetup.sh
+++ b/atsetup.sh
@@ -392,7 +392,7 @@ install_custom_standalone() {
echo
pip install -r system/requirements/requirements_standalone.txt
curl -LO https://github.com/erew123/alltalk_tts/releases/download/DeepSpeed-14.0/deepspeed-0.14.2+cu121torch2.2-cp311-cp311-manylinux_2_24_x86_64.whl
- pip install --upgrade gradio==4.32.2
+ pip install --upgrade gradio==4.44.1
echo Installing DeepSpeed...
pip install deepspeed-0.14.2+cu121torch2.2-cp311-cp311-manylinux_2_24_x86_64.whl
rm deepspeed-0.14.2+cu121torch2.2-cp311-cp311-manylinux_2_24_x86_64.whl
diff --git a/config.py b/config.py
new file mode 100644
index 0000000..905ed52
--- /dev/null
+++ b/config.py
@@ -0,0 +1,413 @@
+import os
+import json
+import time
+import inspect
+import shutil
+from pathlib import Path
+from filelock import FileLock
+from types import SimpleNamespace
+from dataclasses import dataclass
+from abc import ABC, abstractmethod
+from typing import Callable, Any, MutableSequence
+
+
+@dataclass
+class AlltalkConfigTheme:
+ file: str | None = None
+ clazz = ""
+
+@dataclass
+class AlltalkConfigRvcSettings:
+ rvc_enabled: bool = False
+ rvc_char_model_file: str = "Disabled"
+ rvc_narr_model_file: str = "Disabled"
+ split_audio: bool = True
+ autotune: bool = False
+ pitch: int = 0
+ filter_radius: int = 3
+ index_rate: float = 0.75
+ rms_mix_rate: int = 1
+ protect: float = 0.5
+ hop_length: int = 130
+ f0method: str = "fcpe"
+ embedder_model: str = "hubert"
+ training_data_size: int = 45000
+
+@dataclass
+class AlltalkConfigTgwUi:
+ tgwui_activate_tts: bool = True
+ tgwui_autoplay_tts: bool = True
+ tgwui_narrator_enabled: str = "false"
+ tgwui_non_quoted_text_is: str = "character"
+ tgwui_deepspeed_enabled: bool = False
+ tgwui_language: str = "English"
+ tgwui_lowvram_enabled: bool = False
+ tgwui_pitch_set: int = 0
+ tgwui_temperature_set: float = 0.75
+ tgwui_repetitionpenalty_set: int = 10
+ tgwui_generationspeed_set: int = 1
+ tgwui_narrator_voice: str = "female_01.wav"
+ tgwui_show_text: bool = True
+ tgwui_character_voice: str = "female_01.wav"
+ tgwui_rvc_char_voice: str = "Disabled"
+ tgwui_rvc_char_pitch: int = 0
+ tgwui_rvc_narr_voice: str = "Disabled"
+ tgwui_rvc_narr_pitch: int = 0
+
+@dataclass
+class AlltalkConfigApiDef:
+ api_port_number: int = 7851
+ api_allowed_filter: str = "[^a-zA-Z0-9\\s.,;:!?\\-\\'\"$\\u0400-\\u04FF\\u00C0-\\u017F\\u0150\\u0151\\u0170\\u0171\\u011E\\u011F\\u0130\\u0131\\u0900-\\u097F\\u2018\\u2019\\u201C\\u201D\\u3001\\u3002\\u3040-\\u309F\\u30A0-\\u30FF\\u4E00-\\u9FFF\\u3400-\\u4DBF\\uF900-\\uFAFF\\u0600-\\u06FF\\u0750-\\u077F\\uFB50-\\uFDFF\\uFE70-\\uFEFF\\uAC00-\\uD7A3\\u1100-\\u11FF\\u3130-\\u318F\\uFF01\\uFF0c\\uFF1A\\uFF1B\\uFF1F]"
+ api_length_stripping: int = 3
+ api_max_characters: int = 2000
+ api_use_legacy_api: bool = False
+ api_legacy_ip_address: str = "127.0.0.1"
+ api_text_filtering: str = "standard"
+ api_narrator_enabled: str = "false"
+ api_text_not_inside: str = "character"
+ api_language: str = "en"
+ api_output_file_name: str = "myoutputfile"
+ api_output_file_timestamp: bool = True
+ api_autoplay: bool = False
+ api_autoplay_volume: float = 0.5
+
+@dataclass
+class AlltalkConfigDebug:
+ debug_transcode: bool = False
+ debug_tts: bool = False
+ debug_openai: bool = False
+ debug_concat: bool = False
+ debug_tts_variables: bool = False
+ debug_rvc: bool = False
+ debug_func: bool = False
+ debug_api: bool = False
+ debug_fullttstext: bool = False
+ debug_narrator: bool = False
+ debug_gradio_IP: bool = False
+ debug_transcribe: bool = False
+
+@dataclass
+class AlltalkConfigGradioPages:
+ Generate_Help_page: bool = True
+ Voice2RVC_page: bool = True
+ TTS_Generator_page: bool = True
+ TTS_Engines_Settings_page: bool = True
+ alltalk_documentation_page: bool = True
+ api_documentation_page: bool = True
+
+
+@dataclass
+class AlltalkAvailableEngine:
+ name = ""
+ selected_model = ""
+
+
+class AbstractJsonConfig(ABC):
+
+ def __init__(self, config_path: Path | str, file_check_interval: int):
+ self.__config_path = Path(config_path) if type(config_path) is str else config_path
+ self.__last_read_time = 0 # Track when we last read the file
+ self.__file_check_interval = file_check_interval
+
+ def get_config_path(self):
+ return self.__config_path
+
+ def reload(self):
+ self._load_config()
+ return self
+
+ def to_dict(self):
+ # Remove private fields:
+ without_private_fields = {}
+ for attr, value in self.__dict__.items():
+ if not attr.startswith("_"):
+ without_private_fields[attr] = value
+ return without_private_fields
+
+ def _reload_on_change(self):
+ # Check if config file has been modified and reload if needed
+ if time.time() - self.__last_read_time >= self.__file_check_interval:
+ try:
+ most_recent_modification = self.get_config_path().stat().st_mtime
+ if most_recent_modification > self.__last_read_time:
+ self.reload()
+ except Exception as e:
+ print(f"Error checking config file: {e}")
+
+ def _load_config(self):
+ self.__last_read_time = self.get_config_path().stat().st_mtime
+ def __load():
+ with open(self.get_config_path(), "r") as configfile:
+ data = json.load(configfile, object_hook=self._object_hook())
+ self._handle_loaded_config(data)
+ self.__with_lock_and_backup(self.get_config_path(), False, __load)
+
+ def _object_hook(self) -> Callable[[dict[Any, Any]], Any] | None:
+ return lambda d: SimpleNamespace(**d)
+
+ def _save_file(self, path: Path | None | str, default=None, indent=4):
+ file_path = (Path(path) if type(path) is str else path) if path is not None else self.get_config_path()
+
+ def custom_default(o):
+ if isinstance(o, Path):
+ return str(o) # Convert Path objects to strings
+ elif hasattr(o, '__dict__'):
+ return o.__dict__ # Use the object's __dict__ if it exists
+ else:
+ raise TypeError(f"Object of type {type(o).__name__} is not JSON serializable")
+
+ default = default or custom_default
+
+ def __save():
+ with open(file_path, 'w') as file:
+ json.dump(self.to_dict(), file, indent=indent, default=default)
+
+ self.__with_lock_and_backup(file_path, True, __save)
+
+
+ def __with_lock_and_backup(self, path: Path, backup: bool, callable: Callable[[], None]):
+ lock_path = path.with_suffix('.lock')
+ backup_path = None
+ try:
+ with FileLock(lock_path):
+ # Create backup:
+ if path.exists() and backup:
+ backup_path = path.with_suffix('.backup')
+ shutil.copy(path, backup_path)
+
+ try:
+ callable()
+ except Exception as e:
+ if backup_path and backup_path.exists():
+ shutil.copy(backup_path, path)
+ raise Exception(f"Failed to save config: {e}")
+ finally:
+ # Cleanup lock and backup files:
+ if lock_path.exists(): # Only try to delete if it exists
+ try:
+ lock_path.unlink()
+ except FileNotFoundError:
+ pass # Ignore if file doesn't exist
+
+ if backup and backup_path and backup_path.exists():
+ try:
+ backup_path.unlink()
+ except FileNotFoundError:
+ pass # Ignore if file doesn't exist
+
+ @abstractmethod
+ def _handle_loaded_config(self, data):
+ pass
+
+class AlltalkNewEnginesConfig(AbstractJsonConfig):
+ __instance = None
+ __this_dir = Path(__file__).parent.resolve()
+
+ def __init__(self, config_path: Path | str = os.path.join(__this_dir, "system", "tts_engines", "new_engines.json")):
+ super().__init__(config_path, 5)
+ self.engines_available: MutableSequence[AlltalkAvailableEngine] = []
+ self._load_config()
+
+ def get_engine_names_available(self):
+ return [engine.name for engine in self.engines_available]
+
+ @staticmethod
+ def get_instance():
+ if AlltalkNewEnginesConfig.__instance is None:
+ AlltalkNewEnginesConfig.__instance = AlltalkNewEnginesConfig()
+ AlltalkNewEnginesConfig.__instance._reload_on_change()
+ return AlltalkNewEnginesConfig.__instance
+
+ def _handle_loaded_config(self, data):
+ self.engines_available = data.engines_available
+
+ def get_engines_matching(self, condition: Callable[[AlltalkAvailableEngine], bool]):
+ return [x for x in self.engines_available if condition(x)]
+
+
+class AlltalkTTSEnginesConfig(AbstractJsonConfig):
+ __instance = None
+ __this_dir = Path(__file__).parent.resolve()
+
+ def __init__(self, config_path: Path | str = os.path.join(__this_dir, "system", "tts_engines", "tts_engines.json")):
+ super().__init__(config_path, 5)
+ self.engines_available: MutableSequence[AlltalkAvailableEngine] = []
+ self.engine_loaded = ""
+ self.selected_model = ""
+ self._load_config()
+
+ def get_engine_names_available(self):
+ return [engine.name for engine in self.engines_available]
+
+ @staticmethod
+ def get_instance(force_reload = False):
+ if AlltalkTTSEnginesConfig.__instance is None:
+ force_reload = False
+ AlltalkTTSEnginesConfig.__instance = AlltalkTTSEnginesConfig()
+
+ if force_reload:
+ AlltalkTTSEnginesConfig.__instance.reload()
+ else:
+ AlltalkTTSEnginesConfig.__instance._reload_on_change()
+ return AlltalkTTSEnginesConfig.__instance
+
+ def _handle_loaded_config(self, data):
+ # List of the available TTS engines:
+ self.engines_available = self.__handle_loaded_config_engines(data)
+
+ # The currently set TTS engine from tts_engines.json
+ self.engine_loaded = data.engine_loaded
+ self.selected_model = data.selected_model
+
+ def __handle_loaded_config_engines(self, data):
+ available_engines = data.engines_available
+ available_engine_names = [engine.name for engine in available_engines]
+
+ # Getting the engines that are not already part of the available engines:
+ new_engines_config = AlltalkNewEnginesConfig.get_instance()
+ new_engines = new_engines_config.get_engines_matching(lambda eng: eng.name not in available_engine_names)
+
+ # Merge engines:
+ return available_engines + new_engines
+
+ def save(self, path: Path | str | None = None):
+ self._save_file(path)
+
+ def is_valid_engine(self, engine_name):
+ return engine_name in self.get_engine_names_available()
+
+ def change_engine(self, requested_engine):
+ if requested_engine == self.engine_loaded:
+ return self
+ for engine in self.engines_available:
+ if engine.name == requested_engine:
+ self.engine_loaded = requested_engine
+ self.selected_model = engine.selected_model
+ return self
+ return self
+
+@dataclass
+class AlltalkConfig(AbstractJsonConfig):
+ __instance = None
+ __this_dir = Path(__file__).parent.resolve()
+
+ def __init__(self, config_path: Path | str = __this_dir / "confignew.json"):
+ super().__init__(config_path, 5)
+ self.branding = ""
+ self.delete_output_wavs = ""
+ self.gradio_interface = False
+ self.output_folder = ""
+ self.gradio_port_number = 0
+ self.firstrun_model = False
+ self.firstrun_splash = False
+ self.launch_gradio = False
+ self.transcode_audio_format = ""
+ self.theme = AlltalkConfigTheme()
+ self.rvc_settings = AlltalkConfigRvcSettings()
+ self.tgwui = AlltalkConfigTgwUi()
+ self.api_def = AlltalkConfigApiDef()
+ self.debugging = AlltalkConfigDebug()
+ self.gradio_pages = AlltalkConfigGradioPages()
+ self._load_config()
+
+ @staticmethod
+ def default_config_path():
+ return AlltalkConfig.__this_dir / "confignew.json"
+
+ @staticmethod
+ def get_instance(force_reload = False):
+ if AlltalkConfig.__instance is None:
+ force_reload = False
+ AlltalkConfig.__instance = AlltalkConfig()
+
+ if force_reload:
+ AlltalkConfig.__instance.reload()
+ else:
+ AlltalkConfig.__instance._reload_on_change()
+ return AlltalkConfig.__instance
+
+ def get_output_directory(self):
+ return self.__this_dir / self.output_folder
+
+ def save(self, path: Path | str | None = None):
+ self._save_file(path)
+
+ def _handle_loaded_config(self, data):
+ from dataclasses import fields, is_dataclass, asdict
+ debug_me =False
+ if debug_me:
+ print("=== Loading Config ===")
+ print(f"Initial data state: {vars(data)}")
+
+ # Create new instances with defaults
+ default_instances = {
+ 'debugging': AlltalkConfigDebug(),
+ 'rvc_settings': AlltalkConfigRvcSettings(),
+ 'tgwui': AlltalkConfigTgwUi(),
+ 'api_def': AlltalkConfigApiDef(),
+ 'theme': AlltalkConfigTheme(),
+ 'gradio_pages': AlltalkConfigGradioPages()
+ }
+ if debug_me:
+ print("\nDefault values for each class:")
+ for name, instance in default_instances.items():
+ if debug_me:
+ print(f"{name}: {asdict(instance)}")
+ # Show actual default values from dataclass
+ print(f"Default values: {[(f.name, getattr(instance, f.name)) for f in fields(instance)]}")
+ if hasattr(data, name):
+ source = getattr(data, name)
+ if debug_me:
+ print(f"Source data: {vars(source) if hasattr(source, '__dict__') else source}")
+
+ for field in fields(instance):
+ if hasattr(source, field.name):
+ setattr(instance, field.name, getattr(source, field.name))
+ if debug_me:
+ print(f"Field {field.name}: {getattr(instance, field.name)}")
+
+ setattr(self, name, instance)
+
+ # Handle non-dataclass fields
+ for n, v in inspect.getmembers(data):
+ if hasattr(self, n) and not n.startswith("__") and not is_dataclass(type(getattr(self, n))):
+ setattr(self, n, v)
+
+ self.theme.clazz = data.theme.__dict__.get("class", data.theme.__dict__.get("clazz", ""))
+ self.get_output_directory().mkdir(parents=True, exist_ok=True)
+
+ def to_dict(self):
+ from dataclasses import is_dataclass, asdict
+ debug_me =False
+ if debug_me:
+ print("=== Converting to dict ===")
+ result = {}
+
+ for key, value in vars(self).items():
+ if not key.startswith('_'):
+ # print(f"\nProcessing {key}:")
+ if is_dataclass(value):
+ # print(f"Dataclass value before conversion: {vars(value)}")
+ result[key] = asdict(value)
+ # print(f"Converted to dict: {result[key]}")
+ elif isinstance(value, SimpleNamespace):
+ # print(f"SimpleNamespace value: {value.__dict__}")
+ result[key] = value.__dict__
+ else:
+ # print(f"Regular value: {value}")
+ result[key] = value
+
+ if 'theme' in result:
+ if debug_me:
+ print("\nProcessing theme:")
+ print(f"Before class handling: {result['theme']}")
+ result['theme']['class'] = self.theme.clazz
+ result['theme'].pop('clazz', None)
+ if debug_me:
+ print(f"After class handling: {result['theme']}")
+ if debug_me:
+ print(f"\nFinal dict: {result}")
+ return result
+
+
+
diff --git a/finetune.py b/finetune.py
index d2240f1..3eb3217 100644
--- a/finetune.py
+++ b/finetune.py
@@ -889,20 +889,20 @@ def create_pfc_interface():
with gr.Row():
with gr.Column():
with gr.Accordion("💻 Hardware Requirements", open=False):
- gr.Markdown(HELP_CONTENT["gpu_requirements"])
+ gr.Markdown(HELP_CONTENT["gpu_requirements"], elem_classes="custom-markdown")
with gr.Column():
with gr.Accordion("🖥️ Storage Requirements", open=False):
- gr.Markdown(HELP_CONTENT["disk_space_details"])
+ gr.Markdown(HELP_CONTENT["disk_space_details"], elem_classes="custom-markdown")
with gr.Row():
with gr.Column():
with gr.Accordion("🤖 PyTorch & CUDA", open=False):
- gr.Markdown(HELP_CONTENT["pytorch_cuda"])
+ gr.Markdown(HELP_CONTENT["pytorch_cuda"], elem_classes="custom-markdown")
with gr.Column():
with gr.Accordion("📦 Model Setup", open=False):
- gr.Markdown(HELP_CONTENT["model_setup"])
+ gr.Markdown(HELP_CONTENT["model_setup"], elem_classes="custom-markdown")
def run_checks():
system_checks.run_all_checks()
@@ -3925,25 +3925,25 @@ if __name__ == "__main__":
)
with gr.Accordion("🎯 Quick Start Guide", open=False):
gr.Markdown(
- FinetuneContent.STEP1_QUICKSTART) # pylint: disable=no-member
+ FinetuneContent.STEP1_QUICKSTART, elem_classes="custom-markdown") # pylint: disable=no-member
with gr.Accordion("📋 Detailed Instructions", open=False):
gr.Markdown(
- FinetuneContent.STEP1_DETAILED_INSTRUCTIONS) # pylint: disable=no-member
+ FinetuneContent.STEP1_DETAILED_INSTRUCTIONS, elem_classes="custom-markdown") # pylint: disable=no-member
with gr.Accordion("🔧 Process Overview", open=False):
gr.Markdown(
- FinetuneContent.STEP1_PROCESS_OVERVIEW) # pylint: disable=no-member
+ FinetuneContent.STEP1_PROCESS_OVERVIEW, elem_classes="custom-markdown") # pylint: disable=no-member
with gr.Accordion("🔍 Whisper Model Selection", open=False):
gr.Markdown(
- FinetuneContent.STEP1_WHISPER_MODEL_SELECTION) # pylint: disable=no-member
+ FinetuneContent.STEP1_WHISPER_MODEL_SELECTION, elem_classes="custom-markdown") # pylint: disable=no-member
with gr.Accordion("⚙️ Advanced Settings", open=False):
gr.Markdown(
- FinetuneContent.STEP1_ADVANCED_SETTINGS) # pylint: disable=no-member
+ FinetuneContent.STEP1_ADVANCED_SETTINGS, elem_classes="custom-markdown") # pylint: disable=no-member
with gr.Accordion("🔍 Dataset Creation Debug Settings", open=False):
gr.Markdown(
- FinetuneContent.STEP1_DEBUG_SETTINGS) # pylint: disable=no-member
+ FinetuneContent.STEP1_DEBUG_SETTINGS, elem_classes="custom-markdown") # pylint: disable=no-member
with gr.Accordion("❗ Troubleshooting", open=False):
gr.Markdown(
- FinetuneContent.STEP1_TROUBLESHOOTING) # pylint: disable=no-member
+ FinetuneContent.STEP1_TROUBLESHOOTING, elem_classes="custom-markdown") # pylint: disable=no-member
with gr.TabItem("📊 Dataset Validation"):
with gr.Row():
@@ -3953,10 +3953,10 @@ if __name__ == "__main__":
with gr.Row():
with gr.Column():
gr.Markdown(
- FinetuneContent.DATASET_VALIDATION_1) # pylint: disable=no-member
+ FinetuneContent.DATASET_VALIDATION_1, elem_classes="custom-markdown") # pylint: disable=no-member
with gr.Column():
gr.Markdown(
- FinetuneContent.DATASET_VALIDATION_2) # pylint: disable=no-member
+ FinetuneContent.DATASET_VALIDATION_2, elem_classes="custom-markdown") # pylint: disable=no-member
with gr.Row():
progress_box = gr.Textbox(label="Progress", interactive=False)
@@ -4456,31 +4456,31 @@ if __name__ == "__main__":
)
with gr.Accordion("🎯 Quick Start Training Guide", open=False):
gr.Markdown(
- FinetuneContent.STEP2_QUICKSTART) # pylint: disable=no-member
+ FinetuneContent.STEP2_QUICKSTART, elem_classes="custom-markdown") # pylint: disable=no-member
with gr.Accordion("📁 Using Your Own Training Dataset", open=False):
gr.Markdown(
- FinetuneContent.STEP2_YOUR_OWN_DATASET) # pylint: disable=no-member
+ FinetuneContent.STEP2_YOUR_OWN_DATASET, elem_classes="custom-markdown") # pylint: disable=no-member
with gr.Accordion("📊 Training Metrics and Logs", open=False):
gr.Markdown(
- FinetuneContent.STEP2_TRAINING_METRICS) # pylint: disable=no-member
+ FinetuneContent.STEP2_TRAINING_METRICS, elem_classes="custom-markdown") # pylint: disable=no-member
with gr.Accordion("💾 Memory Management", open=False):
gr.Markdown(
- FinetuneContent.STEP2_MEMORY_MANAGEMENT) # pylint: disable=no-member
+ FinetuneContent.STEP2_MEMORY_MANAGEMENT, elem_classes="custom-markdown") # pylint: disable=no-member
with gr.Accordion("⚙️ Batch Size & Gradient Accumulation", open=False):
gr.Markdown(
- FinetuneContent.STEP2_BATCH_SIZE) # pylint: disable=no-member
+ FinetuneContent.STEP2_BATCH_SIZE, elem_classes="custom-markdown") # pylint: disable=no-member
with gr.Accordion("📊 Learning Rate & Schedulers", open=False):
gr.Markdown(
- FinetuneContent.STEP2_LEARNING_RATE) # pylint: disable=no-member
+ FinetuneContent.STEP2_LEARNING_RATE, elem_classes="custom-markdown") # pylint: disable=no-member
with gr.Accordion("🔧 Optimizers", open=False):
gr.Markdown(
- FinetuneContent.STEP2_OPTIMIZERS) # pylint: disable=no-member
+ FinetuneContent.STEP2_OPTIMIZERS, elem_classes="custom-markdown") # pylint: disable=no-member
with gr.Accordion("🔄 Training Epochs", open=False):
gr.Markdown(
- FinetuneContent.STEP2_EPOCHS) # pylint: disable=no-member
+ FinetuneContent.STEP2_EPOCHS, elem_classes="custom-markdown") # pylint: disable=no-member
with gr.Accordion("📈 Max Audio Length", open=False):
gr.Markdown(
- FinetuneContent.STEP2_AUDIO_LENGTH) # pylint: disable=no-member
+ FinetuneContent.STEP2_AUDIO_LENGTH, elem_classes="custom-markdown") # pylint: disable=no-member
#######################
#### GRADIO STEP 3 ####
@@ -4625,30 +4625,30 @@ if __name__ == "__main__":
)
with gr.Accordion("🎯 Testing Overview", open=False):
gr.Markdown(
- FinetuneContent.STEP3_TESTING_OVERVIEW) # pylint: disable=no-member
+ FinetuneContent.STEP3_TESTING_OVERVIEW, elem_classes="custom-markdown") # pylint: disable=no-member
with gr.Accordion("⚠️ Important Notes", open=False):
gr.Markdown(
- FinetuneContent.STEP3_IMPORTANT) # pylint: disable=no-member
+ FinetuneContent.STEP3_IMPORTANT, elem_classes="custom-markdown") # pylint: disable=no-member
with gr.Accordion("📝 Testing Instructions", open=False):
gr.Markdown(
- FinetuneContent.STEP3_INSTRUCTIONS) # pylint: disable=no-member
+ FinetuneContent.STEP3_INSTRUCTIONS, elem_classes="custom-markdown") # pylint: disable=no-member
with gr.Accordion("🔍 What the Testing Step Does", open=False):
gr.Markdown(
- FinetuneContent.STEP3_WHAT_IT_DOES) # pylint: disable=no-member
+ FinetuneContent.STEP3_WHAT_IT_DOES, elem_classes="custom-markdown") # pylint: disable=no-member
with gr.Tab("📦 Model Export"):
with gr.Accordion("🎯 Export Overview", open=False):
gr.Markdown(
- FinetuneContent.EXPORT_OVERVIEW) # pylint: disable=no-member
+ FinetuneContent.EXPORT_OVERVIEW, elem_classes="custom-markdown") # pylint: disable=no-member
with gr.Accordion("📊 Voice Sample Organization", open=False):
gr.Markdown(
- FinetuneContent.EXPORT_VOICE_SAMPLES) # pylint: disable=no-member
+ FinetuneContent.EXPORT_VOICE_SAMPLES, elem_classes="custom-markdown") # pylint: disable=no-member
with gr.Accordion("🔄 Export Options", open=False):
gr.Markdown(
- FinetuneContent.EXPORT_OPTIONS) # pylint: disable=no-member
+ FinetuneContent.EXPORT_OPTIONS, elem_classes="custom-markdown") # pylint: disable=no-member
with gr.Accordion("💾 Storage Management", open=False):
gr.Markdown(
- FinetuneContent.EXPORT_STORAGE) # pylint: disable=no-member
+ FinetuneContent.EXPORT_STORAGE, elem_classes="custom-markdown") # pylint: disable=no-member
final_progress_data = gr.Label(label="Progress:")
with gr.Row():
diff --git a/pytest.ini b/pytest.ini
new file mode 100644
index 0000000..a635c5c
--- /dev/null
+++ b/pytest.ini
@@ -0,0 +1,2 @@
+[pytest]
+pythonpath = .
diff --git a/script.py b/script.py
index c4bbf0f..56e0bdc 100644
--- a/script.py
+++ b/script.py
@@ -1,231 +1,689 @@
-import os
-import re
-import sys
-import json
-import time
-import shutil
-import random
+"""
+Alltalk V2: A Comprehensive TTS and ASR Framework with WebUI Integration
+
+Github: https://github.com/erew123/
+
+This script serves as the core for the Alltalk V2 system, a robust framework combining
+Text-to-Speech (TTS) and Automatic Speech Recognition (ASR) capabilities. It is highly
+configurable and integrates with Text-Generation-WebUI (TGWUI) while also supporting
+standalone deployments. The script initializes configurations, handles subprocess
+management, enables API interactions, and provides transcription utilities.
+
+Key Features:
+-------------
+1. **TTS Engine Management**:
+ - Dynamically load and swap between TTS engines and models.
+ - Manage engine-specific configurations and optimize for GPU or CPU usage.
+
+2. **ASR/Transcription Utilities**:
+ - Integrates Whisper for audio-to-text transcription.
+ - Supports batch processing, live dictation, and various output formats (JSON, TXT, SRT).
+
+3. **WebUI and API Integration**:
+ - Extends Text-Generation-WebUI for seamless integration with conversational AI.
+ - Provides a Gradio-based interface for managing settings and TTS generation.
+
+4. **Enhanced User Configurations**:
+ - Centralized configuration loader for system-wide consistency.
+ - Handles first-time setups and runtime environment detection (Docker, Google Colab, etc.).
+
+5. **Audio File Processing**:
+ - Supports multiple audio formats with validation, pre-processing (noise reduction, bandpass filtering),
+ and file output management.
+ - Automatically organizes transcription files and generates metadata summaries.
+
+6. **Real-Time Feedback and Debugging**:
+ - Extensive debug logging for monitoring function entries, file operations, and network requests.
+ - Progress tracking for long-running tasks like model loading and transcription.
+
+
+Requirements:
+-------------
+- Python 3.8 or later.
+- Dependencies listed in the repository's requirements.txt.
+- Whisper ASR, PyTorch, Gradio, and other specified modules.
+
+Supported Environments:
+-----------------------
+- Google Colab
+- Docker
+- Text-Generation-WebUI (TGWUI)
+- Standalone mode
+"""
import atexit
-import signal
-import logging
-import requests
+import gc
+import inspect
+import importlib
+import json
+import mimetypes
+import os
import platform
+import shutil
+import signal as system_signal
import subprocess
+import sys
import threading
-import soundfile as sf
-from pathlib import Path
+import time
+import warnings
+import zipfile
from datetime import datetime, timedelta
-from requests.exceptions import RequestException, ConnectionError
+from pathlib import Path
+import gradio as gr
+import numpy as np
+import requests
+from requests.exceptions import ConnectionError as RequestsConnectionError
+from requests.exceptions import RequestException
+from tqdm import tqdm
+import torch
+try:
+ import whisper
+ import soundfile as sf
+ import plotly.graph_objects as go
+ from scipy import signal as scipy_signal
+except ImportError as e:
+ print(f"Error: {e}")
+ print("=" * 50)
+ print("🚨 Missing Dependencies Detected 🚨\n")
+ print(
+ "It seems you are missing some required packages.\n"
+ "To resolve this, please install the dependencies by running:\n"
+ )
+ print(" 🔹 pip install -r system/requirements/requirements_standalone.txt")
+ print(" 🔹 or use the 'atsetup' command\n")
+ print("Once the installation is complete, try running the script again.")
+ print("=" * 50)
+ sys.exit(1) # Exit the script to avoid further errors
+this_dir = Path(__file__).parent.resolve()
+
+# Note: The following function names are reserved for TGWUI integration.
+# When running under text-generation-webui, these functions will be imported from
+# system/TGWUI Extension/script.py when in TGWUI's Python environment/extensions dir.
+#
+# Reserved names:
+# - output_modifier
+# - input_modifier
+# - state_modifier
+# - ui
+# - history_modifier
+# - remove_tts_from_history
+# - toggle_text_in_history
+
+TGWUI_AVAILABLE = False
+
+# pylint: disable=import-outside-toplevel
+def output_modifier(string, state):
+ """Modify chat output (required for TGWUI)"""
+ try:
+ from .system.TGWUI_Extension.script import (
+ output_modifier as tgwui_output_modifier,
+ )
+ except ImportError:
+ from system.TGWUI_Extension.script import (
+ output_modifier as tgwui_output_modifier,
+ )
+ return tgwui_output_modifier(string, state)
+
+
+def input_modifier(string, state):
+ """Modify chat input (required for TGWUI)"""
+ try:
+ from .system.TGWUI_Extension.script import (
+ input_modifier as tgwui_input_modifier,
+ )
+ except ImportError:
+ from system.TGWUI_Extension.script import input_modifier as tgwui_input_modifier
+ return tgwui_input_modifier(string, state)
+
+
+def state_modifier(state):
+ """Modify chat state (required for TGWUI)"""
+ try:
+ from .system.TGWUI_Extension.script import (
+ state_modifier as tgwui_state_modifier,
+ )
+ except ImportError:
+ from system.TGWUI_Extension.script import state_modifier as tgwui_state_modifier
+ return tgwui_state_modifier(state)
+
+
+def ui():
+ """Create extension UI (required for TGWUI)"""
+ try:
+ from system.TGWUI_Extension.script import ui as tgwui_ui
+
+ return tgwui_ui()
+ except ImportError:
+ # Return empty interface if not in TGWUI
+ return gr.Blocks()
+
+
+def history_modifier(history):
+ """Modify chat history (required for TGWUI)"""
+ try:
+ from .system.TGWUI_Extension.script import (
+ history_modifier as tgwui_history_modifier,
+ )
+ except ImportError:
+ from system.TGWUI_Extension.script import (
+ history_modifier as tgwui_history_modifier,
+ )
+ return tgwui_history_modifier(history)
+
+
+def remove_tts_from_history(history):
+ """Remove TTS from history (required for TGWUI)"""
+ try:
+ from .system.TGWUI_Extension.script import (
+ remove_tts_from_history as tgwui_remove_tts,
+ )
+ except ImportError:
+ from system.TGWUI_Extension.script import (
+ remove_tts_from_history as tgwui_remove_tts,
+ )
+ return tgwui_remove_tts(history)
+
+
+def toggle_text_in_history(history):
+ """Toggle text in history (required for TGWUI)"""
+ try:
+ from .system.TGWUI_Extension.script import (
+ toggle_text_in_history as tgwui_toggle_text,
+ )
+ except ImportError:
+ from system.TGWUI_Extension.script import (
+ toggle_text_in_history as tgwui_toggle_text,
+ )
+ return tgwui_toggle_text(history)
+
+
+# pylint: disable=ungrouped-imports,unused-import,import-outside-toplevel
+
+try:
+ from modules import chat, shared, ui_chat
+
+ TGWUI_AVAILABLE = True
+except ImportError:
+
+ class DummyShared: # pylint: disable=too-few-public-methods
+ "fake class relating to how we import or dont import TGWUI's remote extension"
+ processing_message = ""
+
+ class DummyState: # pylint: disable=too-few-public-methods
+ "fake class relating to how we import or dont import TGWUI's remote extension"
+ def __init__(self):
+ self.mode = "chat" # Add default mode
+
+ shared = DummyShared()
+
+# pylint: enable=ungrouped-imports,unused-import,import-outside-toplevel
+
+#########################
+# Central config loader #
+#########################
+# Confguration file management for confignew.json
+try:
+ from .config import (
+ AlltalkConfig,
+ AlltalkTTSEnginesConfig,
+ AlltalkNewEnginesConfig,
+ ) # TGWUI import
+ from .system.gradio_pages.help_content import AllTalkHelpContent
+ from .system.gradio_pages.alltalk_diskspace import get_disk_interface
+except ImportError:
+ from config import (
+ AlltalkConfig,
+ AlltalkTTSEnginesConfig,
+ AlltalkNewEnginesConfig,
+ ) # Standalone import
+ from system.gradio_pages.help_content import AllTalkHelpContent
+ from system.gradio_pages.alltalk_diskspace import get_disk_interface
+
+
+def initialize_configs():
+ """Initialize all configuration instances"""
+ config_initalize = AlltalkConfig.get_instance()
+ tts_engines_config_initalize = AlltalkTTSEnginesConfig.get_instance()
+ new_engines_config_initalize = AlltalkNewEnginesConfig.get_instance()
+ return config_initalize, tts_engines_config_initalize, new_engines_config_initalize
+
+# pylint: enable=import-outside-toplevel
+
+# Load in configs
+config, tts_engines_config, new_engines_config = initialize_configs()
+config.save() # Force the config file to save in case it was missing new any settings
+
+#########################################
+# START-UP # State Dictionary (GLOABLS) #
+#########################################
+_state = {
+ 'process': None,
+ 'running_on_google_colab': False, # Are we running on a Google Colab Server?
+ 'tunnel_url_1': None, # Used for Google Colab and finding the tunnel URL for API address
+ 'tunnel_url_2': None, # Used for Google Colab and finding the tunnel URL for Gradio address
+ 'running_in_docker': False, # Are we running on a Docker Server?
+ 'docker_url': f"http://localhost:{config.api_def.api_port_number}", # The inital URL used by docker for API communication in gradio
+ 'alltalk_protocol': "http://", # HTTP is always used, bar Docker or Google Colab. Can be configured here though
+ 'alltalk_ip_port': f"127.0.0.1:{config.api_def.api_port_number}", # IP/Port used for Docker, Google Colab or default
+ 'my_current_url': "null",
+ 'srv_models_available': None, # The current models available for the current TTS engine, pulled from the tts_server's API
+ 'srv_current_model_loaded': None, # The current model loaded, Initally loaded from central config and later pulled from the tts_server's API
+ 'srv_engines_available': None, # The current TTS engines available, Initally loaded from central config and later pulled from the tts_server's API
+ 'srv_current_engine_loaded': None, # The current TTS engines loaded in, Initally loaded from central config and later pulled from the tts_server's API
+ 'gradio_languages_list': None, # The list of languauges disaplyed in the Gradio interface.
+ 'whisper_model': None, # Used to track the whisper model used for dictation etc
+}
+
+############################################################
+# START-UP # Populate _state Engine info - Gradio Needs it #
+############################################################
+def initialize_engine_state(_state):
+ """Initialize engine state from central config"""
+ _state['srv_current_engine_loaded'] = tts_engines_config.engine_loaded
+ _state['srv_engines_available'] = tts_engines_config.get_engine_names_available()
+ _state['srv_currnet_model_loaded'] = tts_engines_config.selected_model
+ return _state
+
+# Call this after creating _state
+_state = initialize_engine_state(_state)
+
+##################################################################
+# START-UP # Populate Language Options Choices - Gradio Needs it #
+##################################################################
+with open(os.path.join(this_dir, "system", "config", "languages.json"), "r", encoding="utf-8") as f:
+ _state['gradio_languages_list'] = json.load(f)
+
+##########################
+# Central print function #
+##########################
+def print_message(message, message_type="standard", component="TTS"):
+ """Centralized print function for AllTalk messages
+ 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.)
+ """
+ # 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} script.py"
+ )
+ 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():
+ """Print debug message for function entry if debug_func is enabled"""
+ if config.debugging.debug_func:
+ current_func = inspect.currentframe().f_back.f_code.co_name
+ print_message(f"Function entry: {current_func}", "debug_func")
+
+
+###########################
+# Central config updaters #
+###########################
+def update_settings_at(
+ delete_output_wavs,
+ gradio_interface,
+ gradio_port_number,
+ upd_output_folder,
+ api_port_number,
+ gr_debug_tts,
+ transcode_audio_format,
+ generate_help_page,
+ voice2rvc_page,
+ tts_generator_page,
+ tts_engines_settings_page,
+ alltalk_documentation_page,
+ api_documentation_page,
+):
+ """Update AllTalk main settings using the centralized config system"""
+ debug_func_entry()
+ try:
+ # Get the current config instance
+ upd_set_config = AlltalkConfig.get_instance()
+
+ # Update main settings
+ upd_set_config.delete_output_wavs = delete_output_wavs
+ upd_set_config.gradio_interface = gradio_interface == "Enabled"
+ upd_set_config.output_folder = upd_output_folder
+ upd_set_config.api_def.api_port_number = api_port_number
+ upd_set_config.gradio_port_number = gradio_port_number
+ upd_set_config.transcode_audio_format = transcode_audio_format
+
+ # Update debugging options
+ for key in vars(upd_set_config.debugging):
+ if not key.startswith("_"): # Skip private attributes
+ setattr(upd_set_config.debugging, key, key in gr_debug_tts)
+
+ # Update gradio pages settings
+ upd_set_config.gradio_pages.Generate_Help_page = generate_help_page
+ upd_set_config.gradio_pages.Voice2RVC_page = voice2rvc_page
+ upd_set_config.gradio_pages.TTS_Generator_page = tts_generator_page
+ upd_set_config.gradio_pages.TTS_Engines_Settings_page = tts_engines_settings_page
+ upd_set_config.gradio_pages.alltalk_documentation_page = alltalk_documentation_page
+ upd_set_config.gradio_pages.api_documentation_page = api_documentation_page
+
+ # Save the updated configuration
+ upd_set_config.save()
+ # Tell tts_server.py to update
+ get_alltalk_settings()
+
+ print_message("Default Settings Saved")
+ return "Settings updated successfully!"
+ except (AttributeError, TypeError) as e:
+ print_message(
+ f"Configuration structure error: {str(e)}",
+ message_type="error"
+ )
+ return "Error updating settings: Invalid configuration structure"
+ except (OSError, IOError) as e:
+ print_message(
+ f"File system error while saving configuration: {str(e)}",
+ message_type="error"
+ )
+ return "Error saving settings: File system error"
+ except ValueError as e:
+ print_message(
+ f"Invalid value provided for configuration: {str(e)}",
+ message_type="error"
+ )
+ return "Error updating settings: Invalid value provided"
+
+
+def update_settings_api(
+ api_length_stripping,
+ api_legacy_ip_address,
+ api_allowed_filter,
+ api_max_characters,
+ api_use_legacy_api,
+ api_text_filtering,
+ api_narrator_enabled,
+ api_text_not_inside,
+ api_language,
+ api_output_file_name,
+ api_output_file_timestamp,
+ api_autoplay,
+ api_autoplay_volume,
+):
+ """Update API settings using the centralized config system"""
+ debug_func_entry()
+ try:
+ # Get the current config instance
+ upd_config = AlltalkConfig.get_instance()
+
+ # Update API settings
+ upd_config.api_def.api_length_stripping = api_length_stripping
+ upd_config.api_def.api_legacy_ip_address = api_legacy_ip_address
+ upd_config.api_def.api_allowed_filter = api_allowed_filter
+ upd_config.api_def.api_max_characters = api_max_characters
+ upd_config.api_def.api_use_legacy_api = (
+ api_use_legacy_api == "AllTalk v1 API (Legacy)"
+ )
+ upd_config.api_def.api_text_filtering = api_text_filtering
+ upd_config.api_def.api_narrator_enabled = api_narrator_enabled
+ upd_config.api_def.api_text_not_inside = api_text_not_inside
+ upd_config.api_def.api_language = api_language
+ upd_config.api_def.api_output_file_name = api_output_file_name
+ upd_config.api_def.api_output_file_timestamp = (
+ api_output_file_timestamp == "Timestamp files"
+ )
+ upd_config.api_def.api_autoplay = api_autoplay == "Play remotely"
+ upd_config.api_def.api_autoplay_volume = api_autoplay_volume
+
+ # Save the updated configuration
+ upd_config.save()
+ # Tell tts_server.py to update
+ get_alltalk_settings()
+
+ print_message("API Settings Saved")
+ return "Default API settings updated successfully!"
+ except (AttributeError, TypeError) as e:
+ print_message(
+ f"Configuration structure error: {str(e)}",
+ message_type="error"
+ )
+ return "Error updating settings: Invalid configuration structure"
+ except (OSError, IOError) as e:
+ print_message(
+ f"File system error while saving configuration: {str(e)}",
+ message_type="error"
+ )
+ return "Error saving settings: File system error"
+ except ValueError as e:
+ print_message(
+ f"Invalid value provided for configuration: {str(e)}",
+ message_type="error"
+ )
+ return "Error updating settings: Invalid value provided"
+
+
+def update_settings_tgwui(activate, autoplay, show_text, language, narrator_enabled):
+ """Update Text-gen-webui settings using the centralized config system"""
+ debug_func_entry()
+ try:
+ # Get the current config instance
+ tgwui_config = AlltalkConfig.get_instance()
+
+ # Update TGWUI settings
+ tgwui_config.tgwui.tgwui_activate_tts = activate == "Enabled"
+ tgwui_config.tgwui.tgwui_autoplay_tts = autoplay == "Enabled"
+ tgwui_config.tgwui.tgwui_show_text = show_text == "Enabled"
+ tgwui_config.tgwui.tgwui_language = language
+ tgwui_config.tgwui.tgwui_narrator_enabled = narrator_enabled
+
+ # Save the updated configuration
+ tgwui_config.save()
+
+ print_message("Default Text-gen-webui Settings Saved")
+ return "Settings updated successfully!"
+ except (AttributeError, TypeError) as e:
+ error_msg = f"Configuration structure error: {str(e)}"
+ return_message = "Error updating settings: Invalid configuration structure"
+ except (OSError, IOError) as e:
+ error_msg = f"File system error while saving configuration: {str(e)}"
+ return_message = "Error saving settings: File system error"
+ except ValueError as e:
+ error_msg = f"Invalid value provided for configuration: {str(e)}"
+ return_message = "Error updating settings: Invalid value provided"
+
+ print_message(error_msg, message_type="error")
+ return return_message
+
+
+def update_rvc_settings(
+ rvc_enabled,
+ rvc_char_model_file,
+ rvc_narr_model_file,
+ split_audio,
+ autotune,
+ pitch,
+ filter_radius,
+ index_rate,
+ rms_mix_rate,
+ protect,
+ hop_length,
+ f0method,
+ embedder_model,
+ training_data_size,
+ progress=None,
+):
+ """Update RVC settings using the centralized config system"""
+ debug_func_entry()
+
+ try:
+ # Get the current config instance
+ rvc_set_config = AlltalkConfig.get_instance()
+
+ # Update RVC settings
+ rvc_set_config.rvc_settings.rvc_enabled = rvc_enabled
+ rvc_set_config.rvc_settings.rvc_char_model_file = rvc_char_model_file
+ rvc_set_config.rvc_settings.rvc_narr_model_file = rvc_narr_model_file
+ rvc_set_config.rvc_settings.split_audio = split_audio
+ rvc_set_config.rvc_settings.autotune = autotune
+ rvc_set_config.rvc_settings.pitch = pitch
+ rvc_set_config.rvc_settings.filter_radius = filter_radius
+ rvc_set_config.rvc_settings.index_rate = index_rate
+ rvc_set_config.rvc_settings.rms_mix_rate = rms_mix_rate
+ rvc_set_config.rvc_settings.protect = protect
+ rvc_set_config.rvc_settings.hop_length = hop_length
+ rvc_set_config.rvc_settings.f0method = f0method
+ rvc_set_config.rvc_settings.embedder_model = embedder_model
+ rvc_set_config.rvc_settings.training_data_size = training_data_size
+
+ def ensure_directory_exists(directory):
+ """Confirm a directory exists"""
+ if not os.path.exists(directory):
+ os.makedirs(directory)
+
+ def load_file_urls(json_path):
+ """Load and return JSON data from specified file path."""
+ with open(json_path, "r", encoding="utf-8") as json_file:
+ return json.load(json_file)
+
+ def download_file(useurl, dest_path):
+ """
+ Download file from URL and save to specified path.
+
+ Args:
+ url (str): URL to download from
+ dest_path (str): Path where file will be saved
+ """
+ # First number (5) = connection timeout (time to establish connection)
+ # Second number (30) = read timeout (time between bytes received)
+ file_response = requests.get(useurl, stream=True, timeout=(5, 30))
+ file_response.raise_for_status()
+ with open(dest_path, "wb") as downloaded_file:
+ for chunk in file_response.iter_content(chunk_size=8192):
+ downloaded_file.write(chunk)
+
+ if rvc_enabled:
+ base_dir = os.path.join(this_dir, "models", "rvc_base")
+ rvc_voices_dir = os.path.join(this_dir, "models", "rvc_voices")
+ ensure_directory_exists(base_dir)
+ ensure_directory_exists(rvc_voices_dir)
+ json_path = os.path.join(
+ this_dir, "system", "tts_engines", "rvc_files.json"
+ )
+ file_urls = load_file_urls(json_path)
+ for idx, file in enumerate(file_urls):
+ if not os.path.exists(os.path.join(base_dir, file)):
+ progress(
+ idx / len(file_urls),
+ desc=f"Downloading Required RVC Files: {file}...",
+ )
+ print(
+ f"[{config.branding}TTS] Downloading {file}..."
+ ) # Print statement for terminal
+ download_file(file_urls[file], os.path.join(base_dir, file))
+ download_result = (
+ "All RVC Base Files are present."
+ if len(file_urls) > 0
+ else "All files are present."
+ )
+ print_message(download_result)
+
+ # Save the updated configuration
+ rvc_set_config.save()
+ # Tell tts_server.py to update
+ get_alltalk_settings()
+
+ return "RVC settings updated successfully!"
+ except RequestException as e:
+ error_msg = f"Error downloading RVC files: {str(e)}"
+ return_message = "Error downloading RVC files: Network or server error"
+ except json.JSONDecodeError as e:
+ error_msg = f"Error reading RVC configuration file: {str(e)}"
+ return_message = "Error with RVC configuration: Invalid JSON format"
+ except (AttributeError, TypeError) as e:
+ error_msg = f"Configuration structure error: {str(e)}"
+ return_message = "Error updating RVC settings: Invalid configuration structure"
+ except (OSError, IOError) as e:
+ error_msg = f"File system error while saving/creating directories: {str(e)}"
+ return_message = "Error with RVC files: File system error"
+ print_message(error_msg, message_type="error")
+ return return_message
+
+
###########################################################################
# START-UP # Silence Character Normaliser when it checks the Ready Status #
###########################################################################
-import warnings
-warnings.filterwarnings('ignore', message='Trying to detect encoding from a tiny portion')
+warnings.filterwarnings(
+ "ignore", message="Trying to detect encoding from a tiny portion"
+)
+
###########################################
# START-UP # AllTalk allowed startup time #
###########################################
startup_wait_time = 240
-# You can change the above setting to a larger number to allow AllTAlk more time to start up.
+# You can change the above setting to a larger number to allow AllTAlk more time to start up.
# The default setting is 240 seconds (4 minutes). If its taking longer though, you may have a
# Very old system or system issue.
##############################################
# START-UP # Load confignew.json into params #
##############################################
-this_dir = Path(__file__).parent.resolve()
-
-def load_config(file_path):
- with open(file_path, "r") as config_file:
- config = json.load(config_file)
- return config
-
-config_file_path = this_dir / "confignew.json"
-params = load_config(config_file_path)
-branding = params['branding']
+config = AlltalkConfig.get_instance()
github_site = "erew123"
github_repository = "alltalk_tts"
github_branch = "alltalkbeta"
current_folder = os.path.basename(os.getcwd())
-output_folder = this_dir / params["output_folder"]
-delete_output_wavs_setting = params["delete_output_wavs"]
-gradio_enabled = params["gradio_interface"]
+output_folder = config.get_output_directory()
+delete_output_wavs_setting = config.delete_output_wavs
+gradio_enabled = config.gradio_interface
script_path = this_dir / "tts_server.py"
-tunnel_url = None
-running_on_google_colab = False
-running_in_docker = False
+
############################################
# START-UP # Display initial splash screen #
############################################
-print(f"[{branding}TTS]\033[94m _ _ _ \033[1;35m_____ _ _ \033[0m _____ _____ ____ ")
-print(f"[{branding}TTS]\033[94m / \ | | |\033[1;35m_ _|_ _| | | __ \033[0m |_ _|_ _/ ___| ")
-print(f"[{branding}TTS]\033[94m / _ \ | | |\033[1;35m | |/ _` | | |/ / \033[0m | | | | \___ \ ")
-print(f"[{branding}TTS]\033[94m / ___ \| | |\033[1;35m | | (_| | | < \033[0m | | | | ___) |")
-print(f"[{branding}TTS]\033[94m /_/ \_\_|_|\033[1;35m |_|\__,_|_|_|\_\ \033[0m |_| |_| |____/ ")
-print(f"[{branding}TTS]")
+# pylint: disable=line-too-long,anomalous-backslash-in-string
+print_message(
+ "\033[94m _ _ _ \033[1;35m_____ _ _ \033[0m _____ _____ ____ "
+)
+print_message(
+ "\033[94m / \ | | |\033[1;35m_ _|_ _| | | __ \033[0m |_ _|_ _/ ___| "
+)
+print_message(
+ "\033[94m / _ \ | | |\033[1;35m | |/ _` | | |/ / \033[0m | | | | \___ \ "
+)
+print_message(
+ "\033[94m / ___ \| | |\033[1;35m | | (_| | | < \033[0m | | | | ___) |"
+)
+print_message(
+ "\033[94m/_/ \_\_|_|\033[1;35m |_|\__,_|_|_|\_\ \033[0m |_| |_| |____/ "
+)
+print_message("")
+# pylint: enable=line-too-long,anomalous-backslash-in-string
-###########################################################
-# START-UP # Check for updates needed to the config file #
-###########################################################
-update_config_path = this_dir / "system" / "config" / "at_configupdate.json"
-downgrade_config_path = this_dir / "system" / "config" / "at_configdowngrade.json"
-def changes_needed(main_config, update_config, downgrade_config):
- """Check if there are any changes to be made to the main configuration."""
- for key in downgrade_config.keys():
- if key in main_config:
- return True
- for key, value in update_config.items():
- if key not in main_config:
- return True
- return False
-
-def update_config(config_file_path, update_config_path, downgrade_config_path):
- try:
- with open(config_file_path, 'r') as file:
- main_config = json.load(file)
- with open(update_config_path, 'r') as file:
- update_config = json.load(file)
- with open(downgrade_config_path, 'r') as file:
- downgrade_config = json.load(file)
-
- # Determine if changes are needed
- if changes_needed(main_config, update_config, downgrade_config):
- # Backup with timestamp to avoid overwriting
- timestamp = datetime.now().strftime("%Y%m%d%H%M%S")
- backup_path = config_file_path.with_suffix(f".{timestamp}.bak")
- logging.info(f"Creating backup of the main config to {backup_path}")
- shutil.copy(config_file_path, backup_path)
-
- # Proceed with updates and downgrades
- for key, value in update_config.items():
- if key not in main_config:
- main_config[key] = value
- for key in downgrade_config.keys():
- if key in main_config:
- del main_config[key]
-
- # Save the updated configuration
- with open(config_file_path, 'w') as file:
- json.dump(main_config, file, indent=4)
-
- print(f"[{branding}TTS] \033[92mConfig file update: \033[91mUpdates applied\033[0m")
- else:
- print(f"[{branding}TTS] \033[92mConfig file update: \033[93mNo Updates required\033[0m")
-
- except Exception as e:
- print(f"[{branding}TTS] \033[92mConfig file update: \033[91mError updating\033[0m")
-
-# Update the configuration
-update_config(config_file_path, update_config_path, downgrade_config_path)
-
-###########################################################################################
-# START-UP # Check for updates needed to the TTS engines list when a new engine is added #
-###########################################################################################
-tts_engines_path = this_dir / "system" / "tts_engines" / "tts_engines.json"
-new_engines_path = this_dir / "system" / "tts_engines" / "new_engines.json"
-tts_engines_backup_path = tts_engines_path.with_suffix(".backup") # Backup file path
-
-# Function to safely load JSON with error handling
-def safe_load_json(file_path, backup_path=None):
- try:
- # Attempt to open and load the JSON file
- with open(file_path, 'r') as f:
- return json.load(f)
- except FileNotFoundError:
- # If the file is not found, handle it by either restoring from a backup or creating a default
- print(f"[{branding}TTS] File not found: {file_path}")
-
- if backup_path and os.path.exists(backup_path):
- # Load the backup if it exists
- print(f"[{branding}TTS] Restoring from backup: {backup_path}")
- with open(backup_path, 'r') as f:
- data = json.load(f)
- # Save the backup data to the original path
- with open(file_path, 'w') as f:
- json.dump(data, f, indent=4)
- print(f"[{branding}TTS] Restored {file_path} from backup.")
- return data
- else:
- # If no backup is available, create a new default JSON structure
- print(f"[{branding}TTS] No backup found, creating a new default tts_engine JSON structure.")
- default_data = {
- "engine_loaded": "piper",
- "selected_model": "piper",
- "engines_available": [
- {
- "name": "parler",
- "selected_model": "parler - parler_tts_mini_v1"
- },
- {
- "name": "piper",
- "selected_model": "piper"
- },
- {
- "name": "vits",
- "selected_model": "vits - tts_models--en--vctk--vits"
- },
- {
- "name": "xtts",
- "selected_model": "xtts - xttsv2_2.0.3"
- }
- ]
- }
- with open(file_path, 'w') as f:
- json.dump(default_data, f, indent=4)
- print(f"[{branding}TTS] Created new default file at: {file_path}")
- return default_data
- except json.JSONDecodeError as e:
- # Handle JSON corruption
- print(f"[{branding}TTS] JSON decoding error in file {file_path}: {e}")
- if backup_path and os.path.exists(backup_path):
- print(f"[{branding}TTS] Restoring from backup due to corrupted file: {backup_path}")
- with open(backup_path, 'r') as f:
- data = json.load(f)
- # Save the backup data to the original path
- with open(file_path, 'w') as f:
- json.dump(data, f, indent=4)
- print(f"[{branding}TTS] Restored {file_path} from backup.")
- return data
- else:
- raise Exception(f"File {file_path} is corrupted and no backup is available.")
-
-# Load tts_engines.json with retry if corrupted
-tts_engines_data = safe_load_json(tts_engines_path, tts_engines_backup_path)
-
-# Load new_engines.json
-with open(new_engines_path, 'r') as f:
- new_engines_data = json.load(f)
-
-# Extract the list of current and new engines
-current_engines = {engine['name'] for engine in tts_engines_data['engines_available']}
-new_engines = new_engines_data['engines_available']
-
-# Iterate over the new engines to see if they need to be added
-for engine in new_engines:
- engine_name = engine['name']
- if engine_name not in current_engines:
- # Check if the directory for the new engine exists
- engine_dir = this_dir / "system" / "tts_engines" / engine_name
- if engine_dir.is_dir():
- # Add the new engine to the list
- tts_engines_data['engines_available'].append(engine)
- print(f"[{branding}TTS] \033[92mNew TTS Engine : \033[91mAdded {engine_name}\033[0m")
-
- # Backup the current tts_engines.json before saving the updated file
- shutil.copy(tts_engines_path, tts_engines_backup_path)
-
- # Save the updated tts_engines.json
- with open(tts_engines_path, 'w') as f:
- json.dump(tts_engines_data, f, indent=4)
- print(f"{tts_engines_path} updated and saved successfully.")
-
-####################################################
-# START-UP # Re-load from confignew.json to params #
-####################################################
-params = load_config(config_file_path)
-
#############################################################################
# START-UP # Check current folder name has dashes '-' in it and error if so #
#############################################################################
@@ -235,223 +693,362 @@ this_script_dir = this_script_path.parent
# Get the current folder name
this_current_folder = this_script_dir.name
if "-" in this_current_folder:
- print(f"[{branding}TTS]")
- print(f"[{branding}TTS] \033[91mWarning\033[0m The current folder name contains a dash ('\033[93m-\033[0m') and this causes errors/issues. Please ensure")
- print(f"[{branding}TTS] \033[91mWarning\033[0m the folder name does not have a dash e.g. rename ('\033[93malltalk_tts-main\033[0m') to ('\033[93malltalk_tts\033[0m').")
- print(f"[{branding}TTS]")
- print(f"[{branding}TTS] \033[92mCurrent folder:\033[0m {this_current_folder}")
+ print_message("")
+ print_message( # pylint: disable=line-too-long
+ "The current folder name contains a dash ('\033[93m-\033[0m') and this causes errors/issues. Please ensure",
+ message_type="warning",
+ )
+ # pylint: disable=line-too-long
+ print_message(
+ "the folder name does not have a dash e.g. rename ('\033[93malltalk_tts-main\033[0m') to ('\033[93malltalk_tts\033[0m').",
+ message_type="warning",
+ )
+ print_message("")
+ print_message(
+ "\033[92mCurrent folder:\033[0m {this_current_folder}", message_type="warning"
+ )
sys.exit(1)
+# pylint: disable=ungrouped-imports,unused-import,import-outside-toplevel,import-error
+
##############################################
# START-UP # Check if we are on Google Colab #
##############################################
def check_google_colab():
+ """
+ Test if we are running on a google colab server
+ """
+ debug_func_entry()
try:
- import google.colab
+ from google.colab import colab
return True
except ImportError:
return False
-running_on_google_colab = check_google_colab()
+_state['running_on_google_colab'] = check_google_colab()
###############################################################################
# START-UP # Test if we are running within Text-gen-webui or as a Standalone #
###############################################################################
try:
- import gradio as gr
from modules import chat, shared, ui_chat
from modules.logging_colors import logger
from modules.ui import create_refresh_button
from modules.utils import gradio
- print(f"[{branding}TTS] \033[92mStart-up Mode : \033[93mText-gen-webui mode\033[0m")
+
+ print_message("\033[92mStart-up Mode : \033[93mText-gen-webui mode\033[0m")
running_in_standalone = False
running_in_tgwui = True
except ModuleNotFoundError:
running_in_standalone = True
running_in_tgwui = False
- print(f"[{branding}TTS] \033[92mStart-up Mode : \033[93mStandalone mode\033[0m")
+ print_message("\033[92mStart-up Mode : \033[93mStandalone mode\033[0m")
+
+# pylint: enable=ungrouped-imports,unused-import,import-outside-toplevel,import-error
######################################################
# START-UP # Check if this is a first time start-up #
######################################################
def run_firsttime_script():
+ """
+ Run the first time startup script based on the current environment
+ (Google Colab, standalone, or TGWUI).
+ """
+ debug_func_entry()
try:
- if running_on_google_colab:
- script_path = '/content/alltalk_tts/system/config/firstrun.py'
+ if _state['running_on_google_colab']:
+ firstrun_script_path = "/content/alltalk_tts/system/config/firstrun.py"
elif running_in_standalone:
- script_path = os.path.join(this_dir, 'system', 'config', 'firstrun.py')
+ firstrun_script_path = os.path.join(this_dir, "system", "config", "firstrun.py")
elif running_in_tgwui:
- script_path = os.path.join(this_dir, 'system', 'config', 'firstrun_tgwui.py')
+ firstrun_script_path = os.path.join(
+ this_dir, "system", "config", "firstrun_tgwui.py"
+ )
else:
- script_path = os.path.join(this_dir, 'system', 'config', 'firstrun.py') # Default to Standalone if nothing matched.
+ firstrun_script_path = os.path.join(this_dir, "system", "config", "firstrun.py")
- subprocess.run([sys.executable, script_path], check=True)
+ subprocess.run([sys.executable, firstrun_script_path], check=True)
except subprocess.CalledProcessError as e:
- print(f"Error occurred while running the script: {e}")
- except Exception as e:
- print(f"An unexpected error occurred: {e}")
+ error_msg = f"Error running first-time setup script: {str(e)}"
+ print_message(error_msg, message_type="error")
+ except (FileNotFoundError, PermissionError) as e:
+ error_msg = f"File system error during first-time setup: {str(e)}"
+ print_message(error_msg, message_type="error")
+
# Call the function to run the startup script
run_firsttime_script()
+
###########################################################
# START-UP # Delete files in outputs folder if configured #
###########################################################
-def delete_old_files(folder_path, days_to_keep):
+def delete_old_files(folder_path, amt_days_to_keep):
+ """
+ Delete files in the output folder that are X days old
+ """
+ debug_func_entry()
current_time = datetime.now()
- print(f"[{branding}TTS] \033[92mWAV file deletion :\033[93m", delete_output_wavs_setting,"\033[0m")
+ print_message(
+ "\033[92mWAV file deletion :\033[93m", delete_output_wavs_setting, "\033[0m"
+ )
for file_name in os.listdir(folder_path):
file_path = os.path.join(folder_path, file_name)
if os.path.isfile(file_path):
file_creation_time = datetime.fromtimestamp(os.path.getctime(file_path))
age = current_time - file_creation_time
- if age > timedelta(days=days_to_keep):
+ if age > timedelta(days=amt_days_to_keep):
os.remove(file_path)
+
# Check and perform file deletion
if delete_output_wavs_setting.lower() == "disabled":
- print(f"[{branding}TTS] \033[92mWAV file deletion :\033[93m Disabled\033[0m")
+ print_message("\033[92mWAV file deletion :\033[93m Disabled\033[0m")
else:
try:
days_to_keep = int(delete_output_wavs_setting.split()[0])
delete_old_files(output_folder, days_to_keep)
except ValueError:
- print(f"[{branding}TTS] \033[92mWAV file deletion :\033[93m Invalid setting for deleting old wav files. Please use 'Disabled' or 'X Days' format\033[0m")
+ # pylint: disable=line-too-long
+ print_message(
+ "\033[92mWAV file deletion :\033[93m Invalid setting for deleting old wav files. Please use 'Disabled' or 'X Days' format\033[0m"
+ )
+
#####################################################################
# START-UP # Check Githubs last update and output into splashscreen #
#####################################################################
-def ordinal(n):
- return "%d%s" % (n, "th" if 4 <= n % 100 <= 20 else {1: "st", 2: "nd", 3: "rd"}.get(n % 10, "th"))
-
def format_datetime(iso_str):
+ """
+ Formats an ISO datetime string into a human-readable format with ordinal day numbers.
+ Example: Converts "2024-03-19T10:30:00Z" to "19th March 2024 at 10:30"
+ """
+ debug_func_entry()
+
+ def _ordinal(n):
+ """Helper function to convert numbers to ordinal form (1st, 2nd, 3rd, etc)"""
+ debug_func_entry()
+ suffix = "th" if 4 <= n % 100 <= 20 else {1: "st", 2: "nd", 3: "rd"}.get(n % 10, "th")
+ return f"{n}{suffix}"
+
dt = datetime.strptime(iso_str, "%Y-%m-%dT%H:%M:%SZ")
- return dt.strftime(f"{ordinal(dt.day)} %B %Y at %H:%M")
+ return dt.strftime(f"{_ordinal(dt.day)} %B %Y at %H:%M")
+
def fetch_latest_commit_sha_and_date(owner, repo, branch):
+ """
+ Fetch the latest commit SHA and date from a GitHub repository branch.
+
+ Args:
+ owner (str): GitHub repository owner
+ repo (str): Repository name
+ branch (str): Branch name to check
+
+ Returns:
+ tuple: (commit_sha, commit_date) or (None, None) if fetch fails
+ """
+ debug_func_entry()
# Modified URL to include the branch
- url = f"https://api.github.com/repos/{owner}/{repo}/commits/{branch}"
+ github_url = f"https://api.github.com/repos/{owner}/{repo}/commits/{branch}"
try:
- response = requests.get(url)
- if response.status_code == 200:
- commit_data = response.json()
- latest_commit_sha = commit_data['sha']
- latest_commit_date = commit_data['commit']['committer']['date']
- return latest_commit_sha, latest_commit_date
- else:
- print(f"[{branding}TTS] \033[92mGithub updated :\033[91m Failed to fetch the latest commit from branch {branch} due to an unexpected response from GitHub")
- return None, None
- except ConnectionError:
- print(f"[{branding}TTS] \033[92mGithub updated :\033[91m Could not reach GitHub to check for updates\033[0m")
+ # Add timeout for both connect and read operations
+ github_response = requests.get(github_url, timeout=(5, 30))
+ if github_response.status_code == 200:
+ commit_data = github_response.json()
+ github_latest_commit_sha = commit_data["sha"]
+ github_latest_commit_date = commit_data["commit"]["committer"]["date"]
+ return github_latest_commit_sha, github_latest_commit_date
+ # pylint: disable=line-too-long
+ print_message(
+ f"\033[92mGithub updated :\033[91m Failed to fetch the latest commit from branch {branch} due to an unexpected response from GitHub"
+ )
+ return None, None
+ except (RequestsConnectionError, requests.Timeout): # Added Timeout to caught exceptions
+ print_message(
+ "\033[92mGithub updated :\033[91m Could not reach GitHub to check for updates\033[0m"
+ )
return None, None
+
def read_or_initialize_sha(file_path, owner, repo, branch):
+ """
+ Read SHA from existing file or initialize with latest commit SHA if file doesn't exist.
+ """
+ debug_func_entry()
if os.path.exists(file_path):
- with open(file_path, 'r') as file:
- data = json.load(file)
- return data.get("last_known_commit_sha")
+ with open(file_path, "r", encoding="utf-8") as file:
+ current_data = json.load(file)
+ return current_data.get("last_known_commit_sha")
else:
# File doesn't exist, fetch the latest SHA and create the file
- latest_commit_sha, _ = fetch_latest_commit_sha_and_date(owner, repo, branch)
+ current_commit_sha, _ = fetch_latest_commit_sha_and_date(owner, repo, branch)
if latest_commit_sha:
- with open(file_path, 'w') as file:
- json.dump({"last_known_commit_sha": latest_commit_sha}, file)
- return latest_commit_sha
+ with open(file_path, "w", encoding="utf-8") as file:
+ json.dump({"last_known_commit_sha": current_commit_sha}, file)
+ return current_commit_sha
return None
+
def update_sha_file(file_path, new_sha):
- with open(file_path, 'w') as file:
+ """
+ Update the stored commit SHA in the specified file.
+ """
+ debug_func_entry()
+ with open(file_path, "w", encoding="utf-8") as file:
json.dump({"last_known_commit_sha": new_sha}, file)
+
# Define the file path based on your directory structure
sha_file_path = this_dir / "system" / "config" / "at_github_sha.json"
# Read or initialize the SHA
-last_known_commit_sha = read_or_initialize_sha(sha_file_path, github_site, github_repository, github_branch)
+last_known_commit_sha = read_or_initialize_sha(
+ sha_file_path, github_site, github_repository, github_branch
+)
# Fetch the latest commit SHA and date from the specific branch
-latest_commit_sha, latest_commit_date = fetch_latest_commit_sha_and_date(github_site, github_repository, github_branch)
+latest_commit_sha, latest_commit_date = fetch_latest_commit_sha_and_date(
+ github_site, github_repository, github_branch
+)
-formatted_date = format_datetime(latest_commit_date) if latest_commit_date else "an unknown date"
+formatted_date = (
+ format_datetime(latest_commit_date) if latest_commit_date else "an unknown date"
+)
if latest_commit_sha and latest_commit_sha != last_known_commit_sha:
- print(f"[{branding}TTS] \033[92mGithub updated :\033[93m {formatted_date} \033[92mBranch:\033[93m {github_branch}\033[0m")
+ print_message(
+ f"\033[92mGithub updated :\033[93m {formatted_date} \033[92mBranch:\033[93m {github_branch}\033[0m"
+ )
# Update the file with the new SHA
update_sha_file(sha_file_path, latest_commit_sha)
elif latest_commit_sha == last_known_commit_sha:
- print(f"[{branding}TTS] \033[92mGithub updated :\033[93m {formatted_date} \033[92mBranch:\033[93m {github_branch}\033[0m")
+ print_message(
+ f"\033[92mGithub updated :\033[93m {formatted_date} \033[92mBranch:\033[93m {github_branch}\033[0m"
+ )
+
##################################################
-# START-UP # Configure the subprocess hanlder ####
+# START-UP # Configure the subprocess handler ####
##################################################
-def signal_handler(sig, frame):
- print(f"[{branding}Shutdown] \033[94mReceived Ctrl+C, terminating subprocess. Kill your Python processes if this fails to exit.\033[92m")
- if process.poll() is None:
- process.terminate()
- process.wait() # Wait for the subprocess to finish
+def signal_handler(sig, frame): # pylint: disable=unused-argument
+ """Handle Ctrl+C signal by saving config, terminating subprocess, and exiting gracefully."""
+ debug_func_entry()
+ config.save()
+ print_message(
+ "\033[94mReceived Ctrl+C, terminating subprocess. Kill your Python processes if this fails to exit.\033[92m"
+ )
+ if _state["process"].poll() is None:
+ _state["process"].terminate()
+ _state["process"].wait() # Wait for the subprocess to finish
sys.exit(0)
#####################################################################
# START-UP # Start the Subprocess and Check for Google Colab/Docker #
#####################################################################
-try:
- import google.colab
- running_on_google_colab = True
- with open('/content/alltalk_tts/googlecolab.json', 'r') as f:
- data = json.load(f)
- tunnel_url_1, tunnel_url_2 = data.get('google_ip_address', [None, None])
-except FileNotFoundError:
- print("Could not find IP address")
- tunnel_url_1, tunnel_url_2 = None, None
-except ImportError:
- running_on_google_colab = False
+# pylint: disable=ungrouped-imports,unused-import,import-outside-toplevel,import-error
+if check_google_colab():
+ _state['running_on_google_colab'] = True
+ try:
+ with open("/content/alltalk_tts/googlecolab.json", "r", encoding="utf-8") as f:
+ data = json.load(f)
+ _state['tunnel_url_1'], _state['tunnel_url_2'] = data.get("google_ip_address", [None, None])
+ except FileNotFoundError:
+ print_message("Could not find IP address")
+ _state['tunnel_url_1'], _state['tunnel_url_2'] = None, None
+else:
+ _state['running_on_google_colab'] = False
+# pylint: enable=ungrouped-imports,unused-import,import-outside-toplevel
+
# Attach the signal handler to the SIGINT signal (Ctrl+C)
-signal.signal(signal.SIGINT, signal_handler)
+system_signal.signal(system_signal.SIGINT, signal_handler)
# Check if we're running in docker
-if os.path.isfile("/.dockerenv") and 'google.colab' not in sys.modules:
- print(f"[{branding}TTS]")
- print(f"[{branding}TTS] \033[94mRunning in Docker environment. Please note:\033[0m")
- print(f"[{branding}TTS] Docker environments have various considerations:")
- print(f"[{branding}TTS] AllTalk v2 is running 2x web servers. One webserver is the API address that exernal TTS")
- print(f"[{branding}TTS] generation requests are sent to. The other webserver is the Gradio management interface.")
- print(f"[{branding}TTS] If you want to access and use both API calls and the Gradio interface, in a docker style")
- print(f"[{branding}TTS] environment, you will need to somehow make these accessable, depending on your scenario:")
- print(f"[{branding}TTS] 1. A Local Area Network (LAN) scenario:")
- print(f"[{branding}TTS] - Ensure you've exposed the Gradio and API ports correctly.")
- print(f"[{branding}TTS] - The application should work as expected.")
- print(f"[{branding}TTS] 2. Internet/Remotely accessed scenario:")
- print(f"[{branding}TTS] - You'll need to set up a secure tunnel or VPN to the host server.")
- print(f"[{branding}TTS] - If you want just API requests, you can map just the API and control AllTalk via API calls.")
- print(f"[{branding}TTS] - If you want both API and Gradio interfaces you need to make both accessible.")
- print(f"[{branding}TTS] 3. Default Addresses:")
- print(f"[{branding}TTS] - Internal API address: http://localhost:{params['api_def']['api_port_number']}")
- print(f"[{branding}TTS] - Internal Gradio address: http://localhost:{params['gradio_port_number']}")
- print(f"[{branding}TTS] Tunnel Setup:")
- print(f"[{branding}TTS] - If using a tunnel, you'll need to provide the API external URL/Address in Gradio.")
- print(f"[{branding}TTS] - Look for 'API URL/Address' option in the Gradio interface `Generate` page.")
- running_in_docker = True
- docker_url = f"http://localhost:{params['api_def']['api_port_number']}"
+if os.path.isfile("/.dockerenv") and "google.colab" not in sys.modules:
+ print_message("")
+ print_message("\033[94mRunning in Docker environment. Please note:\033[0m")
+ print_message("Docker environments have various considerations:")
+ print_message(
+ "AllTalk v2 is running 2x web servers. One webserver is the API address that exernal TTS"
+ )
+ print_message(
+ "generation requests are sent to. The other webserver is the Gradio management interface."
+ )
+ print_message(
+ "If you want to access and use both API calls and the Gradio interface, in a docker style"
+ )
+ print_message(
+ "environment, you will need to somehow make these accessable, depending on your scenario:"
+ )
+ print_message(" 1. A Local Area Network (LAN) scenario:")
+ print_message(" - Ensure you've exposed the Gradio and API ports correctly.")
+ print_message(" - The application should work as expected.")
+ print_message("2. Internet/Remotely accessed scenario:")
+ print_message(
+ " - You'll need to set up a secure tunnel or VPN to the host server."
+ )
+ print_message(
+ " - If you want just API requests, you can map just the API and control AllTalk via API calls."
+ )
+ print_message(
+ " - If you want both API and Gradio interfaces you need to make both accessible."
+ )
+ print_message(" 3. Default Addresses:")
+ print_message(
+ f" - Internal API address: http://localhost:{config.api_def.api_port_number}"
+ )
+ print_message(
+ f" - Internal Gradio address: http://localhost:{config.gradio_port_number}"
+ )
+ print_message(" Tunnel Setup:")
+ print_message(
+ " - If using a tunnel, you'll need to provide the API external URL/Address in Gradio."
+ )
+ print_message(
+ " - Look for 'API URL/Address' option in the Gradio interface `Generate` page."
+ )
+ _state['running_in_docker'] = True
else:
- running_in_docker = False
+ _state['running_in_docker'] = False
# Start the subprocess (now unified for both Docker and non-Docker environments)
-process = subprocess.Popen([sys.executable, script_path])
+# Not using 'with' as we want the process to run in the background
+# pylint: disable=consider-using-with
+_state["process"] = subprocess.Popen([sys.executable, script_path])
# Check if the subprocess has started successfully
-if process.poll() is None:
- if running_on_google_colab:
- print(f"[{branding}TTS]")
- print(f"[{branding}TTS] \033[94mGoogle Colab Detected\033[00m")
- print(f"[{branding}TTS]")
+if _state["process"].poll() is None:
+ if _state['running_on_google_colab']:
+ print_message("")
+ print_message("\033[94mGoogle Colab Detected\033[00m")
+ print_message("")
else:
- print(f"[{branding}TTS] \033[91mWarning\033[0m TTS Subprocess Webserver failing to start process")
- print(f"[{branding}TTS] \033[91mWarning\033[0m It could be that you have something on port:", params["port_number"])
- print(f"[{branding}TTS] \033[91mWarning\033[0m Or you have not started in a Python environment with all the necessary bits installed")
- print(f"[{branding}TTS] \033[91mWarning\033[0m Check you are starting Text-generation-webui with either the start_xxxxx file or the Python environment with cmd_xxxxx file.")
- print(f"[{branding}TTS] \033[91mWarning\033[0m xxxxx is the type of OS you are on e.g. windows, linux or mac.")
- print(f"[{branding}TTS] \033[91mWarning\033[0m Alternatively, you could check no other Python processes are running that shouldn't be e.g. Restart your computer is the simple way.")
+ print_message(
+ "TTS Subprocess Webserver failing to start process", message_type="warning"
+ )
+ print_message(
+ f"It could be that you have something on port: {config.port_number}",
+ message_type="warning",
+ )
+ print_message(
+ "Or you have not started in a Python environment with all the necessary bits installed",
+ message_type="warning",
+ )
+ # pylint: disable=line-too-long
+ print_message(
+ "Check you are starting Text-generation-webui with either the start_xxxxx file or the Python environment with cmd_xxxxx file.",
+ message_type="warning",
+ )
+ # pylint: disable=line-too-long
+ print_message(
+ "xxxxx is the type of OS you are on e.g. windows, linux or mac.",
+ message_type="warning",
+ )
+ # pylint: disable=line-too-long
+ print_message(
+ "Alternatively, you could check no other Python processes are running that shouldn't be e.g. Restart your computer is the simple way.",
+ message_type="warning",
+ )
# Cleanly kill off this script, but allow text-generation-webui to keep running, albeit without this alltalk_tts
sys.exit(1)
@@ -465,824 +1062,1400 @@ time.sleep(initial_delay)
start_time = time.time()
warning_displayed = False
-url = f"http://localhost:{params['api_def']['api_port_number']}/api/ready"
+url = f"http://localhost:{config.api_def.api_port_number}/api/ready"
while time.time() - start_time < timeout:
try:
- response = requests.get(url)
+ response = requests.get(url, timeout=(5, 30))
if response.status_code == 200 and response.text == "Ready":
break
- except requests.RequestException as e:
+ except RequestException as e:
# Log the exception if needed
pass
if not warning_displayed and time.time() - start_time >= warning_delay:
- print(f"[{branding}TTS] \033[91mWarning\033[0m TTS Engine has NOT started up yet. Will keep trying for {timeout} seconds maximum. Please wait.")
- print(f"[{branding}TTS] \033[91mWarning\033[0m Mechanical hard drives and a slow PCI BUS are examples of things that can affect load times.")
- print(f"[{branding}TTS] \033[91mWarning\033[0m Some TTS engines index their AI TTS models on loading, which can be slow on CPU or old systems.")
- print(f"[{branding}TTS] \033[91mWarning\033[0m Using one of the other TTS engines on slower systems can help ease this issue.")
+ print_message(
+ "TTS Engine has NOT started up yet. Will keep trying for "
+ + str(timeout)
+ + " seconds maximum. Please wait.",
+ message_type="warning",
+ )
+ print_message(
+ "Mechanical hard drives and a slow PCI BUS are examples of things that can affect load times.",
+ message_type="warning",
+ )
+ print_message(
+ "Some TTS engines index their AI TTS models on loading, which can be slow on CPU or old systems.",
+ message_type="warning",
+ )
+ print_message(
+ "Using one of the other TTS engines on slower systems can help ease this issue.",
+ message_type="warning",
+ )
warning_displayed = True
-
+
time.sleep(1)
else:
- print(f"[{branding}TTS]")
- print(f"[{branding}TTS] Startup timed out. Full help available here \033[92mhttps://github.com/erew123/alltalk_tts#-help-with-problems\033[0m")
- print(f"[{branding}TTS] On older systems, you may wish to open and edit \033[94mscript.py\033[0m with a text editor and change the")
- print(f"[{branding}TTS] \033[94mstartup_wait_time = 240\033[0m setting to something like \033[94mstartup_wait_time = 460\033[0m as this will allow")
- print(f"[{branding}TTS] AllTalk more time (6 mins) to try load the model into your VRAM. Otherwise, please visit the GitHub for")
- print(f"[{branding}TTS] a list of other possible troubleshooting options.")
+ print_message("")
+ print_message(
+ "Startup timed out. Full help available here https://github.com/erew123/alltalk_tts#-help-with-problems"
+ )
+ print_message(
+ "On older systems, you may wish to open and edit script.py with a text editor and change the"
+ )
+ print_message(
+ "startup_wait_time = 240 setting to something like startup_wait_time = 460 as this will allow"
+ )
+ print_message(
+ "AllTalk more time (6 mins) to try load the model into your VRAM. Otherwise, please visit the GitHub for"
+ )
+ print_message("a list of other possible troubleshooting options.")
# Cleanly kill off this script, but allow text-generation-webui to keep running, albeit without this alltalk_tts
sys.exit(1)
-if running_on_google_colab:
- print(f"[{branding}TTS]")
- print(f"[{branding}TTS] \033[94mAPI Address :\033[00m",f"\033[92m{tunnel_url_1}\033[00m")
- print(f"[{branding}TTS] \033[94mGradio Light:\033[00m",f"\033[92m{tunnel_url_2}\033[00m")
- print(f"[{branding}TTS] \033[94mGradio Dark :\033[00m",f"\033[92m{tunnel_url_2}?__theme=dark\033[00m")
- print(f"[{branding}TTS]")
+if _state['running_on_google_colab']:
+ print_message("")
+ print_message("\033[94mAPI Address :\033[00m \033[92m" + _state['tunnel_url_1'] + "\033[00m")
+ print_message("\033[94mGradio Light:\033[00m \033[92m" + _state['tunnel_url_2'] + "\033[00m")
+ print_message(
+ "\033[94mGradio Dark :\033[00m \033[92m"
+ + _state['tunnel_url_2']
+ + "?__theme=dark\033[00m"
+ )
+ print_message("")
else:
- print(f"[{branding}TTS]")
- print(f"[{branding}TTS] \033[94mAPI Address :\033[00m",f"\033[92m127.0.0.1:{params['api_def']['api_port_number']}\033[00m")
- print(f"[{branding}TTS] \033[94mGradio Light:\033[00m",f"\033[92mhttp://127.0.0.1:{params['gradio_port_number']}\033[00m")
- print(f"[{branding}TTS] \033[94mGradio Dark :\033[00m \033[92mhttp://127.0.0.1:{params['gradio_port_number']}?__theme=dark\033[00m")
- print(f"[{branding}TTS]")
-
-print(f"[{branding}TTS] \033[94mAllTalk WIKI:\033[00m",f"\033[92mhttps://github.com/erew123/alltalk_tts/wiki\033[00m")
-print(f"[{branding}TTS] \033[94mErrors Help :\033[00m",f"\033[92mhttps://github.com/erew123/alltalk_tts/wiki/Error-Messages-List\033[00m")
-print(f"[{branding}TTS]")
+ print_message("")
+ print_message(
+ "\033[94mAPI Address :\033[00m \033[92m127.0.0.1:"
+ + str(config.api_def.api_port_number)
+ + "\033[00m"
+ )
+ print_message(
+ "\033[94mGradio Light:\033[00m \033[92mhttp://127.0.0.1:"
+ + str(config.gradio_port_number)
+ + "\033[00m"
+ )
+ print_message(
+ "\033[94mGradio Dark :\033[00m \033[92mhttp://127.0.0.1:"
+ + str(config.gradio_port_number)
+ + "?__theme=dark\033[00m"
+ )
+ print_message("")
+
+print_message(
+ "\033[94mAllTalk WIKI:\033[00m \033[92mhttps://github.com/erew123/alltalk_tts/wiki\033[00m"
+)
+print_message(
+ "\033[94mErrors Help :\033[00m \033[92mhttps://github.com/erew123/alltalk_tts/wiki/Error-Messages-List\033[00m"
+)
+print_message("")
+
#########################################
# START-UP # Espeak-ng check on Windows #
#########################################
def check_espeak_ng():
- if platform.system() == "Windows":
- try:
- result = subprocess.run(['espeak-ng', '--version'], capture_output=True, text=True, check=True)
- return
- except (subprocess.CalledProcessError, FileNotFoundError) as e:
- # If the command returns an error, print the error message
- print(f"[{branding}TTS]")
- print(f"[{branding}TTS] Espeak-ng for Windows\033[91m WAS NOT FOUND. \033[0mYou can install\033[0m")
- print(f"[{branding}TTS] \033[0mit from this location \033[93m\\alltalk_tts\\system\espeak-ng\\\033[0m")
- print(f"[{branding}TTS] Then close this command prompt window and open a new")
- print(f"[{branding}TTS] command prompt, before starting {branding}again.")
- elif platform.system() == "Darwin": # macOS
- try:
- result = subprocess.run(['espeak-ng', '--version'], capture_output=True, text=True, check=True)
- return
- except FileNotFoundError:
- print(f"[{branding}TTS]")
- print(f"[{branding}TTS] Espeak-ng for macOS\033[91m WAS NOT FOUND. \033[0mYou can install\033[0m")
- print(f"[{branding}TTS] \033[0mit using Homebrew: \033[93mbrew install espeak-ng\033[0m")
- else: # Linux
- try:
- result = subprocess.run(['espeak-ng', '--version'], capture_output=True, text=True, check=True)
- return
- except FileNotFoundError:
- print(f"[{branding}TTS]")
- print(f"[{branding}TTS] Espeak-ng for Linux\033[91m WAS NOT FOUND. \033[0mYou can install\033[0m")
- print(f"[{branding}TTS] \033[0mit using your package manager, e.g., \033[93mapt-get install espeak-ng\033[0m")
- print(f"[{branding}TTS]")
- print(f"[{branding}TTS]")
+ """
+ Test to see if espeak-ng is installed and available. It's required
+ by various TTS engines.
+ """
+ debug_func_entry()
+
+ def print_espeak_warning(platform_name, install_instructions):
+ print_message("")
+ print_message(
+ f"Espeak-ng for {platform_name}\033[91m WAS NOT FOUND. \033[0mYou can install\033[0m",
+ message_type="warning",
+ )
+ print_message(
+ f"\033[0mit using {install_instructions}\033[0m",
+ message_type="warning",
+ )
+
+ try:
+ subprocess.run(
+ ["espeak-ng", "--version"], capture_output=True, text=True, check=True
+ )
+ return
+ except FileNotFoundError:
+ if platform.system() == "Windows":
+ print_espeak_warning(
+ "Windows", r"this location \033[93m\alltalk_tts\system\espeak-ng\\"
+ )
+ print_message(
+ "Then close this command prompt window and open a new",
+ message_type="warning",
+ )
+ print_message("command prompt, before re-starting.", message_type="warning")
+ elif platform.system() == "Darwin": # macOS
+ print_espeak_warning(
+ "macOS", "\033[93mHomebrew: brew install espeak-ng\033[0m"
+ )
+ else: # Linux
+ print_espeak_warning(
+ "Linux",
+ "\033[93myour package manager, e.g., apt-get install espeak-ng\033[0m",
+ )
+ except subprocess.CalledProcessError:
+ # Handle cases where `espeak-ng` exists but fails to run
+ print_message("Error running espeak-ng. Please check the installation.", message_type="error")
+
+ print_message("")
+
+
check_espeak_ng()
+
####################################
# START-UP # Subprecess management #
####################################
+
def start_subprocess():
- global process
- if process is None or process.poll() is not None:
- process = subprocess.Popen([sys.executable, script_path])
+ """Start a new subprocess if none is running. Return status message."""
+ debug_func_entry()
+ if _state["process"] is None or _state["process"].poll() is not None:
+ # pylint: disable=consider-using-with # Process needs to run in background
+ _state["process"] = subprocess.Popen([sys.executable, script_path])
return "Subprocess started."
- else:
- return "Subprocess is already running."
+ return "Subprocess is already running."
+
def stop_subprocess():
- global process
- if process is not None:
- process.terminate()
- process.wait()
- process = None
+ """Terminate running subprocess and wait for completion. Return status message."""
+ debug_func_entry()
+ if _state["process"] is not None:
+ _state["process"].terminate()
+ _state["process"].wait()
+ _state["process"] = None
return "Subprocess stopped."
- else:
- return "Subprocess is not running."
+ return "Subprocess is not running."
+
def restart_subprocess():
+ """Stop current subprocess, display TTS engine swap message, and start new subprocess."""
+ debug_func_entry()
stop_subprocess()
- print(f"[{branding}ENG]")
- print(f"[{branding}ENG] \033[94mSwapping TTS Engine. Please wait.\033[00m")
- print(f"[{branding}ENG]")
+ print_message("")
+ print_message("\033[94mSwapping TTS Engine. Please wait.\033[00m")
+ print_message("")
return start_subprocess()
+
def check_subprocess_status():
- global process
- if process is None or process.poll() is not None:
+ """Check if subprocess is currently running and return status message."""
+ debug_func_entry()
+ if _state["process"] is None or _state["process"].poll() is not None:
return "Subprocess is not running."
- else:
- return "Subprocess is running."
+ return "Subprocess is running."
###################################################################
# START-UP # Register the termination code to be executed at exit #
###################################################################
-atexit.register(lambda: process.terminate() if process.poll() is None else None)
+atexit.register(lambda: _state["process"].terminate() if _state["process"].poll() is None else None)
##########################
# Setup global variables #
##########################
-# Load the language options from languages
-with open(os.path.join(this_dir, "system", "config", "languages.json"), "r") as f:
- languages_list = json.load(f)
-current_model_loaded = None
-#deepspeed_installed = True
+# deepspeed_installed = True
tgwui_lovram = False
tgwui_deepspeed = False
# Create a global lock for tracking TTS generation occuring
process_lock = threading.Lock()
-# Pull the values for IP address, port and protocol for communication with the AllTalk renote server
-alltalk_protocol = "http://"
-alltalk_ip_port = f"127.0.0.1:{params['api_def']['api_port_number']}"
# Pull the connection timeout value for communication requests with the AllTalk remote server
connection_timeout = 10
-# Create a few base global variables that are required
-models_available = None # Gets populated with the list of all models/engines available on the AllTalk Server
-at_settings = None # Gets populated with the list of all settings currently set on the AllTalk Server
-current_model_loaded = None # Gets populated with the name of the current loaded TTS engine/model on the AllTalk Server
# Used to detect if a model is loaded in to AllTalk server to block TTS genereation if needed.
tts_model_loaded = None
+#########################
+# Endpoint API Builders #
+#########################
+def build_url(endpoint, include_api=True):
+ """
+ Build URL for standard API endpoints using static predefined URLs.
+ Used for endpoints that don't need dynamic domain handling.
+
+ Examples:
+ - voice2rvc
+ - reload
+ - stop-generation
+
+ Args:
+ endpoint (str): The API endpoint to append to the base URL
+ include_api (bool): Whether to include /api/ in the URL
+ True: Uses '/api/'
+ False: Uses ''
+
+ Returns:
+ str: Complete URL in format: {base_url}/api/{endpoint}
+ where base_url is determined by runtime environment:
+ - Colab: _state['tunnel_url_1'], _state['tunnel_url_2']
+ - Docker: _state['docker_url']
+ - Local: _state['alltalk_protocol'] + _state['alltalk_ip_port']
+ """
+ debug_func_entry()
+ print_message(
+ f"\033[94mRunning on Docker is: \033[0m{_state['running_in_docker']}",
+ message_type="debug_gradio_IP",
+ )
+ print_message(
+ f"\033[94mRunning on Google is: \033[0m{_state['running_on_google_colab']}",
+ message_type="debug_gradio_IP",
+ )
+
+ if _state['running_on_google_colab']:
+ base_url = _state['tunnel_url_1']
+ elif _state['running_in_docker']:
+ base_url = _state['docker_url']
+ else:
+ base_url = f"{_state['alltalk_protocol']}{_state['alltalk_ip_port']}"
+
+ prefix = "/api/" if include_api else "/"
+ final_url = f"{base_url}{prefix}{endpoint.lstrip('/')}"
+ print_message(
+ f"\033[94mBase URL is set as : \033[0m{final_url}",
+ message_type="debug_gradio_IP",
+ )
+ return final_url
+
+
+def build_dynamic_url(endpoint, include_protocol=True, include_api=True):
+ """
+ Build URL for endpoints that may need the current domain.
+ Used when the URL needs to reference the actual server domain
+ rather than predefined static URLs.
+
+ Examples:
+ - tts-generate
+ - tts-generate-streaming
+ - File URLs (with include_protocol=True)
+
+ Args:
+ endpoint (str): The API endpoint or path to append
+ include_protocol (bool): Whether to include protocol prefix
+ True: Uses 'protocol' global variable
+ False: Uses 'http://'
+ include_api (bool): Whether to include /api/ in the URL
+ True: Uses '/api/'
+ False: Uses ''
+
+ Returns:
+ str: Complete URL with format depending on environment:
+ - Colab: _state['tunnel_url_1']/api/{endpoint}
+ - Docker: _state['docker_url']/api/{endpoint}
+ - Local: {protocol_prefix}{_state['my_current_url']}/api/{endpoint}
+ """
+ debug_func_entry()
+ print_message(
+ f"\033[94mRunning on Docker is: \033[0m{_state['running_in_docker']}",
+ message_type="debug_gradio_IP",
+ )
+ print_message(
+ f"\033[94mRunning on Google is: \033[0m{_state['running_on_google_colab']}",
+ message_type="debug_gradio_IP",
+ )
+
+ prefix = "/api/" if include_api else "/"
+
+ if _state['running_on_google_colab']:
+ base_url = _state['tunnel_url_1']
+ elif _state['running_in_docker']:
+ base_url = _state['docker_url']
+ else:
+ protocol_prefix = f"{_state['alltalk_protocol']}" if include_protocol else "http://"
+ base_url = f"{protocol_prefix}{_state['my_current_url']}"
+
+ final_url = f"{base_url}{prefix}{endpoint.lstrip('/')}"
+ print_message(
+ f"\033[94mBase URL is set as : \033[0m{final_url}",
+ message_type="debug_gradio_IP",
+ )
+ return final_url
+
+
+def update_docker_address(new_url):
+ """
+ Used within the gradio interface to update the IP/URL
+ when running inside a docker environment.
+ """
+ _state['docker_url'] = new_url
+ print_message(f"\033[94mDocker IP/URL for API set to: \033[0m{new_url}")
+ return f"Docker IP/URL for API set to: {new_url}"
+
+
#################################################
# Pull all the settings from the AllTalk Server #
#################################################
def get_alltalk_settings():
- global current_model_loaded, models_available, engines_available, current_engine_loaded
- if running_on_google_colab:
- voices_url = f"{tunnel_url_1}/api/voices"
- rvcvoices_url = f"{tunnel_url_1}/api/rvcvoices"
- settings_url = f"{tunnel_url_1}/api/currentsettings"
- elif running_in_docker:
- voices_url = f"{docker_url}/api/voices"
- rvcvoices_url = f"{docker_url}/api/rvcvoices"
- settings_url = f"{docker_url}/api/currentsettings"
- else:
- voices_url = f"{alltalk_protocol}{alltalk_ip_port}/api/voices"
- rvcvoices_url = f"{alltalk_protocol}{alltalk_ip_port}/api/rvcvoices"
- settings_url = f"{alltalk_protocol}{alltalk_ip_port}/api/currentsettings"
+ """Retrieves current settings from the AllTalk server including voices, models, and configuration.
+ Returns a dictionary of settings with default values if server is unreachable.
+ """
+ debug_func_entry()
+
+ # Define endpoints
+ endpoints = {
+ name: build_url(name) for name in ["voices", "rvcvoices", "currentsettings"]
+ }
+
+ # Default settings in case of failure
+ default_settings = {
+ "voices": ["Please Refresh Settings"],
+ "rvcvoices": ["Please Refresh Settings"],
+ "engines_available": ["Please Refresh Settings"],
+ "current_engine_loaded": ["Please Refresh Settings"],
+ "models_available": ["Please Refresh Settings"],
+ "current_model_loaded": "Please Refresh Settings",
+ "manufacturer_name": "",
+ "deepspeed_capable": False,
+ "deepspeed_available": False,
+ "deepspeed_enabled": False,
+ "generationspeed_capable": False,
+ "generationspeed_set": 1.0,
+ "lowvram_capable": False,
+ "lowvram_enabled": False,
+ "pitch_capable": False,
+ "pitch_set": 0,
+ "repetitionpenalty_capable": False,
+ "repetitionpenalty_set": 10.0,
+ "streaming_capable": False,
+ "temperature_capable": False,
+ "temperature_set": 0.75,
+ "ttsengines_installed": False,
+ "languages_capable": False,
+ "multivoice_capable": False,
+ "multimodel_capable": False,
+ }
try:
- voices_response = requests.get(voices_url, timeout=connection_timeout)
- rvcvoices_response = requests.get(rvcvoices_url, timeout=connection_timeout)
- settings_response = requests.get(settings_url, timeout=connection_timeout)
-
- if voices_response.status_code == 200 and settings_response.status_code == 200:
- voices_data = voices_response.json()
- rvcvoices_data = rvcvoices_response.json()
- settings_data = settings_response.json()
-
- engines_available = sorted(settings_data["engines_available"])
- current_engine_loaded = settings_data["current_engine_loaded"]
- models_available = sorted([model["name"] for model in settings_data["models_available"]])
- current_model_loaded = settings_data["current_model_loaded"]
-
- return {
- "voices": sorted(voices_data["voices"], key=lambda s: s.lower()),
- "rvcvoices": (rvcvoices_data["rvcvoices"]),
- "engines_available": engines_available,
- "current_engine_loaded": current_engine_loaded,
- "models_available": models_available,
- "current_model_loaded": current_model_loaded,
- "manufacturer_name": settings_data.get("manufacturer_name", ""),
- "deepspeed_capable": settings_data.get("deepspeed_capable", False),
- "deepspeed_available": settings_data.get("deepspeed_available", False),
- "deepspeed_enabled": settings_data.get("deepspeed_enabled", False),
- "generationspeed_capable": settings_data.get("generationspeed_capable", False),
- "generationspeed_set": settings_data.get("generationspeed_set", 1.0),
- "lowvram_capable": settings_data.get("lowvram_capable", False),
- "lowvram_enabled": settings_data.get("lowvram_enabled", False),
- "pitch_capable": settings_data.get("pitch_capable", False),
- "pitch_set": settings_data.get("pitch_enabled", False),
- "repetitionpenalty_capable": settings_data.get("repetitionpenalty_capable", False),
- "repetitionpenalty_set": settings_data.get("repetitionpenalty_set", 10.0),
- "streaming_capable": settings_data.get("streaming_capable", False),
- "temperature_capable": settings_data.get("temperature_capable", False),
- "temperature_set": settings_data.get("temperature_set", 0.75),
- "ttsengines_installed": settings_data.get("ttsengines_installed", False),
- "languages_capable": settings_data.get("languages_capable", False),
- "multivoice_capable": settings_data.get("multivoice_capable", False),
- "multimodel_capable": settings_data.get("multimodel_capable", False),
+ # Make all requests with a single session for efficiency
+ with requests.Session() as session:
+ responses = {
+ name: session.get(url, timeout=connection_timeout)
+ for name, url in endpoints.items()
}
- else:
- print(f"[{branding}Server] \033[91mWarning\033[0m Failed to retrieve {branding}settings from API.")
- if voices_response.status_code != 200:
- print(f"[{branding}Server] \033[91mWarning\033[0m Failed to retrieve voices from API. Status code:\n{voices_response.status_code}")
- if settings_response.status_code != 200:
- print(f"[{branding}Server] \033[91mWarning\033[0m Failed to retrieve current settings from API. Status code:\n{settings_response.status_code}")
- return {
- "voices": ["Please Refresh Settings"],
- "rvcvoices": ["Please Refresh Settings"],
- "engines_available": ["Please Refresh Settings"],
- "current_engine_loaded": ["Please Refresh Settings"],
- "models_available": ["Please Refresh Settings"],
- "current_model_loaded": "Please Refresh Settings",
- "manufacturer_name": "",
- "deepspeed_capable": False,
- "deepspeed_available": False,
- "deepspeed_enabled": False,
- "generationspeed_capable": False,
- "generationspeed_set": 1.0,
- "lowvram_capable": False,
- "lowvram_enabled": False,
- "pitch_capable": False,
- "pitch_set": 0,
- "repetitionpenalty_capable": False,
- "repetitionpenalty_set": 10.0,
- "streaming_capable": False,
- "temperature_capable": False,
- "temperature_set": 0.75,
- "ttsengines_installed": False,
- "languages_capable": False,
- "multivoice_capable": False,
- "multimodel_capable": False,
+
+ # Check if all responses are successful
+ if not all(r.status_code == 200 for r in responses.values()):
+ failed_endpoints = [
+ name for name, r in responses.items() if r.status_code != 200
+ ]
+ print_message(
+ f"Failed to retrieve data from: {', '.join(failed_endpoints)}",
+ message_type="warning",
+ component="TTS",
+ )
+ print_message(
+ "Please confirm you have the correct IP/URL & that the system isnt currenly restarting",
+ message_type="warning",
+ component="TTS",
+ )
+ return default_settings
+
+ # Parse responses
+ voices_data = responses["voices"].json()
+ rvcvoices_data = responses["rvcvoices"].json()
+ settings_data = responses["currentsettings"].json()
+
+ # Update state dictionary with all current values
+ _state.update({
+ 'srv_current_voices': sorted(voices_data["voices"], key=lambda s: s.lower()),
+ 'srv_current_rvcvoices': rvcvoices_data["rvcvoices"],
+ 'srv_engines_available': sorted(settings_data["engines_available"]),
+ 'srv_current_engine_loaded': settings_data["current_engine_loaded"],
+ 'srv_models_available': sorted([model["name"] for model in settings_data["models_available"]]),
+ 'srv_current_model_loaded': settings_data["current_model_loaded"],
+ 'srv_character_voice': config.tgwui.tgwui_character_voice,
+ 'srv_narrator_voice': config.tgwui.tgwui_narrator_voice,
+ 'srv_rvc_character_voice': config.rvc_settings.rvc_char_model_file,
+ 'srv_rvc_narrator_voice': config.rvc_settings.rvc_narr_model_file,
+ 'srv_settings_capabilities': {
+ 'temperature_capable': settings_data["temperature_capable"],
+ 'repetitionpenalty_capable': settings_data["repetitionpenalty_capable"],
+ 'languages_capable': settings_data["languages_capable"],
+ 'generationspeed_capable': settings_data["generationspeed_capable"],
+ 'pitch_capable': settings_data["pitch_capable"],
+ 'streaming_capable': settings_data["streaming_capable"]
}
- except (RequestException, ConnectionError) as e:
- print(f"[{branding}Server] \033[91mWarning\033[0m Unable to connect to the {branding}server. Status code:\n{str(e)}")
- return {
- "voices": ["Please Refresh Settings"],
- "rvcvoices": ["Please Refresh Settings"],
- "engines_available": ["Please Refresh Settings"],
- "current_engine_loaded": ["Please Refresh Settings"],
- "models_available": ["Please Refresh Settings"],
- "current_model_loaded": "Please Refresh Settings",
- "manufacturer_name": "",
- "deepspeed_capable": False,
- "deepspeed_available": False,
- "deepspeed_enabled": False,
- "generationspeed_capable": False,
- "generationspeed_set": 1.0,
- "lowvram_capable": False,
- "lowvram_enabled": False,
- "pitch_capable": False,
- "pitch_set": 0,
- "repetitionpenalty_capable": False,
- "repetitionpenalty_set": 10.0,
- "streaming_capable": False,
- "temperature_capable": False,
- "temperature_set": 0.75,
- "ttsengines_installed": False,
- "languages_capable": False,
- "multivoice_capable": False,
- "multimodel_capable": False,
+ })
+
+ # Construct and return settings dictionary
+ base_settings = {
+ "voices": _state['srv_current_voices'],
+ "rvcvoices": _state['srv_current_rvcvoices'],
+ "engines_available": _state['srv_engines_available'],
+ "current_engine_loaded": _state['srv_current_engine_loaded'],
+ "models_available": _state['srv_models_available'],
+ "current_model_loaded": _state['srv_current_model_loaded'],
}
-at_settings = get_alltalk_settings() # Pull all the current settings from the AllTalk server, if its online.
+ # Add remaining settings
+ return {
+ **base_settings,
+ **{
+ k: settings_data.get(k, v)
+ for k, v in default_settings.items()
+ if k not in base_settings
+ }
+ }
+
+ except (RequestException, RequestsConnectionError) as e:
+ print_message(
+ f"Unable to connect to the {config.branding} API server: {str(e)}",
+ message_type="warning",
+ component="TTS",
+ )
+ print_message(
+ "Please confirm you have the correct IP/URL & that the system isnt currenly restarting",
+ message_type="warning",
+ component="TTS",
+ )
+ return default_settings
+
+
+# Pull all the current settings from the AllTalk server, if its online.
+get_alltalk_settings()
#############################
#### TTS STOP GENERATION ####
#############################
def stop_generate_tts():
- if running_on_google_colab:
- api_url = f"{tunnel_url_1}/api/stop-generation"
- elif running_in_docker:
- api_url = f"{docker_url}/api/stop-generation"
- else:
- api_url = f"{alltalk_protocol}{alltalk_ip_port}/api/stop-generation"
+ """
+ Sends a stop TTS generation request
+ """
+ debug_func_entry()
+ api_url = build_url("stop-generation")
try:
- response = requests.put(api_url, timeout=connection_timeout)
- if response.status_code == 200:
- return response.json()["message"]
- else:
- print(f"[{branding}TTS] \033[91mWarning\033[0m Failed to stop generation. Status code:\n{response.status_code}")
- return {"message": "Failed to stop generation"}
- except (RequestException, ConnectionError) as e:
- print(f"[{branding}TTS] \033[91mWarning\033[0m Unable to connect to the {branding}server. Status code:\n{str(e)}")
+ stop_response = requests.put(api_url, timeout=connection_timeout)
+ if stop_response.status_code == 200:
+ return stop_response.json()["message"]
+ print_message(
+ f"Failed to stop generation. Status code:\n{stop_response.status_code}",
+ message_type="warning",
+ )
+ return {"message": "Failed to stop generation"}
+ except (RequestException, RequestsConnectionError) as e:
+ print_message(
+ f"Unable to connect to the {config.branding} server. Status code:\n{str(e)}",
+ message_type="warning",
+ )
return {"message": "Failed to stop generation"}
-#####################################
-#### MODEL LOADING AND UNLOADING ####
-#####################################
-# MODEL - Swap model based on Gradio selection
-def send_reload_request(value_sent):
- try:
- if running_on_google_colab:
- url = f"{tunnel_url_1}/api/reload"
- elif running_in_docker:
- url = f"{docker_url}/api/reload"
- else:
- url = f"{alltalk_protocol}{alltalk_ip_port}/api/reload"
- payload = {"tts_method": value_sent}
- response = requests.post(url, params=payload)
- response.raise_for_status() # Raises an HTTPError for bad responses
- except requests.exceptions.RequestException as e:
- # Handle the HTTP request error
- print(f"[{branding}TTS] \033[91mWarning\033[0m Error during request to webserver process: Status code:\n{e}")
- return {"status": "error", "message": str(e)}
-#############################################################################################################
-# _____ _ _ _ ____ _ _ #
-# |_ _|____ _| |_ __ _ ___ _ __ __ _____| |__ _ _(_) / ___|_ __ __ _ __| (_) ___ #
-# | |/ _ \ \/ / __|____ / _` |/ _ \ '_ \ ____\ \ /\ / / _ \ '_ \| | | | | | | _| '__/ _` |/ _` | |/ _ \ #
-# | | __/> <| ||_____| (_| | __/ | | |_____\ V V / __/ |_) | |_| | | | |_| | | | (_| | (_| | | (_) | #
-# |_|\___/_/\_\\__| \__, |\___|_| |_| \_/\_/ \___|_.__/ \__,_|_| \____|_| \__,_|\__,_|_|\___/ #
-# |___/ #
-#############################################################################################################
+def send_api_request(endpoint, payload=None, headers=None, params=None):
+ """Base function for making API requests to the AllTalk server.
-####################################################################################################
-# TGWUI # Saves all settings back to the config file for the REMOTE version of the TGWUI extension #
-####################################################################################################
-def tgwui_save_settings():
- # Read the existing settings from the file
- try:
- with open(config_file_path, "r") as file:
- existing_settings = json.load(file)
- except FileNotFoundError:
- existing_settings = {}
- # Update the specific settings you want to change
- existing_settings.setdefault("tgwui", {})
- existing_settings["tgwui"]["tgwui_activate_tts"] = params["tgwui"]['tgwui_activate_tts']
- existing_settings["tgwui"]["tgwui_autoplay_tts"] = params["tgwui"]['tgwui_autoplay_tts']
- existing_settings["tgwui"]["tgwui_narrator_enabled"] = params["tgwui"]['tgwui_narrator_enabled']
- existing_settings["tgwui"]["tgwui_non_quoted_text_is"] = params["tgwui"]["tgwui_non_quoted_text_is"]
- existing_settings["tgwui"]["tgwui_language"] = params["tgwui"]['tgwui_language']
- existing_settings["tgwui"]["tgwui_temperature_set"] = params["tgwui"]['tgwui_temperature_set']
- existing_settings["tgwui"]["tgwui_repetitionpenalty_set"] = params["tgwui"]['tgwui_repetitionpenalty_set']
- existing_settings["tgwui"]["tgwui_generationspeed_set"] = params["tgwui"]['tgwui_generationspeed_set']
- existing_settings["tgwui"]["tgwui_pitch_set"] = params["tgwui"]['tgwui_pitch_set']
- existing_settings["tgwui"]["tgwui_narrator_voice"] = params["tgwui"]['tgwui_narrator_voice']
- existing_settings["tgwui"]["tgwui_show_text"] = params["tgwui"]['tgwui_show_text']
- existing_settings["tgwui"]["tgwui_character_voice"] = params["tgwui"]['tgwui_character_voice']
- existing_settings["tgwui"]["tgwui_rvc_char_voice"] = params["tgwui"]['tgwui_rvc_char_voice']
- existing_settings["tgwui"]["tgwui_rvc_narr_voice"] = params["tgwui"]['tgwui_rvc_narr_voice']
- # Save the updated settings back to the file
- with open(config_file_path, "w") as file:
- json.dump(existing_settings, file, indent=4)
-
-#####################################################
-# TGWUI - Low VRAM change request to enable/disable #
-#####################################################
-# LOW VRAM - Gradio Checkbox handling
-def send_lowvram_request(value_sent):
- try:
- params["tgwui"]["tts_model_loaded"] = False
- if value_sent:
- audio_path = this_dir / "lowvramenabled.wav"
- else:
- audio_path = this_dir / "lowvramdisabled.wav"
- url = f"{alltalk_protocol}{alltalk_ip_port}/api/lowvramsetting?new_low_vram_value={value_sent}"
- headers = {"Content-Type": "application/json"}
- response = requests.post(url, headers=headers)
- response.raise_for_status() # Raises an HTTPError for bad responses
- json_response = response.json()
- # Check if the low VRAM request was successful
- if json_response.get("status") == "lowvram-success":
- # Update any relevant variables or perform other actions on success
- params["tgwui"]["tts_model_loaded"] = True
- return f''
- except requests.exceptions.RequestException as e:
- # Handle the HTTP request error
- print(f"[{branding}Server] \033[91mWarning\033[0m Error during request to webserver process: Status code:\n{e}")
- return {"status": "error", "message": str(e)}
+ Args:
+ endpoint (str): API endpoint (e.g., '/api/reload')
+ payload (dict, optional): POST data
+ headers (dict, optional): Request headers
+ params (dict, optional): URL parameters
-#########################
-# TGWUI - DeepSpeed ####
-#########################
-def send_deepspeed_request(value_sent):
+ Returns:
+ dict: Response data or error status
+ """
+ debug_func_entry()
try:
- params["tgwui"]["tts_model_loaded"] = False
- if value_sent:
- audio_path = this_dir / "deepspeedenabled.wav"
- else:
- audio_path = this_dir / "deepspeeddisabled.wav"
- url = f"{alltalk_protocol}{alltalk_ip_port}/api/deepspeed?new_deepspeed_value={value_sent}"
- headers = {"Content-Type": "application/json"}
- response = requests.post(url, headers=headers)
- response.raise_for_status() # Raises an HTTPError for bad responses
- json_response = response.json()
- # Check if the deepspeed request was successful
- if json_response.get("status") == "deepspeed-success":
- # Update any relevant variables or perform other actions on success
- params["tgwui"]["tts_model_loaded"] = True
- return f''
- except requests.exceptions.RequestException as e:
- # Handle the HTTP request error
- print(f"[{branding}Server] \033[91mWarning\033[0m Error during request to webserver process: Status code:\n{e}")
- return {"status": "error", "message": str(e)}
+ # Construct base URL based on environment
+ base_url = build_url(endpoint.lstrip("/"))
-###################################
-# TGWUI - TTS STANDARD GENERATION #
-###################################
-def send_and_generate(gen_text, gen_character_voice, gen_rvccharacter_voice, gen_narrator_voice, gen_rvcnarrator_voice, gen_narrator_activated, gen_textnotinisde, gen_repetition, gen_language, gen_filter, gen_speed, gen_pitch, gen_autoplay, gen_autoplay_vol, gen_file_name, gen_temperature, gen_filetimestamp, gen_stream, gen_stopcurrentgen):
- api_url = f"{alltalk_protocol}{alltalk_ip_port}/api/tts-generate"
- if gen_text == "":
- print(f"[{branding}TTS] No Text was sent to generate as TTS")
- return None, str("No Text was sent to generate as TTS")
- if gen_stopcurrentgen:
- stop_generate_tts() # Call for the current generation to stop
- tgwui_save_settings() # Save the current TGWUI settings to the config file
- if gen_stream == "true":
- api_url = f"{alltalk_protocol}{alltalk_ip_port}/api/tts-generate-streaming"
- encoded_text = requests.utils.quote(gen_text)
- streaming_url = f"{api_url}?text={encoded_text}&voice={gen_character_voice}&language={gen_language}&output_file={gen_file_name}"
- return streaming_url, str("TTS Streaming Audio Generated")
- else:
- data = {
- "text_input": gen_text,
- "text_filtering": gen_filter,
- "character_voice_gen": gen_character_voice,
- "rvccharacter_voice_gen": gen_rvccharacter_voice,
- "narrator_enabled": str(gen_narrator_activated).lower(),
- "narrator_voice_gen": gen_narrator_voice,
- "rvcnarrator_voice_gen": gen_rvcnarrator_voice,
- "text_not_inside": gen_textnotinisde,
- "language": gen_language,
- "output_file_name": str(gen_file_name),
- "output_file_timestamp": str(gen_filetimestamp).lower(),
- "autoplay": str(gen_autoplay).lower(),
- "autoplay_volume": str(gen_autoplay_vol),
- "speed": str(gen_speed),
- "pitch": str(gen_pitch),
- "temperature": str(gen_temperature),
- "repetition_penalty": str(gen_repetition),
+ api_response = requests.post(
+ base_url,
+ json=payload,
+ headers=headers or {"Content-Type": "application/json"},
+ params=params,
+ timeout=(5, 30) # 5 sec connect, 30 sec read timeout
+ )
+ api_response.raise_for_status()
+
+ return {
+ "status": "success",
+ "response": api_response.json() if api_response.content else None,
+ "raw_response": api_response,
}
- # print(f"Debug: Generate request param:", data) if params["debug_tts"] else None
- try:
- response = requests.post(api_url, data=data)
- response.raise_for_status() # Raise an exception for 4xx or 5xx status codes
- result = response.json()
- if gen_autoplay == "true":
- return None, str("TTS Audio Generated (Played remotely)")
- else:
- if params["api_def"]["api_use_legacy_api"]:
- return result['output_file_url'], str("TTS Audio Generated")
- else:
- # Prepend the URL and PORT to the output_file_url
- output_file_url = f"{alltalk_protocol}{alltalk_ip_port}{result['output_file_url']}"
- return output_file_url, str("TTS Audio Generated")
- except (RequestException, ConnectionError) as e:
- print(f"[{branding}TTS] \033[91mWarning\033[0m Error occurred during the API request: Status code:\n{str(e)}")
- return None, str("Error occurred during the API request")
+ except RequestException as e:
+ print_message(
+ f"Error during request to webserver process: Status code:\n{e}",
+ message_type="warning",
+ )
+ return {"status": "error", "message": str(e)}
-####################################################################
-# TGWUI # Text-generation-webui sends text for TTS generation here #
-####################################################################
-def output_modifier(string, state):
- if not params["tgwui"]["tgwui_activate_tts"]:
- return string
- # Strip out Images
- img_info = ""
- cleaned_text, img_info = tgwui_extract_and_remove_images(string)
- if cleaned_text is None:
+
+def send_reload_request(value_sent):
+ """Sends a request to reload the TTS model.
+
+ Args:
+ value_sent: The TTS method/model to load
+ """
+ debug_func_entry()
+ params = {"tts_method": value_sent}
+ return send_api_request("/api/reload", params=params)
+
+
+########################################
+#### Whisper Transcription Handling ####
+########################################
+# pylint: disable=too-few-public-methods
+class TranscriptionProgress:
+ """Track progress of Whisper's audio transcription tasks including file counts and completion status."""
+ def __init__(self):
+ self.current_file = ""
+ self.total_files = 0
+ self.completed_files = 0
+ self.current_progress = 0
+
+
+whisper_progress = TranscriptionProgress()
+
+# File size constants (in bytes)
+WARNING_SIZE = 25 * 1024 * 1024 # 25MB
+MAX_SIZE = 100 * 1024 * 1024 # 100MB
+
+# Allowed audio formats
+ALLOWED_AUDIO_FORMATS = {
+ ".mp3",
+ ".wav",
+ ".m4a",
+ ".ogg",
+ ".flac",
+ ".aac",
+ ".wma",
+ ".aiff",
+ ".alac",
+ ".opus",
+}
+
+
+def setup_directories(setup_script_dir):
+ """Setup required directories"""
+ debug_func_entry()
+ base_dir = os.path.join(setup_script_dir, "transcriptions")
+ uploads_dir = os.path.join(base_dir, "uploads")
+ output_dir = os.path.join(base_dir, "output")
+
+ for directory in [base_dir, uploads_dir, output_dir]:
+ os.makedirs(directory, exist_ok=True)
+ print_message(f"Created/verified directory: {directory}", "debug_transcribe")
+
+ return base_dir, uploads_dir, output_dir
+
+
+def validate_audio_file(file_path):
+ """Validate file type and size"""
+ debug_func_entry()
+ file_size = os.path.getsize(file_path)
+ file_ext = os.path.splitext(file_path)[1].lower()
+ mime_type = mimetypes.guess_type(file_path)[0]
+
+ print_message(f"Validating file: {file_path}", "debug_transcribe")
+ print_message(f"File size: {file_size/1024/1024:.1f}MB", "debug_transcribe")
+ print_message(f"File extension: {file_ext}", "debug_transcribe")
+ print_message(f"MIME type: {mime_type}", "debug_transcribe")
+
+ validate_warnings = []
+ errors = []
+
+ if file_ext not in ALLOWED_AUDIO_FORMATS:
+ msg = f"Invalid file format: {file_ext}"
+ print_message(msg, "error")
+ errors.append(msg)
+
+ if mime_type and not mime_type.startswith("audio/"):
+ msg = f"File does not appear to be audio: {mime_type}"
+ print_message(msg, "error")
+ errors.append(msg)
+
+ if file_size > MAX_SIZE:
+ msg = (
+ f"File too large: {file_size/1024/1024:.1f}MB (max {MAX_SIZE/1024/1024}MB)"
+ )
+ print_message(msg, "error")
+ errors.append(msg)
+ elif file_size > WARNING_SIZE:
+ msg = f"File is large ({file_size/1024/1024:.1f}MB) and may take longer to process"
+ print_message(msg, "warning")
+ validate_warnings.append(msg)
+
+ return validate_warnings, errors
+
+
+def cleanup_gpu_memory():
+ """Thorough GPU memory cleanup"""
+ debug_func_entry()
+ if torch.cuda.is_available():
+ # Clear PyTorch's CUDA cache
+ torch.cuda.empty_cache()
+ # Force garbage collection
+ gc.collect()
+ # Clear any remaining CUDA memory
+ with torch.cuda.device("cuda"):
+ torch.cuda.empty_cache()
+ torch.cuda.ipc_collect()
+ print_message("GPU memory cleaned", "debug_transcribe")
+
+
+def create_output_directory(base_dir, prefix=""):
+ """Create a uniquely named output directory for this batch of transcriptions"""
+ debug_func_entry()
+ timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
+ dir_name = f"{prefix}_{timestamp}" if prefix else timestamp
+ output_dir = os.path.join(base_dir, "output", dir_name)
+ os.makedirs(output_dir, exist_ok=True)
+ print_message(f"Created output directory: {output_dir}", "debug_transcribe")
+ return output_dir
+
+
+def create_output_filename(input_path, output_dir, prefix=""):
+ """Create an output filename based on the input path"""
+ debug_func_entry()
+ stem = Path(input_path).stem
+ if prefix:
+ return os.path.join(output_dir, f"{prefix}_{stem}")
+ return os.path.join(output_dir, stem)
+
+
+def delete_uploaded_files():
+ """Delete all files in the uploads directory"""
+ debug_func_entry()
+ del_upload_script_dir = os.path.dirname(os.path.abspath(__file__))
+ uploads_dir = os.path.join(del_upload_script_dir, "transcriptions", "uploads")
+ deleted_count = 0
+
+ print_message(f"Attempting to delete files in: {uploads_dir}", "debug_transcribe")
+
+ if os.path.exists(uploads_dir):
+ for file in os.listdir(uploads_dir):
+ file_path = os.path.join(uploads_dir, file)
+ try:
+ if os.path.isfile(file_path):
+ os.unlink(file_path)
+ deleted_count += 1
+ print_message(f"Deleted: {file}", "debug_transcribe")
+ except FileNotFoundError:
+ error_msg = f"File not found while trying to delete: {file}"
+ print_message(error_msg, "error")
+ except PermissionError:
+ error_msg = f"Permission denied while trying to delete: {file}"
+ print_message(error_msg, "error")
+ except OSError as e:
+ error_msg = f"OS error while deleting {file}: {str(e)}"
+ print_message(error_msg, "error")
+
+ msg = f"Deleted {deleted_count} uploaded audio files"
+ print_message(msg)
+ return msg
+
+ msg = "No files to delete - uploads directory not found"
+ print_message(msg, "warning")
+ return msg
+
+
+def create_srt_content(segments):
+ """Convert whisper segments to SRT format"""
+ debug_func_entry()
+ srt_content = []
+ for i, segment in enumerate(segments, start=1):
+ # Convert start/end times to SRT format (HH:MM:SS,mmm)
+ srt_start_time = timedelta(seconds=segment["start"])
+ srt_end_time = timedelta(seconds=segment["end"])
+
+ # Format with milliseconds
+ start_str = str(srt_start_time).replace(".", ",")[:11]
+ end_str = str(srt_end_time).replace(".", ",")[:11]
+
+ # Ensure proper formatting for times less than 1 hour
+ if len(start_str) < 11:
+ start_str = "0" + start_str
+ if len(end_str) < 11:
+ end_str = "0" + end_str
+
+ # Build SRT segment
+ srt_content.append(
+ f"{i}\n{start_str} --> {end_str}\n{segment['text'].strip()}\n"
+ )
+
+ return "\n".join(srt_content)
+
+
+def process_audio_files(
+ audio_files,
+ model_size,
+ output_format,
+ delete_after,
+ prefix="",
+ gradio_progress=gr.Progress(track_tqdm=True), # pylint: disable=unused-argument # Used by tqdm for progress tracking
+):
+ """Process multiple audio files and return paths to transcription files"""
+ debug_func_entry()
+ try:
+ print_message(
+ f"Starting transcription process with model: {model_size}",
+ "debug_transcribe",
+ )
+
+ # Setup directories
+ transcribe_script_dir = os.path.dirname(os.path.abspath(__file__))
+ base_dir, uploads_dir, _ = setup_directories(transcribe_script_dir)
+
+ # Create specific output directory for this batch
+ output_dir = create_output_directory(base_dir, prefix)
+ print_message(
+ f"Using prefix: {prefix if prefix else 'none'}", "debug_transcribe"
+ )
+
+ # Validate and copy files to uploads directory
+ processed_files = []
+ validation_messages = []
+
+ if not audio_files:
+ msg = "No audio files provided for processing."
+ print_message(msg, "error")
+ return None, msg
+
+ for audio_file in audio_files:
+ print_message(f"Processing file: {audio_file}", "debug_transcribe")
+ transcribe_warnings, errors = validate_audio_file(audio_file)
+ file_name = Path(audio_file).name
+
+ if errors:
+ for error in errors:
+ validation_messages.append(f"Skipping {file_name}: {error}")
+ continue
+
+ validation_messages.extend(
+ [f"Warning for {file_name}: {warning}" for warning in transcribe_warnings]
+ )
+
+ # Copy file to uploads directory
+ upload_path = os.path.join(uploads_dir, file_name)
+ shutil.copy2(audio_file, upload_path)
+ print_message(f"Copied to uploads: {upload_path}", "debug_transcribe")
+ processed_files.append(upload_path)
+
+ if not processed_files:
+ msg = "No valid files to process\n" + "\n".join(validation_messages)
+ print_message(msg, "error")
+ return None, msg
+
+ # Load model
+ device = "cuda" if torch.cuda.is_available() else "cpu"
+ print_message(
+ f"Loading Whisper model {model_size} on {device}", "debug_transcribe"
+ )
+ _state['whisper_model'] = whisper.load_model(model_size, device=device)
+
+ output_files = []
+ metadata = []
+
+ whisper_progress.total_files = len(processed_files)
+
+ for idx, audio_file in enumerate(
+ tqdm(processed_files, desc="Processing files")
+ ):
+ try:
+ whisper_progress.current_file = Path(audio_file).name
+ whisper_progress.completed_files = idx
+ print_message(f"Transcribing: {audio_file}", "debug_transcribe")
+
+ # Transcribe audio
+ result = _state['whisper_model'].transcribe(audio_file)
+
+ # Create output file
+ base_output_path = create_output_filename(
+ audio_file, output_dir, prefix
+ )
+
+ # Save based on format
+ if output_format == "txt":
+ output_path = f"{base_output_path}.txt"
+ with open(output_path, "w", encoding="utf-8") as file:
+ file.write(result["text"])
+ elif output_format == "srt":
+ output_path = f"{base_output_path}.srt"
+ with open(output_path, "w", encoding="utf-8") as file:
+ file.write(create_srt_content(result["segments"]))
+ else: # json
+ output_path = f"{base_output_path}.json"
+ with open(output_path, "w", encoding="utf-8") as file:
+ json.dump(result, file, indent=4, ensure_ascii=False)
+
+ print_message(
+ f"Saved transcription to: {output_path}", "debug_transcribe"
+ )
+ output_files.append(output_path)
+
+ # Collect metadata
+ metadata.append(
+ {
+ "original_file": Path(audio_file).name,
+ "duration": (
+ result["segments"][-1]["end"] if result["segments"] else 0
+ ),
+ "output_file": Path(output_path).name,
+ "model_used": model_size,
+ "format": output_format,
+ "timestamp": datetime.now().isoformat(),
+ }
+ )
+
+ # Delete audio file if requested
+ if delete_after:
+ os.unlink(audio_file)
+ print_message(
+ f"Deleted audio file: {audio_file}", "debug_transcribe"
+ )
+
+ except (whisper.RuntimeError, torch.cuda.OutOfMemoryError) as e:
+ error_msg = f"Error in transcription of {Path(audio_file).name}: {str(e)}"
+ print_message(error_msg, "error")
+ validation_messages.append(error_msg)
+ continue
+ except (OSError, IOError) as e:
+ error_msg = f"File error processing {Path(audio_file).name}: {str(e)}"
+ print_message(error_msg, "error")
+ validation_messages.append(error_msg)
+ continue
+ except (ValueError, TypeError) as e:
+ error_msg = f"Data processing error in {Path(audio_file).name}: {str(e)}"
+ print_message(error_msg, "error")
+ validation_messages.append(error_msg)
+ continue
+
+ # Create summary file
+ summary_path = os.path.join(
+ output_dir, f"summary_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json"
+ )
+ with open(summary_path, "w", encoding="utf-8") as file:
+ json.dump(
+ {
+ "processed_files": metadata,
+ "total_files": len(processed_files),
+ "successful_transcriptions": len(output_files),
+ "processing_date": datetime.now().isoformat(),
+ "validation_messages": validation_messages,
+ },
+ f,
+ indent=4,
+ )
+
+ print_message(f"Created summary file: {summary_path}", "debug_transcribe")
+
+ # Create ZIP file containing all outputs
+ zip_path = os.path.join(
+ base_dir,
+ "output",
+ (
+ f"{prefix}_transcriptions_{datetime.now().strftime('%Y%m%d_%H%M%S')}.zip"
+ if prefix
+ else f"transcriptions_{datetime.now().strftime('%Y%m%d_%H%M%S')}.zip"
+ ),
+ )
+ with zipfile.ZipFile(zip_path, "w") as zipf:
+ for file in output_files + [summary_path]:
+ # Preserve the directory structure in the ZIP
+ arcname = os.path.relpath(file, os.path.dirname(output_dir))
+ zipf.write(file, arcname)
+
+ print_message(f"Created ZIP file: {zip_path}", "debug_transcribe")
+
+ status_message = "\n".join(
+ validation_messages
+ + [
+ f"Processed {m['original_file']}: {m['duration']:.2f} seconds"
+ for m in metadata
+ ]
+ )
+ status_message += f"\nFiles saved in: {output_dir}"
+
+ return zip_path, status_message
+
+ except (OSError, IOError) as e:
+ print_message(f"File system error: {str(e)}", "error")
+ return None, f"File system error: {str(e)}"
+ except torch.cuda.OutOfMemoryError as e:
+ print_message(f"GPU memory error: {str(e)}", "error")
+ return None, "GPU memory error: Consider using a smaller model"
+ except ValueError as e:
+ print_message(f"Invalid input error: {str(e)}", "error")
+ return None, f"Configuration error: {str(e)}"
+ finally:
+ # Cleanup GPU memory
+ if 'whisper_model' in _state:
+ del _state['whisper_model']
+ cleanup_gpu_memory()
+
+
+########################
+#### Live Dictation ####
+########################
+def reset_audio_stream_handlers(dictate_audio, state, text_output, audio_plot):
+ """Reset handlers for the dictation audio input."""
+ if not isinstance(dictate_audio, gr.Audio):
+ print_message("Error: 'dictate_audio' is not a valid Gradio Audio component.", "error")
return
- # Get current settings
- language_code = languages_list.get(params["tgwui"]["tgwui_language"])
- character_voice = params["tgwui"]["tgwui_character_voice"]
- rvc_character_voice = params["tgwui"]["tgwui_rvc_char_voice"]
- narrator_voice = params["tgwui"]["tgwui_narrator_voice"]
- rvc_narrator_voice = params["tgwui"]["tgwui_rvc_narr_voice"]
- narrator_enabled = params["tgwui"]["tgwui_narrator_enabled"]
- text_not_inside = params["tgwui"]["tgwui_non_quoted_text_is"]
- repetition_policy = params["tgwui"]["tgwui_repetitionpenalty_set"]
- text_filtering = "html"
- speed = params["tgwui"]["tgwui_generationspeed_set"]
- pitch = params["tgwui"]["tgwui_pitch_set"]
- autoplay = False # This is never used in TGWUI so a base False setting is sent
- autoplay_volume = 0.8 # This is never used in TGWUI so a base False setting is sent
- temperature = params["tgwui"]["tgwui_temperature_set"]
- # Lock and process TTS request
- if process_lock.acquire(blocking=False):
- try:
- if "character_menu" in state:
- output_file = state["character_menu"]
- else:
- output_file = str("TTSOUT_")
- generate_response, status_message = send_and_generate(cleaned_text, character_voice, rvc_character_voice, narrator_voice, rvc_narrator_voice, narrator_enabled, text_not_inside, repetition_policy, language_code, text_filtering, speed, pitch, autoplay, autoplay_volume, output_file, temperature, True, False, False)
- if status_message == "TTS Audio Generated":
- # Handle Gradio and playback
- autoplay = "autoplay" if params["tgwui"]["tgwui_autoplay_tts"] else ""
- string = f''
- if params["tgwui"]["tgwui_show_text"]:
- string += tgwui_reinsert_images(cleaned_text, img_info)
- shared.processing_message = "*Is typing...*"
- return string
- else:
- print(f"[{branding}Server] \033[91mWarning\033[0m Audio generation failed. Status code:\n{status_message}")
- finally:
- # Always release the lock, whether an exception occurs or not
- process_lock.release()
- else:
- # The lock is already acquired
- print(f"[{branding}Model] \033[91mWarning\033[0m Audio generation is already in progress. Please wait.")
- return
-
-###########################################################################################################
-# TGWUI # Strips out images from the TGWUI string if needed and re-inserts the image after TTS generation #
-###########################################################################################################
-img_pattern = r']*src\s*=\s*["\'][^"\'>]+["\'][^>]*>'
-def tgwui_extract_and_remove_images(text):
- """
- Extracts all image data from the text and removes it for clean TTS processing.
- Returns the cleaned text and the extracted image data.
- """
- img_matches = re.findall(img_pattern, text)
- img_info = "\n".join(img_matches) # Store extracted image data
- cleaned_text = re.sub(img_pattern, '', text) # Remove images from text
- return cleaned_text, img_info
+ # Clear existing handlers
+ if hasattr(dictate_audio, "stop_recording"):
+ dictate_audio.stop_recording(fn=None)
+ if hasattr(dictate_audio, "start_recording"):
+ dictate_audio.start_recording(fn=None)
+ if hasattr(dictate_audio, "stream"):
+ dictate_audio.stream(fn=None)
-def tgwui_reinsert_images(text, img_info):
- """
- Reinserts the previously extracted image data back into the text.
- """
- if img_info: # Check if there are images to reinsert
- text += f"\n\n{img_info}"
- return text
+ # Re-add the handlers # Ignore them being undefined
+ dictate_audio.start_recording(
+ fn=on_start_recording, inputs=[state], outputs=[state] # pylint: disable=undefined-variable
+ ).success(fn=None, js="() => {console.log('Recording started');}")
-################################################################
-# TGWUI # Used to generate a preview voice sample within TGWUI #
-################################################################
-def random_sentence():
- with open(this_dir / "system" / "config" / "harvard_sentences.txt") as f:
- return random.choice(list(f)).rstrip()
+ dictate_audio.stop_recording(
+ fn=on_stop_recording, inputs=[state], outputs=[state] # pylint: disable=undefined-variable
+ ).success(fn=None, js="() => {console.log('Recording stopped');}")
-def voice_preview(string):
- if not params["tgwui"]["tgwui_activate_tts"]:
- return string
- language_code = languages_list.get(params["tgwui"]["tgwui_language"])
- if not string:
- string = random_sentence()
- generate_response, status_message = send_and_generate(string, params["tgwui"]["tgwui_character_voice"], params["tgwui"]["tgwui_rvc_char_voice"], params["tgwui"]["tgwui_narrator_voice"], params["tgwui"]["tgwui_rvc_narr_voice"], False, "character", params["tgwui"]["tgwui_repetitionpenalty_set"], language_code, "standard", params["tgwui"]["tgwui_generationspeed_set"], params["tgwui"]["tgwui_pitch_set"], False, 0.8, "previewvoice", params["tgwui"]["tgwui_temperature_set"], False, False, False)
- if status_message == "TTS Audio Generated":
- # Handle Gradio and playback
- autoplay = "autoplay" if params["tgwui"]["tgwui_autoplay_tts"] else ""
- return f''
- else:
- # Handle the case where audio generation was not successful
- return f"[{branding}Server] Audio generation failed. Status code:\n{status_message}"
-
-###################################################################
-# TGWUI # Used to inform TGWUI that TTS is disabled/not activated #
-###################################################################
-def state_modifier(state):
- if not params["tgwui"]["tgwui_activate_tts"]:
- return state
- state["stream"] = False
- return state
-
-###################################################################
-# TGWUI # Sends message to TGWUI interface during TTS generation #
-###################################################################
-def input_modifier(string, state):
- if not params["tgwui"]["tgwui_activate_tts"]:
- return string
- shared.processing_message = "*Is recording a voice message...*"
- return string
-
-########################################################################
-# TGWUI # Used to delete historic TTS audios from TGWUI chat interface #
-########################################################################
-def remove_tts_from_history(history):
- for i, entry in enumerate(history["internal"]):
- history["visible"][i] = [history["visible"][i][0], entry[1]]
- return history
-
-def toggle_text_in_history(history):
- for i, entry in enumerate(history["visible"]):
- visible_reply = entry[1]
- if visible_reply.startswith("\n\n{reply}",
- ]
- else:
- history["visible"][i] = [
- history["visible"][i][0],
- f"{visible_reply.split('')[0]}",
- ]
- return history
-
-def history_modifier(history):
- # Remove autoplay from the last reply
- if len(history["internal"]) > 0:
- history["visible"][-1] = [
- history["visible"][-1][0],
- history["visible"][-1][1].replace("controls autoplay>", "controls>"),
- ]
- return history
-
-###########################################################
-# TGWUI # Update IP address and Port from TGWUI interface #
-###########################################################
-def tgwui_update_alltalk_ip_port(value_sent):
- global alltalk_ip_port
- alltalk_ip_port = value_sent
-
-###########################################################
-# TGWUI # Update http/https protocol from TGWUI interface #
-###########################################################
-# Update Protocol from Gradio
-def tgwui_update_alltalk_protocol(value_sent):
- global alltalk_protocol
- alltalk_protocol = value_sent
-
-###########################################################################################################################
-# TGWUI # Debounces model updates when tgwui_update_dropdowns is called, preventing an update from causing a model reload #
-###########################################################################################################################
-def tgwui_handle_ttsmodel_dropdown_change(model_name):
- if not getattr(tgwui_handle_ttsmodel_dropdown_change, "skip_reload", False):
- send_reload_request(model_name)
- tgwui_handle_ttsmodel_dropdown_change.skip_reload = False
-
-##################################################################################################
-# TGWUI # Pulls the current AllTalk Server settings & updates gradio when Refresh button pressed #
-##################################################################################################
-def tgwui_update_dropdowns():
- global at_settings
- at_settings = get_alltalk_settings() # Pull all the current settings from the AllTalk server, if its online.
- current_voices = at_settings["voices"]
- rvccurrent_voices = at_settings["rvcvoices"]
- rvccurrent_character_voice = params["tgwui"]["tgwui_rvc_char_voice"]
- rvccurrent_narrator_voice = params["tgwui"]["tgwui_rvc_char_voice"]
- current_models_available = sorted(at_settings["models_available"])
- current_model_loaded = at_settings["current_model_loaded"]
- current_character_voice = params["tgwui"]["tgwui_character_voice"]
- current_narrator_voice = params["tgwui"]["tgwui_narrator_voice"]
- current_lowvram_capable = at_settings["lowvram_capable"]
- current_lowvram_enabled = at_settings["lowvram_enabled"]
- current_temperature_capable = at_settings["temperature_capable"]
- current_repetitionpenalty_capable = at_settings["repetitionpenalty_capable"]
- current_generationspeed_capable = at_settings["generationspeed_capable"]
- current_pitch_capable = at_settings["pitch_capable"]
- current_deepspeed_capable = at_settings["deepspeed_capable"]
- current_deepspeed_enabled = at_settings["deepspeed_enabled"]
- current_non_quoted_text_is = params.get("tgwui")["tgwui_non_quoted_text_is"] # Use the correct parameter path
- current_languages_capable = at_settings["languages_capable"]
- if at_settings["languages_capable"]:
- language_label = "Languages"
- else:
- language_label = "Model not multi language"
- if current_character_voice not in current_voices:
- current_character_voice = current_voices[0] if current_voices else ""
- params["tgwui"]["tgwui_character_voice"] = current_character_voice
- if current_narrator_voice not in current_voices:
- current_narrator_voice = current_voices[0] if current_voices else ""
- params["tgwui"]["tgwui_narrator_voice"] = current_narrator_voice
- if rvccurrent_character_voice not in rvccurrent_voices:
- rvccurrent_character_voice = rvccurrent_voices[0] if rvccurrent_voices else ""
- params["tgwui"]["tgwui_rvc_char_voice"] = rvccurrent_character_voice
- if rvccurrent_narrator_voice not in rvccurrent_voices:
- rvccurrent_narrator_voice = rvccurrent_voices[0] if rvccurrent_voices else ""
- params["tgwui"]["tgwui_rvc_char_voice"] = rvccurrent_narrator_voice
- tgwui_handle_ttsmodel_dropdown_change.skip_reload = True # Debounce tgwui_tts_dropdown_gr and stop it sending a model reload when it is updated.
-
- return (
- gr.Checkbox(interactive=current_lowvram_capable, value=current_lowvram_enabled),
- gr.Checkbox(interactive=current_deepspeed_capable, value=current_deepspeed_enabled),
- gr.Dropdown(choices=current_voices, value=current_character_voice),
- gr.Dropdown(choices=rvccurrent_voices, value=rvccurrent_character_voice, interactive=True),
- gr.Dropdown(choices=current_voices, value=current_narrator_voice),
- gr.Dropdown(choices=rvccurrent_voices, value=rvccurrent_narrator_voice, interactive=True),
- gr.Dropdown(choices=current_models_available, value=current_model_loaded),
- gr.Dropdown(interactive=current_temperature_capable),
- gr.Dropdown(interactive=current_repetitionpenalty_capable),
- gr.Dropdown(interactive=current_languages_capable, label=language_label),
- gr.Dropdown(interactive=current_generationspeed_capable),
- gr.Dropdown(interactive=current_pitch_capable),
- gr.Dropdown(value=current_non_quoted_text_is),
+ dictate_audio.stream(
+ fn=process_audio,
+ inputs=[dictate_audio, state],
+ outputs=[state, text_output, audio_plot],
+ show_progress=False,
+ concurrency_limit=1,
)
-###############################################################
-# TGWUI # Gradio interface & layout for Text-generation-webui #
-###############################################################
-def ui():
- global at_settings
- alltalk_settings = get_alltalk_settings() # Pull all the current settings from the AllTalk server, if its online.
- with gr.Accordion(params["branding"] + " TTS (Text-gen-webui Remote)"):
- tgwui_available_voices_gr = alltalk_settings["voices"]
- tgwui_rvc_available_voices_gr = alltalk_settings["rvcvoices"]
- # Activate alltalk_tts, Enable autoplay, Show text
- with gr.Row():
- def tgwui_on_load():
- tgwui_update_dropdowns()
- app.load(tgwui_on_load, inputs=None, outputs=None)
- tgwui_activate_tts_gr = gr.Checkbox(value=params["tgwui"]["tgwui_activate_tts"], label="Enable TGWUI TTS")
- tgwui_autoplay_gr = gr.Checkbox(value=params["tgwui"]["tgwui_autoplay_tts"], label="Autoplay TTS Generated")
- tgwui_show_text_gr = gr.Checkbox(value=params["tgwui"]["tgwui_show_text"], label="Show Text in chat")
- # Low vram enable, Deepspeed enable, Link
- with gr.Row():
- tgwui_lowvram_enabled_gr = gr.Checkbox(value=alltalk_settings["lowvram_enabled"] if alltalk_settings["lowvram_capable"] else False, label="Enable Low VRAM Mode", interactive=alltalk_settings["lowvram_capable"])
- tgwui_lowvram_enabled_play_gr = gr.HTML(visible=False)
- tgwui_deepspeed_enabled_gr = gr.Checkbox(value=params["tgwui"]["tgwui_deepspeed_enabled"], label="Enable DeepSpeed", interactive=alltalk_settings["deepspeed_capable"],)
- tgwui_deepspeed_enabled_play_gr = gr.HTML(visible=False)
- tgwui_empty_space_gr = gr.HTML(f"
AllTalk Server & Documentation Link")
+def setup_transcription_directory():
+ """Setup directory for saving transcriptions"""
+ debug_func_entry()
+ trans_script_dir = os.path.dirname(os.path.abspath(__file__))
+ transcripts_dir = os.path.join(trans_script_dir, "transcriptions", "live_dictation")
+ os.makedirs(transcripts_dir, exist_ok=True)
+ return transcripts_dir
- # Model, Language, Character voice
- with gr.Row():
- tgwui_tts_dropdown_gr = gr.Dropdown(choices=models_available, label="TTS Engine/Model", value=current_model_loaded,)
- tgwui_language_gr = gr.Dropdown(languages_list.keys(), label="Languages" if alltalk_settings["languages_capable"] else "Model not multi language", interactive=alltalk_settings["languages_capable"], value=params["tgwui"]["tgwui_language"])
- tgwui_narrator_enabled_gr = gr.Dropdown(choices=[("Enabled", "true"), ("Disabled", "false"), ("Silent", "silent")], label="Narrator Enable", value="true" if params.get("tgwui_narrator_enabled") == "true" else ("silent" if params.get("tgwui_narrator_enabled") == "silent" else "false"))
- # Narrator
- with gr.Row():
- tgwui_available_voices_gr = alltalk_settings["voices"]
- tgwui_default_voice_gr = params["tgwui"]["tgwui_character_voice"]
- if tgwui_default_voice_gr not in tgwui_available_voices_gr:
- tgwui_default_voice_gr = tgwui_available_voices_gr[0] if tgwui_available_voices_gr else ""
- tgwui_character_voice_gr = gr.Dropdown(choices=tgwui_available_voices_gr, label="Character Voice", value=tgwui_default_voice_gr, allow_custom_value=True,)
- tgwui_narr_voice_gr = params["tgwui"]["tgwui_narrator_voice"]
- if tgwui_narr_voice_gr not in tgwui_available_voices_gr:
- tgwui_narr_voice_gr = tgwui_available_voices_gr[0] if tgwui_available_voices_gr else ""
- tgwui_narrator_voice_gr = gr.Dropdown(choices=tgwui_available_voices_gr, label="Narrator Voice", value=tgwui_narr_voice_gr, allow_custom_value=True,)
- tgwui_non_quoted_text_is_gr = gr.Dropdown(choices=[("Character", "character"), ("Narrator", "narrator"), ("Silent", "silent")], label='Narrator unmarked text is', value=params.get("tgwui_non_quoted_text_is", "character"))
+def create_transcript_file(directory, prefix=""):
+ """Create a new transcript file with timestamp"""
+ debug_func_entry()
+ timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
+ filename = (
+ f"{prefix}_dictation_{timestamp}.txt"
+ if prefix
+ else f"dictation_{timestamp}.txt"
+ )
+ return os.path.join(directory, filename)
- # RVC voices
- with gr.Row():
- tgwui_rvc_default_voice_gr = params["tgwui"]["tgwui_rvc_char_voice"]
- tgwui_rvc_narrator_voice_gr = params["tgwui"]["tgwui_rvc_narr_voice"]
- if tgwui_rvc_default_voice_gr not in tgwui_rvc_available_voices_gr:
- tgwui_rvc_default_voice_gr = tgwui_rvc_available_voices_gr[0] if tgwui_rvc_available_voices_gr else ""
- tgwui_rvc_char_voice_gr = gr.Dropdown(choices=tgwui_rvc_available_voices_gr, label="RVC Character Voice", value=tgwui_rvc_default_voice_gr, allow_custom_value=True,)
- if tgwui_rvc_narrator_voice_gr not in tgwui_rvc_available_voices_gr:
- tgwui_rvc_narrator_voice_gr = tgwui_rvc_available_voices_gr[0] if tgwui_rvc_available_voices_gr else ""
- tgwui_rvc_narr_voice_gr = gr.Dropdown(choices=tgwui_rvc_available_voices_gr, label="RVC Narrator Voice", value=tgwui_rvc_narrator_voice_gr, allow_custom_value=True,)
- # Temperature, Repetition Penalty, Speed, pitch
- with gr.Row():
- tgwui_temperature_set_gr = gr.Slider(minimum=0.05, maximum=1, step=0.05, label="Temperature", value=params["tgwui"]["tgwui_temperature_set"], interactive=alltalk_settings["temperature_capable"])
- tgwui_repetitionpenalty_set_gr = gr.Slider(minimum=0.5, maximum=20, step=0.5, label="Repetition Penalty", value=params["tgwui"]["tgwui_repetitionpenalty_set"], interactive=alltalk_settings["repetitionpenalty_capable"])
- with gr.Row():
- tgwui_generationspeed_set_gr = gr.Slider(minimum=0.30, maximum=2.00, step=0.10, label="TTS Speed", value=params["tgwui"]["tgwui_generationspeed_set"], interactive=alltalk_settings["generationspeed_capable"])
- tgwui_pitch_set_gr = gr.Slider(minimum=-10, maximum=10, step=1, label="Pitch", value=params["tgwui"]["tgwui_pitch_set"], interactive=alltalk_settings["pitch_capable"])
+def load_whisper_model(model_name):
+ """Load the Whisper model"""
+ debug_func_entry()
+ print_message(f"Loading Whisper model: {model_name}", "debug_transcribe")
+ device = "cuda" if torch.cuda.is_available() else "cpu"
+ _state['whisper_model'] = whisper.load_model(model_name, device=device)
+ print_message(f"Model loaded on {device}", "debug_transcribe")
+ return _state['whisper_model']
- # Preview speech
- with gr.Row():
- tgwui_preview_text_gr = gr.Text(show_label=False, placeholder="Preview text", elem_id="silero_preview_text", scale=2,)
- tgwui_preview_play_gr = gr.Button("Generate Preview", scale=1)
- tgwui_preview_audio_gr = gr.HTML(visible=False)
- with gr.Row():
- tgwui_protocol_gr = gr.Dropdown(choices=["http://", "https://"], label="AllTalk Server Protocol", value=alltalk_protocol)
- tgwui_protocol_gr.change(tgwui_update_alltalk_protocol, tgwui_protocol_gr, None)
- tgwui_ip_address_port_gr = gr.Textbox(label="AllTalk Server IP:Port", value=alltalk_ip_port)
- tgwui_ip_address_port_gr.change(tgwui_update_alltalk_ip_port, tgwui_ip_address_port_gr, None)
- tgwui_refresh_settings_gr = gr.Button("Refresh settings & voices")
- tgwui_refresh_settings_gr.click(tgwui_update_dropdowns, None, [tgwui_lowvram_enabled_gr, tgwui_deepspeed_enabled_gr, tgwui_character_voice_gr, tgwui_rvc_char_voice_gr, tgwui_narrator_voice_gr, tgwui_rvc_narr_voice_gr, tgwui_tts_dropdown_gr, tgwui_temperature_set_gr, tgwui_repetitionpenalty_set_gr, tgwui_language_gr, tgwui_generationspeed_set_gr, tgwui_pitch_set_gr, tgwui_non_quoted_text_is_gr])
+def butter_bandpass(lowcut, highcut, fs, order=5):
+ """Design a butterworth bandpass filter"""
+ debug_func_entry()
+ nyq = 0.5 * fs
+ low = lowcut / nyq
+ high = highcut / nyq
+ b, a = scipy_signal.butter(order, [low, high], btype="band")
+ return b, a
- with gr.Row():
- tgwui_convert_gr = gr.Button("Remove old TTS audio and leave only message texts")
- tgwui_convert_cancel_gr = gr.Button("Cancel", visible=False)
- tgwui_convert_confirm_gr = gr.Button("Confirm (cannot be undone)", variant="stop", visible=False)
- tgwui_stop_generation_gr = gr.Button("Stop current TTS generation")
- tgwui_stop_generation_gr.click(stop_generate_tts, None, None,)
- # Convert history with confirmation
- convert_arr = [tgwui_convert_confirm_gr, tgwui_convert_gr, tgwui_convert_cancel_gr]
- tgwui_convert_gr.click(lambda: [gr.update(visible=True), gr.update(visible=False), gr.update(visible=True),], None, convert_arr,)
- tgwui_convert_confirm_gr.click(lambda: [ gr.update(visible=False), gr.update(visible=True), gr.update(visible=False),], None, convert_arr,
- ).then(remove_tts_from_history, gradio("history"), gradio("history")
- ).then(chat.save_history, gradio("history", "unique_id", "character_menu", "mode"), None,
- ).then(chat.redraw_html, gradio(ui_chat.reload_arr), gradio("display"))
- tgwui_convert_cancel_gr.click(lambda: [gr.update(visible=False), gr.update(visible=True), gr.update(visible=False),], None, convert_arr,)
+def apply_audio_processing(audio_data, sample_rate, settings):
+ """Apply audio processing based on settings"""
+ debug_func_entry()
+ if not settings.get("enable_audio_processing"):
+ return audio_data
- # Toggle message text in history
- tgwui_show_text_gr.change(lambda x: params["tgwui"].update({"tgwui_show_text": x}), tgwui_show_text_gr, None
- ).then(toggle_text_in_history, gradio("history"), gradio("history")
- ).then(chat.save_history, gradio("history", "unique_id", "character_menu", "mode"), None,
- ).then(chat.redraw_html, gradio(ui_chat.reload_arr), gradio("display"))
+ # Convert to float32 and normalize
+ audio_data = audio_data.astype(np.float32)
- # Event functions to update the parameters in the backend
- tgwui_activate_tts_gr.change(lambda x: params["tgwui"].update({"tgwui_activate_tts": x}), tgwui_activate_tts_gr, None)
- tgwui_autoplay_gr.change(lambda x: params["tgwui"].update({"tgwui_autoplay_tts": x}), tgwui_autoplay_gr, None)
- tgwui_lowvram_enabled_gr.change(lambda x: params["tgwui"].update({"tgwui_lowvram_enabled": x}), tgwui_lowvram_enabled_gr, None)
- tgwui_lowvram_enabled_gr.change(lambda x: send_lowvram_request(x), tgwui_lowvram_enabled_gr, tgwui_lowvram_enabled_play_gr, None)
+ if settings.get("bandpass_filter"):
+ # Apply bandpass filter
+ b, a = butter_bandpass(
+ settings.get("bandpass_low", 85),
+ settings.get("bandpass_high", 3800),
+ sample_rate,
+ )
+ audio_data = scipy_signal.filtfilt(b, a, audio_data)
- # Trigger the send_reload_request function when the dropdown value changes
- tgwui_tts_dropdown_gr.change(tgwui_handle_ttsmodel_dropdown_change, tgwui_tts_dropdown_gr, None)
+ if settings.get("noise_reduction"):
+ # Simple noise reduction
+ noise_floor = np.mean(np.abs(audio_data)) * 2
+ audio_data[np.abs(audio_data) < noise_floor] = 0
- tgwui_deepspeed_enabled_gr.change(lambda x: params["tgwui"].update({"tgwui_deepspeed_enabled": x}), tgwui_deepspeed_enabled_gr, None)
- tgwui_deepspeed_enabled_gr.change(send_deepspeed_request, tgwui_deepspeed_enabled_gr, tgwui_deepspeed_enabled_play_gr, None)
- tgwui_character_voice_gr.change(lambda x: params["tgwui"].update({"tgwui_character_voice": x}), tgwui_character_voice_gr, None)
- tgwui_language_gr.change(lambda x: params["tgwui"].update({"tgwui_language": x}), tgwui_language_gr, None)
+ if settings.get("compression"):
+ # Apply compression
+ threshold = 0.1
+ ratio = 0.5
+ makeup_gain = 1.5
- # TSS Settings
- tgwui_temperature_set_gr.change(lambda x: params["tgwui"].update({"tgwui_temperature_set": x}), tgwui_temperature_set_gr, None)
- tgwui_repetitionpenalty_set_gr.change(lambda x: params["tgwui"].update({"tgwui_repetitionpenalty_set": x}), tgwui_repetitionpenalty_set_gr, None)
- tgwui_generationspeed_set_gr.change(lambda x: params["tgwui"].update({"tgwui_generationspeed_set": x}), tgwui_generationspeed_set_gr, None)
- tgwui_pitch_set_gr.change(lambda x: params["tgwui"].update({"tgwui_pitch_set": x}), tgwui_pitch_set_gr, None)
+ mask = np.abs(audio_data) > threshold
+ audio_data[mask] = np.sign(audio_data[mask]) * (
+ threshold + (np.abs(audio_data[mask]) - threshold) * ratio
+ )
+ audio_data *= makeup_gain
- # Narrator selection actions
- tgwui_narrator_enabled_gr.change(lambda x: params["tgwui"].update({"tgwui_narrator_enabled": x}), tgwui_narrator_enabled_gr, None)
- tgwui_non_quoted_text_is_gr.change(lambda x: params["tgwui"].update({"tgwui_non_quoted_text_is": x}), tgwui_non_quoted_text_is_gr, None)
- tgwui_narrator_voice_gr.change(lambda x: params["tgwui"].update({"tgwui_narrator_voice": x}), tgwui_narrator_voice_gr, None)
-
- # RVC selection actions
- tgwui_rvc_char_voice_gr.change(lambda x: params["tgwui"].update({"tgwui_rvc_char_voice": x}), tgwui_rvc_char_voice_gr, None)
- tgwui_rvc_narr_voice_gr.change(lambda x: params["tgwui"].update({"tgwui_rvc_narr_voice": x}), tgwui_rvc_narr_voice_gr, None)
+ # Final normalization
+ max_val = np.max(np.abs(audio_data))
+ if max_val > 0:
+ audio_data = audio_data / max_val
+
+ return audio_data
+
+
+def identify_speaker(segments):
+ """Simple speaker identification based on segment characteristics"""
+ debug_func_entry()
+ # Very basic approach - could be enhanced with proper speaker diarization
+ current_speaker = 1
+ last_end_time = 0
+
+ for segment in segments:
+ # If there's a significant gap, might be a different speaker
+ if segment["start"] - last_end_time > 1.0:
+ current_speaker = 2 if current_speaker == 1 else 1
+ last_end_time = segment["end"]
+
+ return current_speaker
+
+def visualize_audio_levels(
+ audio_data,
+ sample_rate=16000, # Default sample rate for Whisper
+ window_size=1024,
+ stride=None,
+ y_axis_range=(0, 1), # Fixed range for audio levels
+):
+ """Create an enhanced audio level visualization."""
+ debug_func_entry()
+
+ # Set stride for overlapping windows
+ stride = stride or window_size // 2 # Default: 50% overlap
+
+ # Calculate RMS values for each window
+ windows = range(0, len(audio_data) - window_size, stride)
+ rms_values = [
+ np.sqrt(np.mean(audio_data[i : i + window_size] ** 2)) for i in windows
+ ]
+
+ # Calculate time points in seconds
+ time_points = [(i * stride) / sample_rate for i in range(len(rms_values))]
+
+ # Downsample if too many points (performance optimization)
+ max_points = 1000
+ if len(rms_values) > max_points:
+ step = len(rms_values) // max_points
+ rms_values = rms_values[::step]
+ time_points = time_points[::step]
+
+ # Create the plot
+ fig = go.Figure()
+
+ # Add audio levels line
+ fig.add_trace(
+ go.Scatter(x=time_points, y=rms_values, mode="lines", name="Audio Level")
+ )
+
+ # Add optional dynamic range visualization (e.g., warning thresholds)
+ safe_level = 0.7 # Example threshold for 'safe' audio level
+ fig.add_trace(
+ go.Scatter(
+ x=time_points,
+ y=[safe_level] * len(time_points),
+ mode="lines",
+ line={"dash": "dash", "color": "red"},
+ name="Warning Threshold",
+ )
+ )
+
+ # Update layout
+ fig.update_layout(
+ title="Real-time Audio Levels",
+ xaxis_title="Time (s)",
+ yaxis_title="Level",
+ template="plotly_white", # Use a clean white theme
+ xaxis={"showgrid": True},
+ yaxis={
+ "showgrid": True,
+ "range": y_axis_range, # Set fixed range for audio levels
+ },
+ )
+ return fig
+
+
+def process_audio(audio, state):
+ """Process audio chunk and update transcript"""
+ debug_func_entry()
+ # Default return values for error cases
+ empty_plot = visualize_audio_levels(np.zeros(1024))
+ default_return = (
+ state if state else None,
+ state["display_text"] if state and "display_text" in state else "",
+ empty_plot,
+ )
+
+ if audio is None or state is None or not state.get("is_active"):
+ return default_return
+
+ try:
+ # Get audio data and sample rate
+ sample_rate, audio_data = audio[0], audio[1]
+
+ # Convert to float32 and normalize
+ audio_data = audio_data.astype(np.float32) / 32768.0
+
+ # Calculate RMS energy
+ energy = np.sqrt(np.mean(audio_data**2))
+ print_message(f"Audio energy level: {energy}", "debug_transcribe")
+
+ # Skip if too quiet
+ if energy < 0.005:
+ print_message("Audio too quiet, skipping", "debug_transcribe")
+ plot_data = visualize_audio_levels(audio_data)
+ return state, state["display_text"], plot_data
+
+ # Resample to 16kHz if needed
+ if sample_rate != 16000:
+ print_message(f"Resampling from {sample_rate} to 16000", "debug_transcribe")
+ number_of_samples = round(len(audio_data) * 16000 / sample_rate)
+ audio_data = scipy_signal.resample(audio_data, number_of_samples).astype(
+ np.float32
+ )
+
+ # Simple normalization
+ max_val = np.max(np.abs(audio_data))
+ if max_val > 0:
+ audio_data = audio_data / max_val
+
+ # pylint: disable=line-too-long
+ print_message(
+ f"Processing audio chunk - shape: {audio_data.shape}, range: [{audio_data.min():.3f}, {audio_data.max():.3f}]",
+ "debug_transcribe",
+ )
+
+ if 'whisper_model' not in _state:
+ error_msg = "Whisper model not loaded"
+ print_message(error_msg, "error")
+ return default_return
+
+ # Process with Whisper
+ result = _state['whisper_model'].transcribe(
+ audio_data,
+ language=state.get("source_language", "auto"),
+ task="translate" if state.get("translate_to_english") else "transcribe",
+ temperature=0.0,
+ condition_on_previous_text=True,
+ )
+
+ transcribed_text = result["text"].strip()
+
+ if transcribed_text:
+ # Format text based on timestamps setting
+ if state.get("add_timestamps"):
+ timestamp = datetime.now().strftime("%H:%M:%S")
+ formatted_text = f"[{timestamp}] {transcribed_text}"
+ state["text"] += formatted_text + "\n"
+ # For non-timestamped text, append with space and handle sentence endings
+ if state["text"] and not state["text"].endswith((".", "!", "?", "\n")):
+ state["text"] += " "
+ state["text"] += transcribed_text
+
+ # Update display text
+ state["display_text"] = state["text"]
+
+ # Get confidence scores (if available)
+ try:
+ confidences = [
+ segment.get("confidence", 1.0) for segment in result["segments"]
+ ]
+ avg_confidence = (
+ sum(confidences) / len(confidences) if confidences else 1.0
+ )
+ state["display_text"] += f"\n\nConfidence: {avg_confidence:.2%}"
+ except (KeyError, TypeError, AttributeError):
+ pass # Non-critical confidence calculation failed
+
+ # Save transcript
+ save_transcript(state, state.get("export_format", "txt"))
+
+ # Create audio plot
+ try:
+ plot_data = visualize_audio_levels(audio_data)
+ except (ValueError, TypeError, np.VisualWarning) as plot_error:
+ print_message(
+ f"Plot error (non-critical): {plot_error}", "debug_transcribe"
+ )
+ plot_data = None
+
+ return state, state["display_text"], plot_data
+ return default_return
+
+ except (ValueError, TypeError) as e:
+ error_msg = f"Error processing audio data: {str(e)}"
+ except np.VisualWarning as e:
+ error_msg = f"Error in numerical operations: {str(e)}"
+ except RuntimeError as e:
+ error_msg = f"Error in speech recognition: {str(e)}"
+ except (OSError, IOError) as e:
+ error_msg = f"Error saving transcript: {str(e)}"
+ print_message(error_msg, "error")
+ return default_return
+
+def create_srt_file(segments, filename):
+ """Create an SRT file from segments"""
+ debug_func_entry()
+ srt_content = create_srt_content(segments)
+ with open(filename, "w", encoding="utf-8") as open_srt_f:
+ open_srt_f.write(srt_content)
+
+def save_transcript(state, format_type):
+ """Save transcript in specified format"""
+ debug_func_entry()
+ if not state or not state.get("text"):
+ return
+
+ # Format text based on timestamps setting
+ if state.get("add_timestamps"):
+ save_text = state["text"] # Already formatted with timestamps
+ else:
+ # Format as flowing text with proper sentence spacing
+ sentences = state["text"].replace("\n", " ").split(". ")
+ save_text = ". ".join(s.strip() for s in sentences if s.strip())
+ if not save_text.endswith((".", "!", "?")):
+ save_text += "."
+
+ if format_type == "txt":
+ with open(state["output_file"], "w", encoding="utf-8") as save_transcript_f:
+ save_transcript_f.write(save_text)
+
+ elif format_type == "srt":
+ srt_filename = state["output_file"].replace(".txt", ".srt")
+ create_srt_file(state.get("segments", []), srt_filename)
+
+ elif format_type == "json":
+ json_filename = state["output_file"].replace(".txt", ".json")
+ metadata = {
+ "text": save_text,
+ "segments": state.get("segments", []),
+ "word_count": len(save_text.split()),
+ "model": state["model_name"],
+ "settings": state["settings"],
+ }
+ with open(json_filename, "w", encoding="utf-8") as save_transcript_f:
+ json.dump(metadata, save_transcript_f, indent=2)
+
+ print_message(f"Saved transcript to: {state['output_file']}", "debug_transcribe")
+
+
+def start_new_dictation(
+ model_name,
+ prefix,
+ language,
+ source_lang,
+ translate,
+ export_fmt,
+ timestamps,
+ diarization,
+ audio_proc,
+ bandpass,
+ noise_red,
+ compress,
+ silence_thresh,
+ bp_low,
+ bp_high,
+):
+ """Load model and prepare for dictation"""
+ debug_func_entry()
+ try:
+ # Load model into state
+ load_whisper_model(model_name)
+
+ transcripts_dir = setup_transcription_directory()
+ output_file = create_transcript_file(transcripts_dir, prefix)
+
+ state = {
+ "text": "",
+ "display_text": "",
+ "output_file": output_file,
+ "chunks": 0,
+ "is_active": True,
+ "model_name": model_name,
+ "word_count": 0,
+ # Settings
+ "language": language,
+ "source_language": source_lang,
+ "translate_to_english": translate,
+ "export_format": export_fmt,
+ "add_timestamps": timestamps,
+ "enable_diarization": diarization,
+ "settings": {
+ "enable_audio_processing": audio_proc,
+ "bandpass_filter": bandpass,
+ "noise_reduction": noise_red,
+ "compression": compress,
+ "silence_threshold": silence_thresh,
+ "bandpass_low": bp_low,
+ "bandpass_high": bp_high,
+ },
+ }
+
+ print_message(f"Started new transcription: {output_file}", "debug_transcribe")
+ message = "Model loaded! Click the microphone icon to start/stop recording."
+
+ return (
+ state,
+ message,
+ gr.update(interactive=True),
+ gr.update(interactive=False),
+ gr.update(interactive=True),
+ )
+ except (whisper.RuntimeError, torch.cuda.OutOfMemoryError) as e:
+ error_msg = f"Model error: {str(e)}"
+ return_msg = "Error: Model could not be loaded - try a smaller model"
+ except (OSError, IOError) as e:
+ error_msg = f"File system error: {str(e)}"
+ return_msg = "Error: Could not create necessary files/directories"
+ except ValueError as e:
+ error_msg = f"Invalid parameter: {str(e)}"
+ return_msg = "Error: Invalid configuration parameters provided"
+ print_message(error_msg, "error")
+ return (
+ None,
+ return_msg,
+ gr.update(interactive=False),
+ gr.update(interactive=True),
+ gr.update(interactive=False),
+ )
+
+def finish_dictation(state, dictate_audio, text_output, audio_plot):
+ """End dictation and cleanup."""
+ debug_func_entry()
+
+ if state is not None:
+ print_message(f"Finished dictation: {state.get('output_file', 'Unknown')}", "debug_transcribe")
+ save_transcript(state, state.get("export_format", "txt"))
+ state["is_active"] = False
+
+ # Reset the audio stream handlers only if dictate_audio is a valid component
+ if isinstance(dictate_audio, gr.Audio):
+ reset_audio_stream_handlers(dictate_audio, state, text_output, audio_plot)
+
+ # Clean up the model
+ if 'whisper_model' in _state:
+ _state['whisper_model'].to("cpu") # Move model to CPU first
+ del _state['whisper_model']
+
+ # Clean up GPU resources
+ cleanup_gpu_memory()
+ torch.cuda.synchronize()
+
+ return (
+ state,
+ "Transcription finished. Ready for a new session!",
+ gr.update(interactive=True),
+ gr.update(interactive=True),
+ gr.update(interactive=False),
+ )
- # Play preview
- tgwui_preview_text_gr.submit(voice_preview, tgwui_preview_text_gr, tgwui_preview_audio_gr)
- tgwui_preview_play_gr.click(voice_preview, tgwui_preview_text_gr, tgwui_preview_audio_gr)
##################################################################
# _ _ _ _____ _ _ ____ _ _ #
@@ -1293,19 +2466,18 @@ def ui():
# #
##################################################################
-if gradio_enabled == True:
- import importlib
- import gradio as gr
+if gradio_enabled is True:
# Get the directory of the current script
script_dir = Path(__file__).resolve().parent
- my_current_url = "null"
- at_default_voice_gr = params["tgwui"]["tgwui_character_voice"]
+ # at_default_voice_gr = config.tgwui.tgwui_character_voice # Disabled as I think now un-used
####################################################
# Dynamically import the Themes builder for Gradio #
####################################################
- themesmodule_path = this_dir / 'system' / 'gradio_pages' / 'themes' / 'loadThemes.py'
+ themesmodule_path = (
+ this_dir / "system" / "gradio_pages" / "themes" / "loadThemes.py"
+ )
# Add the directory containing the module to the system path
- sys.path.insert(0, str(this_dir / 'system' / 'gradio_pages' / 'themes'))
+ sys.path.insert(0, str(this_dir / "system" / "gradio_pages" / "themes"))
# Import the module dynamically
spec = importlib.util.spec_from_file_location("loadThemes", themesmodule_path)
loadThemes = importlib.util.module_from_spec(spec)
@@ -1319,211 +2491,178 @@ if gradio_enabled == True:
###########################################################
# Check if the script is running in standalone mode
if script_dir.name == "alltalk_tts":
- # Running in standalone mode, add the script's directory to the Python module search path
+ # Running in standalone mode, add the script's
+ # directory to the Python module search path
sys.path.insert(0, str(script_dir))
else:
- # Running as part of text-generation-webui, add the parent directory of "alltalk_tts" to the Python module search path
+ # Running as part of text-generation-webui, add the parent
+ # directory of "alltalk_tts" to the Python module search path
sys.path.insert(0, str(script_dir.parent))
- ########################################################################################
- # Dynamically import modules and set path based on being a standalone or part of TGWUI #
- ########################################################################################
- # Function to get TTS engines data
- def get_tts_engines_data():
- global engines_available, engine_loaded, selected_model
- tts_engines_file = os.path.join(this_dir, "system", "tts_engines", "tts_engines.json")
- with open(tts_engines_file, "r") as f:
- tts_engines_data = json.load(f)
- engines_available = [engine["name"] for engine in tts_engines_data["engines_available"]]
- engine_loaded = tts_engines_data["engine_loaded"]
- selected_model = tts_engines_data["selected_model"]
- return engines_available, engine_loaded, selected_model
# Determine if running as standalone or within another project
+ # TODO: Want to re-check on this and how its impacting TGWUI. May not be needed anymore!
if __name__ == "__main__" or "text-generation-webui" not in this_dir.parts:
# Standalone execution
base_package = None # No base package needed for absolute imports
- # print("standalone execution", __name__)
- from system.gradio_pages.alltalk_documentation import alltalk_documentation
- from system.gradio_pages.alltalk_generation_help import alltalk_generation_help
- from system.gradio_pages.alltalk_about import alltalk_about
- from system.gradio_pages.alltalk_diskspace import get_disk_interface
- from system.gradio_pages.api_documentation import api_documentation
- if params["firstrun_splash"]:
- from system.gradio_pages.alltalk_welcome import alltalk_welcome
else:
# Running within text-generation-webui
# Dynamically build the base package using the current folder name
- from .system.gradio_pages.alltalk_documentation import alltalk_documentation
- from .system.gradio_pages.alltalk_generation_help import alltalk_generation_help
- from .system.gradio_pages.alltalk_about import alltalk_about
- from .system.gradio_pages.alltalk_diskspace import get_disk_interface
- from .system.gradio_pages.api_documentation import api_documentation
- if params["firstrun_splash"]:
- from .system.gradio_pages.alltalk_welcome import alltalk_welcome
- # print("Running within text-generation-webui", __name__)
current_folder_name = this_dir.name
base_package = f"extensions.{current_folder_name}"
- # Function to dynamically import a module from a given path
- def dynamic_import(module_name, package=None):
+ def dynamic_import(module_path, package=None):
+ """
+ Dynamically import a module from a given path.
+
+ Args:
+ module_path (str): Path or name of module to import
+ package (str, optional): Package to import module from. Defaults to None.
+
+ Returns:
+ module: Imported module object or None if import fails
+ """
+ debug_func_entry()
try:
if package:
- module = importlib.import_module(module_name, package=package)
+ new_module = importlib.import_module(module_path, package=package)
else:
- module = importlib.import_module(module_name)
- return module
+ new_module = importlib.import_module(module_path)
+ return new_module
except ModuleNotFoundError as e:
- print(f"Module not found: {module_name} - {e}")
+ print_message(
+ f"Module not found: {module_path} - {str(e)}", message_type="error"
+ )
return None
- except Exception as e:
- print(f"Error importing {module_name}: {e}")
+ except (ImportError, SyntaxError) as e:
+ print_message(f"Error importing {module_path}: {str(e)}", message_type="error")
return None
- # Call the function to populate engines_available
- get_tts_engines_data()
+ def load_engine_configs(_state):
+ """Load engine configs into globals for Gradio settings pages"""
+ for engine_name in _state.get('srv_engines_available', []):
+ if base_package:
+ module_name = f"{base_package}.system.tts_engines.{engine_name}.{engine_name}_settings_page"
+ else:
+ module_name = f"system.tts_engines.{engine_name}.{engine_name}_settings_page"
+
+ module = dynamic_import(module_name, base_package)
+ if module:
+ # Load the engine's config from its JSON file
+ json_file_path = os.path.join(this_dir, "system", "tts_engines", engine_name, "model_settings.json")
+ try:
+ with open(json_file_path, "r", encoding="utf-8") as config_file:
+ globals()[f"{engine_name}_model_config_data"] = json.load(config_file)
+ except FileNotFoundError:
+ print_message(f"Could not find settings file for {engine_name}")
+ except json.JSONDecodeError:
+ print_message("Invalid JSON in settings file for {engine_name}")
+
+ # Call this before creating the Gradio interface to dynamically import TTS Engine pages
+ load_engine_configs(_state)
- # Dynamically import modules and load JSON data for each available engine
- for engine_name in engines_available:
- if base_package:
- module_name = f"{base_package}.system.tts_engines.{engine_name}.{engine_name}_settings_page"
- else:
- module_name = f"system.tts_engines.{engine_name}.{engine_name}_settings_page"
- module = dynamic_import(module_name, base_package)
- if module:
- globals()[f"{engine_name}_at_gradio_settings_page"] = getattr(module, f"{engine_name}_at_gradio_settings_page")
- globals()[f"{engine_name}_model_update_settings"] = getattr(module, f"{engine_name}_model_update_settings")
- json_file_path = os.path.join(this_dir, "system", "tts_engines", engine_name, "model_settings.json")
- with open(json_file_path, "r") as f:
- globals()[f"{engine_name}_model_config_data"] = json.load(f)
-
###########################################
# Finishing Dynamically importing Modules #
###########################################
- def confirm(message):
- return gr.Interface.fn("confirmation", f"""""")
-
- def save_config_data():
- try:
- # Save the updated JSON data to confignew.json
- with open(os.path.join(this_dir, "confignew.json"), "w") as f:
- json.dump(params, f, indent=4)
- reload_config()
- except Exception as e:
- error_message = "Failed to save config data."
- print(f"[{branding}TTS] \033[91mError:\033[0m {error_message}")
- print(f"[{branding}TTS] \033[91mError Details:\033[0m {str(e)}")
-
- def reload_config():
- # Send a GET request to the /reload_config endpoint
- if running_on_google_colab:
- reload_config_url = f"{tunnel_url_1}/api/reload_config"
- elif running_in_docker:
- reload_config_url = f"{docker_url}/api/reload_config"
- else:
- reload_config_url = f"http://localhost:{params['api_def']['api_port_number']}/api/reload_config"
- try:
- response = requests.get(reload_config_url)
- if response.status_code == 200:
- print(f"[{branding}TTS] Reloaded config")
- else:
- print(f"[{branding}TTS] Failed to reload config file")
- except ConnectionError as e:
- error_message = "Connection to the server has been lost. Please reload the webpage."
- print(f"[{branding}TTS] \033[91mError:\033[0m {error_message}")
- except Exception as e:
- error_message = "An error occurred while reloading the config file."
- print(f"[{branding}TTS] \033[91mError:\033[0m {error_message}")
- print(f"[{branding}TTS] \033[91mError Details:\033[0m {str(e)}")
-
##########################################################################################
# Pulls the current AllTalk Server settings & updates gradio when Refresh button pressed #
##########################################################################################
def at_update_dropdowns():
- global at_settings
- at_settings = get_alltalk_settings() # Pull all the current settings from the AllTalk server, if its online.
- engines_available = at_settings["engines_available"]
- current_engine_loaded = at_settings["current_engine_loaded"]
- current_voices = at_settings["voices"]
- current_character_voice = params["tgwui"]["tgwui_character_voice"]
- current_narrator_voice = params["tgwui"]["tgwui_narrator_voice"]
- rvccurrent_voices = at_settings["rvcvoices"]
- rvccurrent_character_voice = params["rvc_settings"]["rvc_char_model_file"]
- rvccurrent_narrator_voice = params["rvc_settings"]["rvc_narr_model_file"]
- current_temperature_capable = at_settings["temperature_capable"]
- current_repetitionpenalty_capable = at_settings["repetitionpenalty_capable"]
- current_languages_capable = at_settings["languages_capable"]
- current_generationspeed_capable = at_settings["generationspeed_capable"]
- current_pitch_capable = at_settings["pitch_capable"]
- current_languages_capable = at_settings["languages_capable"]
- if at_settings["languages_capable"]:
- language_label = "Languages"
- else:
- language_label = "Model not multi language"
- if at_settings["streaming_capable"]:
+ """Update the gradio dropdowns with the current settings from state"""
+ debug_func_entry()
+
+ # Call get_alltalk_settings to update state with latest values
+ get_alltalk_settings()
+
+ # Build stream choices if streaming is available
+ if _state['srv_settings_capabilities']['streaming_capable']:
gen_choices = [("Standard", "false"), ("Streaming (Disable Narrator)", "true")]
else:
gen_choices = [("Standard", "false")]
- if current_character_voice not in current_voices:
- current_character_voice = current_voices[0] if current_voices else ""
- if current_narrator_voice not in current_voices:
- current_narrator_voice = current_voices[0] if current_voices else ""
- if rvccurrent_character_voice not in rvccurrent_voices:
- rvccurrent_character_voice = rvccurrent_voices[0] if rvccurrent_voices else ""
- if rvccurrent_narrator_voice not in rvccurrent_voices:
- rvccurrent_narrator_voice = rvccurrent_voices[0] if rvccurrent_voices else ""
+
+ # Set language label based on capability
+ language_label = "Languages" if _state[
+ 'srv_settings_capabilities'
+ ][
+ 'languages_capable'
+ ] else "Model not multi language"
+
+ # Handle default voices if they're not in available voices
+ current_character_voice = _state['srv_character_voice']
+ if current_character_voice not in _state['srv_current_voices']:
+ current_character_voice = _state['srv_current_voices'][0] if _state['srv_current_voices'] else ""
+
+ current_narrator_voice = _state['srv_narrator_voice']
+ if current_narrator_voice not in _state['srv_current_voices']:
+ current_narrator_voice = _state['srv_current_voices'][0] if _state['srv_current_voices'] else ""
+
+ rvc_character_voice = _state['srv_rvc_character_voice']
+ if rvc_character_voice not in _state['srv_current_rvcvoices']:
+ rvc_character_voice = _state['srv_current_rvcvoices'][0] if _state['srv_current_rvcvoices'] else ""
+
+ rvc_narrator_voice = _state['srv_rvc_narrator_voice']
+ if rvc_narrator_voice not in _state['srv_current_rvcvoices']:
+ rvc_narrator_voice = _state['srv_current_rvcvoices'][0] if _state['srv_current_rvcvoices'] else ""
+
+ # Return all Gradio component updates
return (
gr.Dropdown(choices=gen_choices, interactive=True),
- gr.Dropdown(choices=current_voices, value=current_character_voice, interactive=True),
- gr.Dropdown(choices=rvccurrent_voices, value=rvccurrent_character_voice, interactive=True),
- gr.Dropdown(choices=current_voices, value=current_narrator_voice, interactive=True),
- gr.Dropdown(choices=rvccurrent_voices, value=rvccurrent_narrator_voice, interactive=True),
- gr.Slider(interactive=current_generationspeed_capable),
- gr.Slider(interactive=current_pitch_capable),
- gr.Slider(interactive=current_temperature_capable),
- gr.Slider(interactive=current_repetitionpenalty_capable),
- gr.Dropdown(interactive=current_languages_capable, label=language_label),
- gr.Dropdown(choices=models_available, value=current_model_loaded),
- gr.Dropdown(choices=engines_available, value=current_engine_loaded),
- )
-
+ gr.Dropdown(choices=_state['srv_current_voices'], value=current_character_voice, interactive=True),
+ gr.Dropdown(choices=_state['srv_current_rvcvoices'], value=rvc_character_voice, interactive=True),
+ gr.Dropdown(choices=_state['srv_current_voices'], value=current_narrator_voice, interactive=True),
+ gr.Dropdown(choices=_state['srv_current_rvcvoices'], value=rvc_narrator_voice, interactive=True),
+ gr.Slider(interactive=_state['srv_settings_capabilities']['generationspeed_capable']),
+ gr.Slider(interactive=_state['srv_settings_capabilities']['pitch_capable']),
+ gr.Slider(interactive=_state['srv_settings_capabilities']['temperature_capable']),
+ gr.Slider(interactive=_state['srv_settings_capabilities']['repetitionpenalty_capable']),
+ gr.Dropdown(interactive=_state['srv_settings_capabilities']['languages_capable'], label=language_label),
+ gr.Dropdown(choices=_state['srv_models_available'], value=_state['srv_current_model_loaded']),
+ gr.Dropdown(choices=_state['srv_engines_available'], value=_state['srv_current_engine_loaded'])
+ )
+
######################################################################################
# Sends request to reload the current TTS engine & set the default TTS engine loaded #
######################################################################################
- def set_engine_loaded(engine_name):
- global engine_loaded, selected_model, tts_engines_data, models_available, tts_engines_path, tts_engines_backup_path, at_update_dropdowns
+ def set_engine_loaded(chosen_engine_name):
+ """
+ Send an engine reload request and update tts_engines list with the chosen engine
+ and selected model.
- # Load the JSON with a retry mechanism
- safe_load_json(tts_engines_path, tts_engines_backup_path)
-
- # Iterate and update engines
- for engine in tts_engines_data["engines_available"]:
- if engine["name"] == engine_name:
- tts_engines_data["engine_loaded"] = engine_name
- tts_engines_data["selected_model"] = engine["selected_model"]
- break
+ Args:
+ chosen_engine_name (str): Name of the engine to load
- # Backup the existing file before writing
- shutil.copy(tts_engines_path, tts_engines_backup_path)
+ Returns:
+ tuple: Success message and updated UI elements
+
+ Raises:
+ RequestsConnectionError: If unable to connect to TTS engine
+ TimeoutError: If engine doesn't become ready within retry limit
+ """
+ debug_func_entry()
- # Write the updated JSON data safely
- with open(tts_engines_path, "w") as f:
- try:
- json.dump(tts_engines_data, f, indent=4)
- except Exception as e:
- print(f"Error writing to {tts_engines_path}: {e}")
- # Restore the backup if writing failed
- shutil.copy(tts_engines_backup_path, tts_engines_path)
- raise Exception("Failed to save tts_engines.json, restored from backup.")
+ # Use the config system to update the engine
+ tts_engines_config_loaded = AlltalkTTSEnginesConfig.get_instance()
+ # Update the engine and selected model
+ if tts_engines_config_loaded.is_valid_engine(chosen_engine_name):
+ # Find the engine and its selected_model in engines_available
+ for engine in tts_engines_config.engines_available:
+ if engine.name == chosen_engine_name:
+ # Update both engine_loaded and selected_model at top level
+ tts_engines_config_loaded.engine_loaded = engine.name
+ tts_engines_config_loaded.selected_model = engine.selected_model
+ break
- engine_loaded = engine_name
- selected_model = tts_engines_data["selected_model"]
+ # Change engine in config - this updates selected_model automatically
+ tts_engines_config_loaded.change_engine(chosen_engine_name)
+ tts_engines_config_loaded.save()
+
+ # Update state
+ _state['srv_current_engine_loaded'] = chosen_engine_name
+ _state['srv_current_model_loaded'] = tts_engines_config.selected_model
+
+ # Save changes
+ tts_engines_config_loaded.save()
- # Add a brief delay to ensure file is fully written
- time.sleep(0.1) # 100ms delay
# Restart the subprocess
restart_subprocess()
@@ -1533,41 +2672,61 @@ if gradio_enabled == True:
retries = 0
while retries < max_retries:
try:
- response = requests.get(url)
- if response.status_code == 200 and response.text == "Ready":
+ ready_response = requests.get(url, timeout=(5, 30))
+ if ready_response.status_code == 200 and ready_response.text == "Ready":
break
- except requests.exceptions.RequestException:
+ except RequestException:
pass
-
+
retries += 1
if retries == max_retries:
- raise Exception("Failed to connect to the TTS engine after multiple retries.")
-
+ raise RequestsConnectionError(
+ "Failed to connect to the TTS engine after maximum retries. "
+ f"Attempted {max_retries} times over {max_retries * retry_delay} seconds."
+ )
+
time.sleep(retry_delay)
-
- models_available = at_settings["models_available"]
# Update the dropdowns directly
- print(f"[{branding}ENG]")
- print(f"[{branding}ENG] {branding}Server Ready")
- gen_stream_value, gen_char_choices, rvcgen_char_choices, gen_narr_choices, rvcgen_narr_choices, gen_speed_interactive, gen_pitch_interactive, gen_temperature_interactive, gen_repetition_interactive, gen_lang_interactive, model_choices_gr_interactive, engine_choices_interactive = at_update_dropdowns()
+ print_message("")
+ print_message("Server Ready")
+
+ # Update dropdowns - this line would change
+ return_values = at_update_dropdowns()
+
+ return (
+ "TTS Engine changed successfully!", # Output message
+ *return_values, # Unpack all the UI update values
+ )
- return ("TTS Engine changed successfully!", # This is your output message
- gen_stream_value, gen_char_choices, rvcgen_char_choices, gen_narr_choices, rvcgen_narr_choices, gen_speed_interactive, gen_pitch_interactive, gen_temperature_interactive, gen_repetition_interactive, gen_lang_interactive, model_choices_gr_interactive, engine_choices_interactive
- )
-
###############################
# Sends voice2rvc request off #
###############################
def voice2rvc(audio, rvc_voice, rvc_pitch, rvc_f0method):
+ """
+ Convert voice audio using RVC (Retrieval-based Voice Conversion).
+
+ Args:
+ audio: Input audio (tuple of sample_rate and data, or file path)
+ rvc_voice: Selected RVC voice model
+ rvc_pitch: Pitch adjustment value
+ rvc_f0method: F0 extraction method
+
+ Returns:
+ str: Path to converted audio file, or None if conversion fails
+ """
+ debug_func_entry()
# Save the uploaded or recorded audio to a file
input_tts_path = this_dir / "outputs" / "voice2rvcInput.wav"
+
if rvc_voice == "Disabled":
- print(f"[{branding}ENG] Voice2RVC Convert: No RVC voice was selected")
- return
- if audio == None:
- print(f"[{branding}ENG] Voice2RVC Convert: No recorded audio was provided")
- return
+ print_message("Voice2RVC Convert: No RVC voice was selected")
+ return None
+
+ if audio is None:
+ print_message("Voice2RVC Convert: No recorded audio was provided")
+ return None
+
if isinstance(audio, tuple):
sample_rate, audio_data = audio
# Save the numpy array as a wav file
@@ -1575,292 +2734,269 @@ if gradio_enabled == True:
else:
# It's a file path
os.rename(audio, input_tts_path)
-
+
# Define the output path for the processed audio
output_rvc_path = this_dir / "outputs" / "voice2rvcOutput.wav"
- if running_on_google_colab:
- url = f"{tunnel_url_1}/api/voice2rvc"
- elif running_in_docker:
- url = f"{docker_url}/api/voice2rvc"
- else:
- url = f"{alltalk_protocol}{alltalk_ip_port}/api/voice2rvc"
+ voice2rvc_url = build_url("voice2rvc")
# Submit the paths to the API endpoint
- response = requests.post(url, data={
- "input_tts_path": str(input_tts_path),
- "output_rvc_path": str(output_rvc_path),
- "pth_name": rvc_voice,
- "pitch": rvc_pitch,
- "method": str(rvc_f0method),
- })
+ voice2rvc_response = requests.post(
+ voice2rvc_url,
+ data={
+ "input_tts_path": str(input_tts_path),
+ "output_rvc_path": str(output_rvc_path),
+ "pth_name": rvc_voice,
+ "pitch": rvc_pitch,
+ "method": str(rvc_f0method),
+ },
+ timeout=(5, 30) # 5 sec connect timeout, 30 sec read timeout
+ )
- if response.status_code == 200:
- result = response.json()
+ if voice2rvc_response.status_code == 200:
+ result = voice2rvc_response.json()
if result["status"] == "success":
- result_path = result["output_path"]
- else:
- result_path = None
- else:
- result_path = None
-
- return result_path
-
+ return result["output_path"]
+
+ return None
+
##################################################################################################
# Sends request to reload the current TTS engine model & set the default TTS engine model loaded #
##################################################################################################
- def change_model_loaded(engine_name, selected_model):
- global at_update_dropdowns
+ def change_model_loaded(new_engine_name, new_selected_model):
+ """
+ Change the currently loaded TTS model and update configuration.
+
+ Args:
+ new_engine_name (str): Name of TTS engine to use
+ new_selected_model (str): Name of model to load
+
+ Returns:
+ tuple: Status message and updated UI component values, or
+ dict: Error status and message if request fails
+ """
+ debug_func_entry()
try:
- print(f"[{branding}ENG]")
- print(f"[{branding}ENG] \033[94mChanging model loaded. Please wait.\033[00m")
- print(f"[{branding}ENG]")
- if running_on_google_colab:
- url = f"{tunnel_url_1}/api/reload"
- elif running_in_docker:
- url = f"{docker_url}/api/reload"
- else:
- url = f"{alltalk_protocol}{alltalk_ip_port}/api/reload"
- payload = {"tts_method": selected_model}
- response = requests.post(url, params=payload)
- response.raise_for_status() # Raises an HTTPError for bad responses
+ print_message("")
+ print_message("\033[94mChanging model loaded. Please wait.\033[00m")
+ print_message("")
+ model_loaded_url = build_url("reload")
+ payload = {"tts_method": new_selected_model}
+ model_loaded_response = requests.post(model_loaded_url, params=payload, timeout=(5, 30))
+ model_loaded_response.raise_for_status() # Raises an HTTPError for bad responses
# Update the tts_engines.json file
- tts_engines_file = os.path.join(this_dir, "system", "tts_engines", "tts_engines.json")
- with open(tts_engines_file, "r") as f:
- tts_engines_data = json.load(f)
- tts_engines_data["selected_model"] = f"{selected_model}"
- for engine in tts_engines_data["engines_available"]:
- if engine["name"] == engine_name:
- engine["selected_model"] = f"{selected_model}"
- break
- with open(tts_engines_file, "w") as f:
- json.dump(tts_engines_data, f)
- print(f"[{branding}ENG]")
- print(f"[{branding}ENG] {branding}Server Ready")
- gen_stream_value, gen_char_choices, rvcgen_char_choices, gen_narr_choices, rvcgen_narr_choices, gen_speed_interactive, gen_pitch_interactive, gen_temperature_interactive, gen_repetition_interactive, gen_lang_interactive, model_choices_gr_interactive, engine_choices_interactive = at_update_dropdowns()
- return ("TTS Model changed successfully!", # This is your output message
- gen_stream_value, gen_char_choices, rvcgen_char_choices, gen_narr_choices, rvcgen_narr_choices, gen_speed_interactive, gen_pitch_interactive, gen_temperature_interactive, gen_repetition_interactive, gen_lang_interactive, model_choices_gr_interactive, engine_choices_interactive
+ tts_engines_file = os.path.join(
+ this_dir, "system", "tts_engines", "tts_engines.json"
)
- except requests.exceptions.RequestException as e:
+ with open(tts_engines_file, "r", encoding="utf-8") as change_model_f:
+ tts_engines_data = json.load(change_model_f)
+ tts_engines_data["selected_model"] = f"{new_selected_model}"
+ for engine in tts_engines_data["engines_available"]:
+ if engine["name"] == new_engine_name:
+ engine["selected_model"] = f"{new_selected_model}"
+ break
+ with open(tts_engines_file, "w", encoding="utf-8") as change_model_f:
+ json.dump(tts_engines_data, change_model_f)
+ print_message("")
+ print_message("Server Ready")
+
+ return_values = at_update_dropdowns()
+
+ return ("TTS Model changed successfully!", *return_values)
+
+ except RequestException as e:
# Handle the HTTP request error
- print(f"[{branding}TTS] \033[91mWarning\033[0m Error during request to webserver process: Status code:\n{e}")
+ print_message(
+ f"Error during request to webserver process: Status code:\n{e}",
+ message_type="warning",
+ )
return {"status": "error", "message": str(e)}
- def update_settings_tg(activate, autoplay, show_text, language, narrator_enabled):
- # Update the config_data dictionary with the new values
- params["activate"] = activate == "Enabled"
- params["autoplay"] = autoplay == "Enabled"
- params["show_text"] = show_text == "Enabled"
- params["language"] = language
- params["narrator_enabled"] = narrator_enabled
- print(f"[{branding}TTS] Default Text-gen-webui Settings Saved")
- save_config_data()
- return "Settings updated successfully!"
+ debugging_options = config.debugging
+ debugging_choices = list(vars(debugging_options).keys())
+ default_values = [key for key, value in vars(debugging_options).items() if value]
- debugging_options = params['debugging']
- debugging_choices = list(debugging_options.keys())
- default_values = [key for key, value in debugging_options.items() if value]
-
- def update_settings_at(delete_output_wavs, gradio_interface, gradio_port_number, output_folder, api_port_number, gr_debug_tts, transcode_audio_format, generate_help_page, voice2rvc_page, tts_generator_page, tts_engines_settings_page, alltalk_documentation_page, api_documentation_page):
- # Update the config_data dictionary with the new values
- params["delete_output_wavs"] = delete_output_wavs
- params["gradio_interface"] = gradio_interface == "Enabled"
- params["output_folder"] = output_folder
- params['api_def']['api_port_number'] = api_port_number
- params["gradio_port_number"] = gradio_port_number
- for key in debugging_options.keys():
- params['debugging'][key] = key in gr_debug_tts
- params["transcode_audio_format"] = transcode_audio_format
-
- # Update gradio_pages settings
- params["gradio_pages"] = {
- "Generate_Help_page": generate_help_page,
- "Voice2RVC_page": voice2rvc_page,
- "TTS_Generator_page": tts_generator_page,
- "TTS_Engines_Settings_page": tts_engines_settings_page,
- "alltalk_documentation_page": alltalk_documentation_page,
- "api_documentation_page": api_documentation_page
- }
-
- output_message = "Settings updated successfully!"
- print(f"[{branding}TTS] Default Settings Saved")
- save_config_data()
- return output_message
-
- def update_settings_api(api_length_stripping, api_legacy_ip_address, api_allowed_filter, api_max_characters, api_use_legacy_api, api_text_filtering, api_narrator_enabled, api_text_not_inside, api_language, api_output_file_name, api_output_file_timestamp, api_autoplay, api_autoplay_volume):
- # Update the params dictionary with the new values
- params["api_def"]["api_length_stripping"] = api_length_stripping
- params["api_def"]["api_allowed_filter"] = api_allowed_filter
- params["api_def"]["api_max_characters"] = api_max_characters
- params["api_def"]["api_use_legacy_api"] = api_use_legacy_api == "AllTalk v1 API (Legacy)"
- params["api_def"]["api_legacy_ip_address"] = api_legacy_ip_address
- params["api_def"]["api_text_filtering"] = api_text_filtering
- params["api_def"]["api_narrator_enabled"] = api_narrator_enabled
- params["api_def"]["api_text_not_inside"] = api_text_not_inside
- params["api_def"]["api_language"] = api_language
- params["api_def"]["api_output_file_name"] = api_output_file_name
- params["api_def"]["api_output_file_timestamp"] = api_output_file_timestamp == "Timestamp files"
- params["api_def"]["api_autoplay"] = api_autoplay == "Play remotely"
- params["api_def"]["api_autoplay_volume"] = api_autoplay_volume
- output_message = "Default API settings updated successfully!"
- print(f"[{branding}TTS] API Settings Saved")
- save_config_data()
- return output_message
-
- def ensure_directory_exists(directory):
- if not os.path.exists(directory):
- os.makedirs(directory)
-
- def load_file_urls(json_path):
- with open(json_path, 'r') as f:
- return json.load(f)
-
- def download_file(url, dest_path):
- response = requests.get(url, stream=True)
- response.raise_for_status()
- with open(dest_path, 'wb') as f:
- for chunk in response.iter_content(chunk_size=8192):
- f.write(chunk)
-
- def update_rvc_settings(rvc_enabled, rvc_char_model_file, rvc_narr_model_file, split_audio, autotune, pitch,
- filter_radius, index_rate, rms_mix_rate, protect, hop_length, f0method, embedder_model, training_data_size, progress=gr.Progress(track_tqdm=True)):
- params["rvc_settings"]["rvc_enabled"] = rvc_enabled
- params["rvc_settings"]["rvc_char_model_file"] = rvc_char_model_file
- params["rvc_settings"]["rvc_narr_model_file"] = rvc_narr_model_file
- params["rvc_settings"]["split_audio"] = split_audio
- params["rvc_settings"]["autotune"] = autotune
- params["rvc_settings"]["pitch"] = pitch
- params["rvc_settings"]["filter_radius"] = filter_radius
- params["rvc_settings"]["index_rate"] = index_rate
- params["rvc_settings"]["rms_mix_rate"] = rms_mix_rate
- params["rvc_settings"]["protect"] = protect
- params["rvc_settings"]["hop_length"] = hop_length
- params["rvc_settings"]["f0method"] = f0method
- params["rvc_settings"]["embedder_model"] = embedder_model
- params["rvc_settings"]["training_data_size"] = training_data_size
- if rvc_enabled:
- base_dir = os.path.join(this_dir, "models", "rvc_base")
- rvc_voices_dir = os.path.join(this_dir, "models", "rvc_voices")
- ensure_directory_exists(base_dir)
- ensure_directory_exists(rvc_voices_dir)
- json_path = os.path.join(this_dir, "system", "tts_engines", "rvc_files.json")
- file_urls = load_file_urls(json_path)
- for idx, file in enumerate(file_urls):
- if not os.path.exists(os.path.join(base_dir, file)):
- progress(idx / len(file_urls), desc=f"Downloading Required Files: {file}...")
- print(f"[{branding}TTS] Downloading {file}...") # Print statement for terminal
- download_file(file_urls[file], os.path.join(base_dir, file))
- result = "Files downloaded successfully." if len(file_urls) > 0 else "All files are present."
- print(f"[{branding}TTS] {result}")
- print(f"[{branding}TTS] RVC Settings Saved")
- save_config_data()
- return "RVC settings updated successfully!"
-
- def generate_tts(gen_text, gen_char, rvcgen_char, rvcgen_char_pitch, gen_narr, rvcgen_narr, rvcgen_narr_pitch, gen_narren, gen_textni, gen_repetition, gen_lang, gen_filter, gen_speed, gen_pitch, gen_autopl, gen_autoplvol, gen_filen, gen_temperature, gen_filetime, gen_stream, gen_stopcurrentgen):
- if running_on_google_colab:
- api_url = f"{tunnel_url_1}/api/tts-generate"
- else:
- api_url = f"http://{my_current_url}/api/tts-generate"
+ def generate_tts(
+ gen_text,
+ gen_char,
+ rvcgen_char,
+ rvcgen_char_pitch,
+ gen_narr,
+ rvcgen_narr,
+ rvcgen_narr_pitch,
+ gen_narren,
+ gen_textni,
+ gen_repetition,
+ gen_lang,
+ gen_filter,
+ gen_speed,
+ gen_pitch,
+ gen_autopl,
+ gen_autoplvol,
+ gen_filen,
+ gen_temperature,
+ gen_filetime,
+ gen_stream,
+ gen_stopcurrentgen,
+ ):
+ """
+ Send TTS request from the gradio interface to the API address
+ """
+ debug_func_entry()
+ api_url = build_dynamic_url("tts-generate", include_protocol=False)
+ if api_url == "http://null/api/tts-generate": # Check if api_url is null
+ print_message(
+ "The URL in the API request was not set. This is usually because of a TTS request from",
+ message_type="error",
+ )
+ print_message(
+ "the Gradio web interface when the AllTalk server has been resarted but the Gradio page",
+ message_type="error",
+ )
+ print_message(
+ "was not re-loaded. Please refresh your Gradio page and try again.",
+ message_type="error",
+ )
+ # pylint: disable=line-too-long
+ return None, str(
+ "Error: Did you restart the AllTalk Server and not refresh your Gradio page? Please see the console/terminal message."
+ )
if gen_text == "":
- print(f"[{branding}TTS] No Text was sent to generate as TTS")
+ print_message("No Text was sent to generate as TTS")
return None, str("No Text was sent to generate as TTS")
if gen_stopcurrentgen:
stop_generate_tts()
if gen_stream == "true":
- if running_on_google_colab:
- api_url = f"{tunnel_url_1}/api/tts-generate-streaming"
- elif running_in_docker:
- api_url = f"{docker_url}/api/tts-generate-streaming"
- else:
- api_url = f"http://{my_current_url}/api/tts-generate-streaming"
+ api_url = build_dynamic_url(
+ "tts-generate-streaming", include_protocol=False
+ )
encoded_text = requests.utils.quote(gen_text)
+ # pylint: disable=line-too-long
streaming_url = f"{api_url}?text={encoded_text}&voice={gen_char}&language={gen_lang}&output_file={gen_filen}"
return streaming_url, str("TTS Streaming Audio Generated")
- else:
- data = {
- "text_input": gen_text,
- "text_filtering": gen_filter,
- "character_voice_gen": gen_char,
- "rvccharacter_voice_gen": rvcgen_char,
- "rvccharacter_pitch": rvcgen_char_pitch,
- "narrator_enabled": str(gen_narren).lower(),
- "narrator_voice_gen": gen_narr,
- "rvcnarrator_voice_gen": rvcgen_narr,
- "rvcnarrator_pitch": rvcgen_narr_pitch,
- "text_not_inside": gen_textni,
- "language": gen_lang,
- "output_file_name": gen_filen,
- "output_file_timestamp": str(gen_filetime).lower(),
- "autoplay": str(gen_autopl).lower(),
- "autoplay_volume": str(gen_autoplvol),
- "speed": str(gen_speed),
- "pitch": str(gen_pitch),
- "temperature": str(gen_temperature),
- "repetition_penalty": str(gen_repetition),
- }
- #print(f"Debug: Generate request param:", data) if params["debug_tts"] else None
- max_retries = 180 #3 minutes
- retry_delay = 1 # seconds
- retries = 0
- while retries < max_retries:
- try:
- response = requests.post(api_url, data=data, timeout=60)
- response.raise_for_status() # Raise an exception for 4xx or 5xx status codes
- result = response.json()
- if gen_autopl == "true":
- return None, str("TTS Audio Generated (Played remotely)")
- else:
- if params["api_def"]["api_use_legacy_api"]:
- return result['output_file_url'], str("TTS Audio Generated")
- else:
- # Set the protocol type
- protocol = "http://" # or "https://" if using HTTPS
- # Prepend the URL and PORT to the output_file_url
- if running_on_google_colab:
- output_file_url = f"{tunnel_url_1}{result['output_file_url']}"
- elif running_in_docker:
- output_file_url = f"{docker_url}{result['output_file_url']}"
- else:
- output_file_url = f"{protocol}{my_current_url}{result['output_file_url']}"
- return output_file_url, str("TTS Audio Generated")
- except requests.exceptions.RequestException as e:
- retries += 1
- if retries == max_retries:
- error_message = "An error occurred. Please see console output."
- print(f"[{branding}TTS] \033[91mError:\033[0m {error_message}")
- print(f"[{branding}TTS] \033[91mError Details:\033[0m {str(e)}")
- # Handle the error or return an appropriate error message
- return None, str(error_message)
+ # pylint: disable=line-too-long
+ tts_data = {
+ "text_input": gen_text,
+ "text_filtering": gen_filter,
+ "character_voice_gen": gen_char,
+ "rvccharacter_voice_gen": rvcgen_char,
+ "rvccharacter_pitch": rvcgen_char_pitch,
+ "narrator_enabled": str(gen_narren).lower(),
+ "narrator_voice_gen": gen_narr,
+ "rvcnarrator_voice_gen": rvcgen_narr,
+ "rvcnarrator_pitch": rvcgen_narr_pitch,
+ "text_not_inside": gen_textni,
+ "language": gen_lang,
+ "output_file_name": gen_filen,
+ "output_file_timestamp": str(gen_filetime).lower(),
+ "autoplay": str(gen_autopl).lower(),
+ "autoplay_volume": str(gen_autoplvol),
+ "speed": str(gen_speed),
+ "pitch": str(gen_pitch),
+ "temperature": str(gen_temperature),
+ "repetition_penalty": str(gen_repetition),
+ }
+ print_message(
+ "\033[94mDebug of data > def generate_tts > script.py\033[0m",
+ message_type="debug_tts_variables",
+ )
+
+ keys = list(tts_data.keys())
+ for i, key in enumerate(keys):
+ # Use └─ for the last item, and ├─ for others
+ prefix = "└─" if i == len(keys) - 1 else "├─"
+ print_message(f"{prefix} {key}: {tts_data[key]}", message_type="debug_tts_variables")
+ print_message(
+ f"API Url being used: {api_url}", message_type="debug_tts_variables"
+ )
+ max_retries = 180 # 3 minutes
+ retry_delay = 1 # seconds
+ retries = 0
+ while retries < max_retries:
+ try:
+ tts_response = requests.post(api_url, data=tts_data, timeout=60)
+ tts_response.raise_for_status()
+ result = tts_response.json()
+
+ if gen_autopl == "true":
+ return None, str("TTS Audio Generated (Played remotely)")
+ if config.api_def.api_use_legacy_api:
+ return result["output_file_url"], str("TTS Audio Generated")
+
+ # Prepend the URL and PORT to the output_file_url
+ output_file_url = build_dynamic_url(
+ result["output_file_url"].lstrip("/"),
+ include_protocol=False,
+ include_api=False,
+ )
+ return output_file_url, str("TTS Audio Generated")
+
+ except (requests.exceptions.Timeout, json.JSONDecodeError, RequestException) as e:
+ retries += 1
+ error_message = None
+ if retries == max_retries:
+ if isinstance(e, requests.exceptions.Timeout):
+ error_message = "Request timed out after maximum retries"
+ elif isinstance(e, json.JSONDecodeError):
+ error_message = "Failed to parse API response"
+ print_message(f"Error Details: {str(e)}", message_type="error")
+ print_message(f"Raw response: {tts_response.content}", message_type="error")
+ elif isinstance(e, RequestException):
+ error_message = "An error occurred while communicating with the API"
+ print_message(f"Error Details: {str(e)}", message_type="error")
+ print_message(f"Error: {error_message}", message_type="error")
+ return None, str(error_message)
+ if isinstance(e, RequestException):
time.sleep(retry_delay)
- def alltalk_gradio():
- global languages_list, at_settings
+ def alltalk_gradio(): # pylint: disable=too-many-statements, too-many-branches
+ """
+ Setup the main gradio interface for AllTalk
+ """
+ debug_func_entry()
+
# Get the URL IP or domain name
def get_domain_name(request: gr.Request):
- global my_current_url
- if running_on_google_colab:
- my_current_url = tunnel_url_1
+ """
+ Get domain name based on environment and request headers.
+ Args:
+ request (gr.Request): Gradio request object
+ Returns:
+ str: Error message if domain can't be retrieved, None otherwise
+ """
+ if _state['running_on_google_colab']:
+ _state['my_current_url'] = _state['tunnel_url_1']
return None
- elif running_in_docker:
- my_current_url = docker_url
- return None
+
+ if _state['running_in_docker']:
+ _state['my_current_url'] = _state['docker_url']
+ return None
+
if request:
host = request.headers.get("host", "Unknown")
- my_current_url = host.split(":")[0] # Split the host by ":" and take the first part
- my_current_url = my_current_url + ":" + str(params['api_def']['api_port_number'])
+ _state['my_current_url'] = host.split(":")[0] # Split the host by ":" and take the first part
+ _state['my_current_url'] = f"{_state['my_current_url']}:{config.api_def.api_port_number}"
return None
- else:
- return "Unable to retrieve the domain name."
-
- # Get the list of languages from languages
- languages = list(languages_list.keys())
- with gr.Blocks(theme=selected_theme, title="AllTalk", analytics_enabled=False) as app:
+
+ return "Unable to retrieve the domain name."
+
+ with gr.Blocks(
+ css=AllTalkHelpContent.custom_css,
+ theme=selected_theme,
+ title="AllTalk",
+ analytics_enabled=False,
+ ) as app: # pylint: disable=redefined-outer-name
with gr.Row():
- gr.Markdown("## AllTalk TTS")
+ gr.Markdown("## AllTalk TTS V2")
gr.Markdown("")
gr.Markdown("")
- dark_mode_btn = gr.Button("Light/Dark Mode", variant="primary", size="sm")
- dark_mode_btn.click(None, None, None,
- js="""() => {
+ dark_mode_btn = gr.Button(
+ "Light/Dark Mode", variant="primary", size="sm"
+ )
+ dark_mode_btn.click(
+ None,
+ None,
+ None,
+ js="""() => {
if (document.querySelectorAll('.dark').length) {
document.querySelectorAll('.dark').forEach(el => el.classList.remove('dark'));
// localStorage.setItem('darkMode', 'disabled');
@@ -1868,118 +3004,409 @@ if gradio_enabled == True:
document.querySelector('body').classList.add('dark');
// localStorage.setItem('darkMode', 'enabled');
}
- }""", show_api=False)
- if params.get("firstrun_splash", True):
- alltalk_welcome()
+ }""",
+ show_api=False,
+ )
+ if config.firstrun_splash:
+ with gr.Tab("AllTalk v2 Welcome page"):
+
+ def modify_config():
+ config.firstrun_splash = False
+ config.save()
+ return "Welcome screen is disabled."
+
+ gr.Markdown(
+ AllTalkHelpContent.WELCOME, elem_classes="custom-markdown"
+ )
+ with gr.Row():
+ gr.Markdown(
+ AllTalkHelpContent.WELCOME1, elem_classes="custom-markdown"
+ )
+ gr.Markdown(
+ AllTalkHelpContent.WELCOME2, elem_classes="custom-markdown"
+ )
+ update_btn = gr.Button(
+ "Click here to hide welcome screen on the next startup"
+ )
+ update_btn.click(fn=modify_config, inputs=None, outputs=None)
with gr.Tab("Generate TTS"):
- with gr.Tab("Generate"):
+ with gr.Row():
+ gen_text = gr.Textbox(label="Text Input", lines=6)
+ if _state['running_in_docker']:
with gr.Row():
- gen_text = gr.Textbox(label="Text Input", lines=10)
- if running_in_docker:
- with gr.Row():
- with gr.Accordion("Docker IP/URL for API Address updater", open=False):
- with gr.Row():
- docker_url = f"http://localhost:{params['api_def']['api_port_number']}"
- docker_upd = gr.Textbox(label="Docker IP/URL for API Address", value=docker_url, show_label=False)
- update_docker_btn = gr.Button("Update Docker IP/URL API Address")
- def update_docker_address(new_url):
- global docker_url # Need this to modify the global variable
- docker_url = new_url
- return f"Docker API address updated to: {new_url}"
- update_docker_btn.click(
- fn=update_docker_address,
- inputs=[docker_upd],
- outputs=[gr.Text(label="Status")]
+ with gr.Accordion(
+ "Docker IP/URL for API Address updater", open=False
+ ):
+ with gr.Row():
+ _state['docker_url'] = (
+ f"http://localhost:{config.api_def.api_port_number}"
)
- with gr.Row():
- with gr.Group():
- with gr.Row():
- engine_choices = gr.Dropdown(choices=engines_available, label="TTS Engine", value=engine_loaded, show_label=False)
- engine_btn = gr.Button("Swap TTS Engine")
- with gr.Group():
- with gr.Row():
- model_choices_gr = gr.Dropdown(choices=models_available, label="TTS Models", value=current_model_loaded, interactive=True, show_label=False, allow_custom_value=True)
- model_btn_gr = gr.Button("Load Different Model")
+ docker_upd = gr.Textbox(
+ label="Docker IP/URL for API Address",
+ value=_state['docker_url'],
+ show_label=False,
+ scale=2,
+ )
+ update_docker_btn = gr.Button(
+ "Update Docker IP/URL API Address", scale=1
+ )
+ with gr.Accordion(
+ "HELP - 🐳 Docker IP/URL for API Addresss", open=False
+ ):
+ with gr.Row():
+ gr.Markdown(
+ AllTalkHelpContent.DOCKER_EXPLAINER,
+ elem_classes="custom-markdown",
+ )
+ with gr.Row():
with gr.Group():
- at_available_voices_gr = at_settings["voices"]
- rvcat_available_voices_gr = at_settings["rvcvoices"]
with gr.Row():
- at_default_voice_gr = params["tgwui"]["tgwui_character_voice"]
- if at_default_voice_gr not in at_available_voices_gr:
- at_default_voice_gr = at_available_voices_gr[0] if at_available_voices_gr else ""
- gen_char = gr.Dropdown(choices=at_available_voices_gr, label="Character Voice", value=at_default_voice_gr, allow_custom_value=True,)
- rvcat_default_voice_gr = params["rvc_settings"]["rvc_char_model_file"]
- if rvcat_default_voice_gr not in rvcat_available_voices_gr:
- rvcat_default_voice_gr = rvcat_available_voices_gr[0] if rvcat_available_voices_gr else ""
- rvcgen_char = gr.Dropdown(choices=rvcat_available_voices_gr, label="RVC Character Voice", value=rvcat_default_voice_gr, allow_custom_value=True,)
- at_narrator_voice_gr = params["tgwui"]["tgwui_narrator_voice"]
- if at_narrator_voice_gr not in at_available_voices_gr:
- at_narrator_voice_gr = at_available_voices_gr[0] if at_available_voices_gr else ""
- gen_narr = gr.Dropdown(choices=at_available_voices_gr, label="Narrator Voice", value=at_narrator_voice_gr, allow_custom_value=True,)
- rvcat_narrator_voice_gr = params["rvc_settings"]["rvc_narr_model_file"]
- if rvcat_narrator_voice_gr not in rvcat_available_voices_gr:
- rvcat_narrator_voice_gr = rvcat_available_voices_gr[0] if rvcat_available_voices_gr else ""
- rvcgen_narr = gr.Dropdown(choices=rvcat_available_voices_gr, label="RVC Narrator Voice", value=rvcat_narrator_voice_gr, allow_custom_value=True,)
- with gr.Group():
+ engine_choices = gr.Dropdown(
+ choices=_state['srv_engines_available'],
+ label="TTS Engine",
+ value=_state['srv_current_engine_loaded'],
+ show_label=False,
+ )
+ engine_btn = gr.Button("Swap TTS Engine")
+ with gr.Group():
with gr.Row():
- rvcat_default_pitch_gr = gr.Slider(minimum=-24, maximum=24, step=1, label="RVC Character Pitch", info="Corrects the Character input TTS pitch to match the desired RVC voice output pitch.", value=params["rvc_settings"].get("pitch", 0), interactive=True, visible=False)
- rvcat_narrator_pitch_gr = gr.Slider(minimum=-24, maximum=24, step=1, label="RVC Narrator Pitch", info="Corrects the Narrator input TTS pitch to match the desired RVC voice output pitch.", value=params["rvc_settings"].get("pitch", 0), interactive=True, visible=False)
- def update_visibility(char_voice, narr_voice):
- is_visible = char_voice != "Disabled" or narr_voice != "Disabled"
- return gr.update(visible=is_visible), gr.update(visible=is_visible)
- rvcgen_char.change(fn=update_visibility, inputs=[rvcgen_char, rvcgen_narr], outputs=[rvcat_default_pitch_gr, rvcat_narrator_pitch_gr])
- rvcgen_narr.change(fn=update_visibility, inputs=[rvcgen_char, rvcgen_narr], outputs=[rvcat_default_pitch_gr, rvcat_narrator_pitch_gr])
+ model_choices_gr = gr.Dropdown(
+ choices=_state['srv_models_available'],
+ label="TTS Models",
+ value=_state['srv_current_model_loaded'],
+ interactive=True,
+ show_label=False,
+ allow_custom_value=True,
+ )
+ model_btn_gr = gr.Button("Load Different Model")
+ with gr.Group():
+ at_available_voices_gr = _state['srv_current_voices']
+ rvcat_available_voices_gr = _state['srv_current_rvcvoices']
+ with gr.Row():
+ at_default_voice_gr = config.tgwui.tgwui_character_voice
+ if at_default_voice_gr not in at_available_voices_gr:
+ at_default_voice_gr = (
+ at_available_voices_gr[0]
+ if at_available_voices_gr
+ else ""
+ )
+ gen_char = gr.Dropdown(
+ choices=at_available_voices_gr,
+ label="Character Voice",
+ value=at_default_voice_gr,
+ allow_custom_value=True,
+ )
+ rvcat_default_voice_gr = config.rvc_settings.rvc_char_model_file
+ if rvcat_default_voice_gr not in rvcat_available_voices_gr:
+ rvcat_default_voice_gr = (
+ rvcat_available_voices_gr[0]
+ if rvcat_available_voices_gr
+ else ""
+ )
+ rvcgen_char = gr.Dropdown(
+ choices=rvcat_available_voices_gr,
+ label="RVC Character Voice",
+ value=rvcat_default_voice_gr,
+ allow_custom_value=True,
+ )
+ at_narrator_voice_gr = config.tgwui.tgwui_narrator_voice
+ if at_narrator_voice_gr not in at_available_voices_gr:
+ at_narrator_voice_gr = (
+ at_available_voices_gr[0]
+ if at_available_voices_gr
+ else ""
+ )
+ gen_narr = gr.Dropdown(
+ choices=at_available_voices_gr,
+ label="Narrator Voice",
+ value=at_narrator_voice_gr,
+ allow_custom_value=True,
+ )
+ rvcat_narrator_voice_gr = (
+ config.rvc_settings.rvc_narr_model_file
+ )
+ if rvcat_narrator_voice_gr not in rvcat_available_voices_gr:
+ rvcat_narrator_voice_gr = (
+ rvcat_available_voices_gr[0]
+ if rvcat_available_voices_gr
+ else ""
+ )
+ rvcgen_narr = gr.Dropdown(
+ choices=rvcat_available_voices_gr,
+ label="RVC Narrator Voice",
+ value=rvcat_narrator_voice_gr,
+ allow_custom_value=True,
+ )
+ with gr.Group():
+ with gr.Row():
+ rvcat_default_pitch_gr = gr.Slider(
+ minimum=-24,
+ maximum=24,
+ step=1,
+ label="RVC Character Pitch",
+ info="Corrects the Character input TTS pitch to match the desired RVC voice output pitch.",
+ value=config.rvc_settings.pitch,
+ interactive=True,
+ visible=False,
+ )
+ rvcat_narrator_pitch_gr = gr.Slider(
+ minimum=-24,
+ maximum=24,
+ step=1,
+ label="RVC Narrator Pitch",
+ info="Corrects the Narrator input TTS pitch to match the desired RVC voice output pitch.",
+ value=config.rvc_settings.pitch,
+ interactive=True,
+ visible=False,
+ )
- with gr.Accordion("Advanced Engine/Model Settings", open=False):
- with gr.Row():
- #Get the current URL from the page
- domain_name_output = gr.Textbox(label="Domain Name", visible=False)
- def on_load(request: gr.Request):
- domain_name = get_domain_name(request)
- domain_name_output.value = domain_name
- app.load(on_load, inputs=None, outputs=None)
- if at_settings["streaming_capable"]:
- gen_choices = [("Standard", "false"), ("Streaming (Disable Narrator)", "true")]
- else:
- gen_choices = [("Standard", "false")]
- #Continue on with the Gradio interface
- gen_stream = gr.Dropdown(choices=gen_choices, label="Generation Mode", value="false")
- gen_lang = gr.Dropdown(value=params['api_def']['api_language'], choices=["ar", "zh", "cs", "nl", "en", "fr", "de", "hi", "hu", "it", "ja", "ko", "pl", "pt", "ru", "es", "tr"], label="Languages" if at_settings["languages_capable"] else "Model not multi language", interactive=at_settings["languages_capable"])
- gen_narren = gr.Dropdown(choices=[("Enabled", "true"), ("Disabled", "false"), ("Enabled (Silent)", "silent")], label="Narrator Enabled/Disabled", value="true" if params['api_def']['api_narrator_enabled'] == "true" else ("silent" if params['api_def']['api_narrator_enabled'] == "silent" else "false"))
- gen_textni = gr.Dropdown(choices=[("Character", "character"), ("Narrator", "narrator"), ("Silent", "silent")], label="Narrator Text-not-inside", value=params['api_def']['api_text_not_inside'])
- gen_stopcurrentgen = gr.Dropdown(choices={("Stop", "true"), ("Dont stop", "false")}, label="Auto-Stop current generation", value="true")
- with gr.Row():
- gen_filter = gr.Dropdown(value=params['api_def']['api_text_filtering'], label="Text filtering", choices=["none", "standard", "html"])
- gen_filetime = gr.Dropdown(choices={("Timestamp files", "true"), ("Dont Timestamp (Over-write)", "false")}, label="Include Timestamp", value="true" if params['api_def']['api_output_file_timestamp'] else "false")
- gen_autopl = gr.Dropdown(choices={("Play locally", "false"), ("Play remotely", "true")}, label="Play Locally or Remotely", value="true" if params['api_def']['api_autoplay'] else "false")
- gen_autoplvol = gr.Dropdown(choices=[str(i / 10) for i in range(11)], value=str(params['api_def']['api_autoplay_volume']), label="Remote play volume", allow_custom_value=True)
- gen_filen = gr.Textbox(value=params['api_def']['api_output_file_name'], label="Output File Name")
- with gr.Row():
- gen_speed = gr.Slider(minimum=0.25, maximum=2.00, step=0.25, label="Speed", value="1.00", interactive=at_settings["generationspeed_capable"])
- gen_pitch = gr.Slider(minimum=-10, maximum=10, step=1, label="Pitch", value="1", interactive=at_settings["pitch_capable"])
- gen_temperature = gr.Slider(minimum=0.05, maximum=1.0, step=0.05, label="Temperature", value=0.75, interactive=at_settings["temperature_capable"])
- gen_repetition = gr.Slider(minimum=1.0, maximum=20.0, step=1.0, label="Repetition Penalty", value=10, interactive=at_settings["repetitionpenalty_capable"])
- #Toggle narrator selection on Streaming select
- def update_narren_and_autopl(gen_stream):
- if gen_stream == "true":
- return "false", "false"
+ def update_visibility(char_voice, narr_voice):
+ is_visible = (
+ char_voice != "Disabled" or narr_voice != "Disabled"
+ )
+ return gr.update(visible=is_visible), gr.update(
+ visible=is_visible
+ )
+
+ rvcgen_char.change(
+ fn=update_visibility,
+ inputs=[rvcgen_char, rvcgen_narr],
+ outputs=[rvcat_default_pitch_gr, rvcat_narrator_pitch_gr],
+ )
+ rvcgen_narr.change(
+ fn=update_visibility,
+ inputs=[rvcgen_char, rvcgen_narr],
+ outputs=[rvcat_default_pitch_gr, rvcat_narrator_pitch_gr],
+ )
+
+ with gr.Accordion("Advanced Engine/Model Settings", open=False):
+ with gr.Row():
+ # Get the current URL from the page
+ domain_name_output = gr.Textbox(
+ label="Domain Name", visible=False
+ )
+
+ def on_load(request: gr.Request):
+ domain_name = get_domain_name(request)
+ domain_name_output.value = domain_name
+
+ app.load(on_load, inputs=None, outputs=None)
+ if _state['srv_settings_capabilities']['streaming_capable']:
+ gen_choices = [
+ ("Standard", "false"),
+ ("Streaming (Disable Narrator)", "true"),
+ ]
else:
- return gen_narren.value, gen_autopl.value
- gen_stream.change(update_narren_and_autopl, inputs=[gen_stream], outputs=[gen_narren, gen_autopl])
+ gen_choices = [("Standard", "false")]
+ # Continue on with the Gradio interface
+ gen_stream = gr.Dropdown(
+ choices=gen_choices, label="Generation Mode", value="false"
+ )
+ gen_lang = gr.Dropdown(
+ value=config.api_def.api_language,
+ choices=[
+ "ar",
+ "zh",
+ "cs",
+ "nl",
+ "en",
+ "fr",
+ "de",
+ "hi",
+ "hu",
+ "it",
+ "ja",
+ "ko",
+ "pl",
+ "pt",
+ "ru",
+ "es",
+ "tr",
+ ],
+ label=(
+ "Languages"
+ if _state['srv_settings_capabilities']['languages_capable']
+ else "Model not multi language"
+ ),
+ interactive=_state['srv_settings_capabilities']['languages_capable'],
+ allow_custom_value=True,
+ )
+ gen_narren = gr.Dropdown(
+ choices=[
+ ("Enabled", "true"),
+ ("Disabled", "false"),
+ ("Enabled (Silent)", "silent"),
+ ],
+ label="Narrator Enabled/Disabled",
+ value=(
+ "true"
+ if config.api_def.api_narrator_enabled == "true"
+ else (
+ "silent"
+ if config.api_def.api_narrator_enabled == "silent"
+ else "false"
+ )
+ ),
+ allow_custom_value=True,
+ )
+ gen_textni = gr.Dropdown(
+ choices=[
+ ("Character", "character"),
+ ("Narrator", "narrator"),
+ ("Silent", "silent"),
+ ],
+ label="Narrator Text-not-inside",
+ value=config.api_def.api_text_not_inside,
+ )
+ gen_stopcurrentgen = gr.Dropdown(
+ choices={("Stop", "true"), ("Dont stop", "false")},
+ label="Auto-Stop current generation",
+ value="true",
+ )
with gr.Row():
- output_audio = gr.Audio(show_label=False, label="Generated Audio", autoplay=True, scale=3)
- output_message = gr.Textbox( label="TTS Result", lines=5, scale=1)
+ gen_filter = gr.Dropdown(
+ value=config.api_def.api_text_filtering,
+ label="Text filtering",
+ choices=["none", "standard", "html"],
+ )
+ gen_filetime = gr.Dropdown(
+ choices=[
+ ("Timestamp files", "true"),
+ ("Dont Timestamp (Over-write)", "false"),
+ ],
+ label="Include Timestamp",
+ value=(
+ "true"
+ if config.api_def.api_output_file_timestamp
+ else "false"
+ ),
+ allow_custom_value=True,
+ )
+ gen_autopl = gr.Dropdown(
+ choices={
+ ("Play locally", "false"),
+ ("Play remotely", "true"),
+ },
+ label="Play Locally or Remotely",
+ value="true" if config.api_def.api_autoplay else "false",
+ allow_custom_value=True,
+ )
+ gen_autoplvol = gr.Dropdown(
+ choices=[str(i / 10) for i in range(11)],
+ value=str(config.api_def.api_autoplay_volume),
+ label="Remote play volume",
+ allow_custom_value=True,
+ )
+ gen_filen = gr.Textbox(
+ value=config.api_def.api_output_file_name,
+ label="Output File Name",
+ )
with gr.Row():
- dark_mode_btn = gr.Button("Light/Dark Mode", variant="primary")
- refresh_button = gr.Button("Refresh Server Settings", elem_id="refresh_button")
- stop_button = gr.Button("Interupt TTS Generation")
- submit_button = gr.Button("Generate TTS")
+ gen_speed = gr.Slider(
+ minimum=0.25,
+ maximum=2.00,
+ step=0.25,
+ label="Speed",
+ value="1.00",
+ interactive=_state['srv_settings_capabilities']['generationspeed_capable'],
+ )
+ gen_pitch = gr.Slider(
+ minimum=-10,
+ maximum=10,
+ step=1,
+ label="Pitch",
+ value="1",
+ interactive=_state['srv_settings_capabilities']['pitch_capable'],
+ )
+ gen_temperature = gr.Slider(
+ minimum=0.1,
+ maximum=1.0,
+ step=0.05,
+ label="Temperature",
+ value=0.75,
+ interactive=_state['srv_settings_capabilities']['temperature_capable'],
+ )
+ gen_repetition = gr.Slider(
+ minimum=1.0,
+ maximum=20.0,
+ step=1.0,
+ label="Repetition Penalty",
+ value=10,
+ interactive=_state['srv_settings_capabilities']['repetitionpenalty_capable']
+ )
- model_btn_gr.click(fn=change_model_loaded, inputs=[engine_choices, model_choices_gr], outputs=[output_message, gen_stream, gen_char, rvcgen_char, gen_narr, rvcgen_narr, gen_speed, gen_pitch, gen_temperature, gen_repetition, gen_lang, model_choices_gr, engine_choices])
- engine_btn.click(fn=set_engine_loaded, inputs=[engine_choices], outputs=[output_message, gen_stream, gen_char, rvcgen_char, gen_narr, rvcgen_narr, gen_speed, gen_pitch, gen_temperature, gen_repetition, gen_lang, model_choices_gr, engine_choices])
-
- dark_mode_btn.click(None, None, None,
- js="""() => {
+ # Toggle narrator selection on Streaming select
+ def update_narren_and_autopl(gen_stream):
+ debug_func_entry()
+ if gen_stream == "true":
+ return "false", "false"
+ return gen_narren.value, gen_autopl.value
+
+ gen_stream.change(
+ update_narren_and_autopl,
+ inputs=[gen_stream],
+ outputs=[gen_narren, gen_autopl],
+ )
+ with gr.Row():
+ output_audio = gr.Audio(
+ show_label=False,
+ label="Generated Audio",
+ autoplay=True,
+ scale=3,
+ )
+ output_message = gr.Textbox(label="Status/Result", lines=5, scale=1)
+ with gr.Row():
+ dark_mode_btn = gr.Button("Light/Dark Mode", variant="primary")
+ refresh_button = gr.Button(
+ "Refresh Server Settings", elem_id="refresh_button"
+ )
+ stop_button = gr.Button("Interupt TTS Generation")
+ submit_button = gr.Button("Generate TTS")
+
+ model_btn_gr.click(
+ fn=change_model_loaded,
+ inputs=[engine_choices, model_choices_gr],
+ outputs=[
+ output_message,
+ gen_stream,
+ gen_char,
+ rvcgen_char,
+ gen_narr,
+ rvcgen_narr,
+ gen_speed,
+ gen_pitch,
+ gen_temperature,
+ gen_repetition,
+ gen_lang,
+ model_choices_gr,
+ engine_choices,
+ ],
+ )
+ engine_btn.click(
+ fn=set_engine_loaded,
+ inputs=[engine_choices],
+ outputs=[
+ output_message,
+ gen_stream,
+ gen_char,
+ rvcgen_char,
+ gen_narr,
+ rvcgen_narr,
+ gen_speed,
+ gen_pitch,
+ gen_temperature,
+ gen_repetition,
+ gen_lang,
+ model_choices_gr,
+ engine_choices,
+ ],
+ )
+
+ dark_mode_btn.click(
+ None,
+ None,
+ None,
+ js="""() => {
if (document.querySelectorAll('.dark').length) {
document.querySelectorAll('.dark').forEach(el => el.classList.remove('dark'));
// localStorage.setItem('darkMode', 'disabled');
@@ -1987,291 +3414,1422 @@ if gradio_enabled == True:
document.querySelector('body').classList.add('dark');
// localStorage.setItem('darkMode', 'enabled');
}
- }""", show_api=False)
- refresh_button.click(at_update_dropdowns, None, [gen_stream, gen_char, rvcgen_char, gen_narr, rvcgen_narr, gen_speed, gen_pitch, gen_temperature, gen_repetition, gen_lang, model_choices_gr, engine_choices])
- stop_button.click(stop_generate_tts, inputs=[], outputs=[output_message])
- submit_button.click(generate_tts, inputs=[gen_text, gen_char, rvcgen_char, rvcat_default_pitch_gr, gen_narr, rvcgen_narr, rvcat_narrator_pitch_gr, gen_narren, gen_textni, gen_repetition, gen_lang, gen_filter, gen_speed, gen_pitch, gen_autopl, gen_autoplvol, gen_filen, gen_temperature, gen_filetime, gen_stream, gen_stopcurrentgen], outputs=[output_audio, output_message])
-
- if params.get("gradio_pages", {}).get("Generate_Help_page", True):
- with gr.Tab("Generate Help"):
- help_content = alltalk_generation_help()
- gr.Markdown(help_content)
-
- if params.get("gradio_pages", {}).get("Voice2RVC_page", True):
- with gr.Tab("Voice2RVC"):
- gr.Markdown("""Voice2RVC allows you to convert your spoken audio files into synthesized speech using advanced RVC (Retrieval-based Voice Conversion) models. You can either record your own speech or upload a pre-recorded audio file for processing. The tool offers features trim your input audio and undo changes if necessary. Simply record or upload your audio, select an RVC voice model, and submit it for processing. Once completed, you can download your synthesized speech.""")
- with gr.Row():
- audio_input = gr.Audio(sources=["microphone", "upload"], type="numpy", label="Record audio or Upload a spoken audio file")
- if running_in_docker:
+ }""",
+ show_api=False,
+ )
+ refresh_button.click(
+ at_update_dropdowns,
+ None,
+ [gen_stream, gen_char, rvcgen_char, gen_narr, rvcgen_narr, gen_speed, gen_pitch,
+ gen_temperature, gen_repetition, gen_lang, model_choices_gr, engine_choices],
+ )
+ stop_button.click(
+ stop_generate_tts, inputs=[], outputs=[output_message]
+ )
+ submit_button.click(
+ generate_tts,
+ inputs=[
+ gen_text,
+ gen_char,
+ rvcgen_char,
+ rvcat_default_pitch_gr,
+ gen_narr,
+ rvcgen_narr,
+ rvcat_narrator_pitch_gr,
+ gen_narren,
+ gen_textni,
+ gen_repetition,
+ gen_lang,
+ gen_filter,
+ gen_speed,
+ gen_pitch,
+ gen_autopl,
+ gen_autoplvol,
+ gen_filen,
+ gen_temperature,
+ gen_filetime,
+ gen_stream,
+ gen_stopcurrentgen,
+ ],
+ outputs=[output_audio, output_message],
+ )
+ if _state['running_in_docker']:
+ update_docker_btn.click(
+ fn=update_docker_address,
+ inputs=[docker_upd],
+ outputs=[output_message],
+ )
+ if config.gradio_pages.Generate_Help_page:
+ with gr.Accordion("HELP - 🎯 TTS Generation Basics", open=False):
with gr.Row():
- with gr.Accordion("Docker IP/URL for API Address updater", open=False):
+ gr.Markdown(
+ AllTalkHelpContent.GENERATE_SCREEN1,
+ elem_classes="custom-markdown",
+ )
+ gr.Markdown(
+ AllTalkHelpContent.GENERATE_SCREEN2,
+ elem_classes="custom-markdown",
+ )
+ with gr.Accordion("HELP - ⚙️ Advanced TTS Features", open=False):
+ with gr.Row():
+ gr.Markdown(
+ AllTalkHelpContent.GENERATE_SCREEN3,
+ elem_classes="custom-markdown",
+ )
+ gr.Markdown(
+ AllTalkHelpContent.GENERATE_SCREEN4,
+ elem_classes="custom-markdown",
+ )
+
+ if config.gradio_pages.Voice2RVC_page:
+ with gr.Tab("Voice2RVC"):
+ with gr.Row():
+ audio_input = gr.Audio(
+ sources=["microphone", "upload"],
+ type="numpy",
+ label="Record audio or Upload a spoken audio file",
+ )
+ if _state['running_in_docker']:
+ with gr.Row():
+ with gr.Accordion(
+ "Docker IP/URL for API Address updater", open=False
+ ):
with gr.Row():
- docker_url = f"http://localhost:{params['api_def']['api_port_number']}"
- docker_upd = gr.Textbox(label="Docker IP/URL for API Address", value=docker_url, show_label=False)
- update_docker_btn = gr.Button("Update Docker IP/URL API Address")
- def update_docker_address(new_url):
- global docker_url # Need this to modify the global variable
- docker_url = new_url
- return f"Docker API address updated to: {new_url}"
+ _state['docker_url'] = f"http://localhost:{config.api_def.api_port_number}"
+ docker_upd = gr.Textbox(
+ label="Docker IP/URL for API Address",
+ value=_state['docker_url'],
+ show_label=False,
+ )
+ update_docker_btn = gr.Button(
+ "Update Docker IP/URL API Address"
+ )
update_docker_btn.click(
fn=update_docker_address,
inputs=[docker_upd],
- outputs=[gr.Text(label="Status")]
- )
+ outputs=[gr.Text(label="Status")],
+ )
with gr.Row():
- rvc_voices_dropdown = gr.Dropdown(choices=at_settings["rvcvoices"], label="Select RVC Voice to generate as", value=at_settings["rvcvoices"][0], scale=1)
- rvc_pitch_slider = gr.Slider(minimum=-24, maximum=24, step=1, label="RVC Pitch", info="Depending on the pitch of your input audio, you will need to adjust this accordingly to change the pitch for the output voice. The higher the value, the higher the pitch.", value=0, interactive=True, scale=2)
+ rvc_voices_dropdown = gr.Dropdown(
+ choices=_state['srv_current_rvcvoices'],
+ label="Select RVC Voice to generate as",
+ value=_state['srv_current_rvcvoices'][0],
+ scale=1,
+ )
+ # pylint: disable=line-too-long
+ rvc_pitch_slider = gr.Slider(
+ minimum=-24,
+ maximum=24,
+ step=1,
+ label="RVC Pitch",
+ info="Depending on the pitch of your input audio, you will need to adjust this accordingly to change the pitch for the output voice. The higher the value, the higher the pitch.",
+ value=0,
+ interactive=True,
+ scale=2,
+ )
with gr.Row():
- rvc_f0method = gr.Radio(label="Pitch Extraction Algorithm", info="Select the algorithm to be used for extracting the pitch (F0) during audio conversion. The default algorithm is rmvpe, which is generally recommended for most cases due to its balance of accuracy and performance.", choices=["crepe", "crepe-tiny", "dio", "fcpe", "harvest", "hybrid[rmvpe+fcpe]", "pm", "rmvpe"], value=params["rvc_settings"].get("f0method", "rmvpe"), interactive=True, scale=1)
+ # pylint: disable=line-too-long
+ rvc_f0method = gr.Radio(
+ label="Pitch Extraction Algorithm",
+ info="Select the algorithm to be used for extracting the pitch (F0) during audio conversion. The default algorithm is rmvpe, which is generally recommended for most cases due to its balance of accuracy and performance.",
+ choices=[
+ "crepe",
+ "crepe-tiny",
+ "dio",
+ "fcpe",
+ "harvest",
+ "hybrid[rmvpe+fcpe]",
+ "pm",
+ "rmvpe",
+ ],
+ value=config.rvc_settings.f0method,
+ interactive=True,
+ scale=1,
+ )
submit_button = gr.Button("Submit to RVC", scale=0)
audio_output = gr.Audio(label="Converted Audio")
-
- submit_button.click(fn=voice2rvc, inputs=[audio_input, rvc_voices_dropdown, rvc_pitch_slider, rvc_f0method], outputs=audio_output)
-
- if params.get("gradio_pages", {}).get("TTS_Generator_page", True):
+
+ submit_button.click(
+ fn=voice2rvc,
+ inputs=[
+ audio_input,
+ rvc_voices_dropdown,
+ rvc_pitch_slider,
+ rvc_f0method,
+ ],
+ outputs=audio_output,
+ )
+ with gr.Accordion("HELP - 🎯 Voice2RVC Basics", open=False):
+ with gr.Row():
+ gr.Markdown(
+ AllTalkHelpContent.VOICE2RVC,
+ elem_classes="custom-markdown",
+ )
+ with gr.Row():
+ gr.Markdown(
+ AllTalkHelpContent.VOICE2RVC1,
+ elem_classes="custom-markdown",
+ )
+ gr.Markdown(
+ AllTalkHelpContent.VOICE2RVC2,
+ elem_classes="custom-markdown",
+ )
+
+ with gr.Tab("Transcribe"):
+ with gr.Row():
+ with gr.Group():
+ audio_files = gr.File(
+ file_count="multiple",
+ label="Upload Audio Files",
+ elem_classes="small-file-upload",
+ )
+ delete_after_process = gr.Checkbox(
+ label="Clean Up Temporary Audio Files After Processing",
+ value=False,
+ )
+ with gr.Row():
+ with gr.Group():
+ with gr.Row():
+ prefix_input = gr.Textbox(
+ label="Output Prefix (optional)",
+ placeholder="e.g., project_name, meeting_date",
+ value="",
+ scale=3,
+ )
+ model_choices = gr.Dropdown(
+ choices=[
+ "tiny",
+ "base",
+ "small",
+ "medium",
+ "turbo",
+ "large-v3",
+ "large-v3-turbo",
+ ],
+ value="turbo",
+ label="Whisper Model Size",
+ scale=1,
+ )
+ format_choices = gr.Dropdown(
+ choices=["txt", "json", "srt"],
+ value="txt",
+ label="Output Format",
+ scale=1,
+ )
+ with gr.Row():
+ delete_btn = gr.Button("Delete Uploaded Audio")
+ process_btn = gr.Button("Transcribe", variant="primary")
+ with gr.Row():
+ status_output = gr.Textbox(
+ label="Processing Status", lines=2
+ )
+
+ with gr.Row():
+ output_zip = gr.File(
+ label="Download Transcriptions (ZIP)",
+ elem_classes="small-file-upload2",
+ )
+
+ with gr.Accordion("HELP - 🎯 Transcribe Basics", open=False):
+ with gr.Row():
+ gr.Markdown(
+ AllTalkHelpContent.TRANSCRIBE,
+ elem_classes="custom-markdown",
+ )
+ with gr.Row():
+ gr.Markdown(
+ AllTalkHelpContent.TRANSCRIBE1,
+ elem_classes="custom-markdown",
+ )
+ gr.Markdown(
+ AllTalkHelpContent.TRANSCRIBE2,
+ elem_classes="custom-markdown",
+ )
+
+ process_btn.click(
+ fn=process_audio_files,
+ inputs=[
+ audio_files,
+ model_choices,
+ format_choices,
+ delete_after_process,
+ prefix_input,
+ ],
+ outputs=[output_zip, status_output],
+ )
+
+ delete_btn.click(
+ fn=delete_uploaded_files, inputs=[], outputs=[status_output]
+ )
+
+ with gr.Tab("Dictate"):
+ state = gr.State(None)
+ # Sort the dictionary by full names alphabetically
+ sorted_languages = dict(
+ sorted(
+ AllTalkHelpContent.WHISPER_LANGUAGES.items(),
+ key=lambda item: item[1],
+ )
+ )
+ # Reverse the dictionary for lookups (Full Name -> Code)
+ name_to_code = {name: code for code, name in sorted_languages.items()}
+
+ def process_language(language_name):
+ # Convert selected language name back to its 2-digit code
+ return name_to_code[language_name]
+
+ with gr.Row():
+ model_choices = gr.Dropdown(
+ choices=[
+ "tiny",
+ "base",
+ "small",
+ "medium",
+ "turbo",
+ "large-v3",
+ "large-v3-turbo",
+ ],
+ value="turbo",
+ label="Whisper Model",
+ scale=1,
+ )
+ language_select = gr.Dropdown(
+ choices=list(sorted_languages.values()),
+ value="English",
+ label="Language",
+ allow_custom_value=True,
+ )
+ language_select.change(
+ fn=process_language,
+ inputs=language_select,
+ )
+ export_format = gr.Dropdown(
+ choices=["txt", "srt", "json"],
+ value="txt",
+ label="Output Format",
+ scale=1,
+ )
+ prefix_input = gr.Textbox(
+ label="Output File Prefix (optional)",
+ placeholder="e.g., meeting_notes, todo_list",
+ scale=2,
+ )
+ start_btn = gr.Button("Load Model", variant="primary")
+ finish_btn = gr.Button("Finish & Unload", interactive=False)
+
+ with gr.Accordion(
+ "Advanced Settings (Change Before Loading The Model)", open=False
+ ):
+ with gr.Row():
+ # Update the dropdowns
+ translate_to_english = gr.Dropdown(
+ label="Translate to English",
+ choices={False, True},
+ value=False,
+ allow_custom_value=True,
+ )
+ source_language = gr.Dropdown(
+ choices=list(sorted_languages.values()),
+ value="English",
+ label="Source Language",
+ allow_custom_value=True,
+ )
+ source_language.change(
+ fn=process_language,
+ inputs=source_language,
+ )
+ add_timestamps = gr.Dropdown(
+ label="Add Timestamps", choices={False, True}, value=False
+ )
+ enable_diarization = gr.Dropdown(
+ label="Enable Speaker Diarization",
+ choices={False, True},
+ value=False,
+ )
+
+ with gr.Row():
+ enable_audio_processing = gr.Checkbox(
+ label="Enable ALL Audio Enhancements", value=True
+ )
+ bandpass_filter = gr.Checkbox(
+ label="Apply Bandpass Filter", value=True
+ )
+ noise_reduction = gr.Checkbox(
+ label="Apply Noise Reduction", value=True
+ )
+ compression = gr.Checkbox(
+ label="Apply Audio Compression", value=True
+ )
+
+ # At the start of interface setup
+ checkbox_state = gr.State({"processing": False})
+
+ def update_audio_settings(main_enable, state_dict):
+ """Enable all individual settings when main is checked"""
+ if not state_dict["processing"]:
+ state_dict["processing"] = True
+ state_dict["processing"] = False
+ # Return individual updates and state separately
+ return (
+ gr.update(value=main_enable), # bandpass
+ gr.update(value=main_enable), # noise
+ gr.update(value=main_enable), # compression
+ state_dict,
+ )
+ return (
+ gr.update(), # bandpass
+ gr.update(), # noise
+ gr.update(), # compression
+ state_dict,
+ )
+
+ def update_main_setting(bandpass, noise, compress, state_dict):
+ """Main should only be checked if all individuals are checked"""
+ if not state_dict["processing"]:
+ state_dict["processing"] = True
+ ret = gr.update(value=all([bandpass, noise, compress]))
+ state_dict["processing"] = False
+ return ret, state_dict
+ return gr.update(), state_dict
+
+ # In interface:
+ enable_audio_processing.change(
+ fn=update_audio_settings,
+ inputs=[enable_audio_processing, checkbox_state],
+ outputs=[
+ bandpass_filter,
+ noise_reduction,
+ compression,
+ checkbox_state,
+ ],
+ )
+
+ for checkbox in [bandpass_filter, noise_reduction, compression]:
+ checkbox.change(
+ fn=update_main_setting,
+ inputs=[
+ bandpass_filter,
+ noise_reduction,
+ compression,
+ checkbox_state,
+ ],
+ outputs=[enable_audio_processing, checkbox_state],
+ )
+
+ with gr.Row():
+ silence_threshold = gr.Slider(
+ minimum=0.001,
+ maximum=0.02,
+ value=0.008,
+ label="Silence Threshold",
+ )
+ bandpass_low = gr.Slider(
+ minimum=50,
+ maximum=200,
+ value=75,
+ label="Bandpass Low Freq (Hz)",
+ )
+ bandpass_high = gr.Slider(
+ minimum=2000,
+ maximum=8000,
+ value=3800,
+ label="Bandpass High Freq (Hz)",
+ )
+
+ with gr.Row():
+ # Audio input with waveform visualization
+ waveform_opts = gr.WaveformOptions(
+ sample_rate=16000, # Set to Whisper's expected rate
+ show_recording_waveform=True,
+ show_controls=False,
+ waveform_color="#1f77b4",
+ waveform_progress_color="#2ecc71",
+ )
+
+ # Audio input with configured waveform
+ dictate_audio = gr.Audio(
+ sources=["microphone"],
+ type="numpy",
+ streaming=True,
+ label="Click `Record` to start dictation and `Stop` to pause or stop dictation",
+ show_label=True,
+ interactive=False,
+ waveform_options=waveform_opts,
+ )
+ with gr.Accordion("Show Audio Levels Graph", open=False, elem_classes="fixed-accordion"):
+ audio_plot = gr.Plot(
+ label="Audio Levels",
+ show_label=True,
+ container=True,
+ elem_classes="fixed-plot",
+ )
+ with gr.Row():
+ text_output = gr.Textbox(
+ label="Live Transcription",
+ lines=10,
+ placeholder="Transcription will appear here as you speak...",
+ show_copy_button=True,
+ )
+
+ # Dictatation Button click handlers
+ start_btn.click(
+ fn=start_new_dictation,
+ inputs=[
+ model_choices,
+ prefix_input,
+ language_select,
+ source_language,
+ translate_to_english,
+ export_format,
+ add_timestamps,
+ enable_diarization,
+ enable_audio_processing,
+ bandpass_filter,
+ noise_reduction,
+ compression,
+ silence_threshold,
+ bandpass_low,
+ bandpass_high,
+ ],
+ outputs=[state, text_output, dictate_audio, start_btn, finish_btn],
+ )
+
+ finish_btn.click(
+ fn=finish_dictation,
+ inputs=[state, dictate_audio, text_output, audio_plot], # Pass the actual components
+ outputs=[
+ state, # Update the state
+ text_output, # Update the transcription text output
+ start_btn, # Update the start button
+ finish_btn, # Update the finish button
+ ],
+ )
+
+
+ def on_start_recording(state):
+ if state and state.get("is_active"):
+ print_message("Recording started", "debug_transcribe")
+ state["is_recording"] = True
+ return state
+
+ def on_stop_recording(state):
+ if state and state.get("is_active"):
+ print_message("Recording stopped", "debug_transcribe")
+ state["is_recording"] = False
+ return state
+
+
+ dictate_audio.start_recording(
+ fn=on_start_recording, inputs=[state], outputs=[state]
+ ).success(fn=None, js="() => {console.log('Recording started');}")
+
+ dictate_audio.stop_recording(
+ fn=on_stop_recording, inputs=[state], outputs=[state]
+ ).success(fn=None, js="() => {console.log('Recording stopped');}")
+
+ dictate_audio.stream(
+ fn=process_audio,
+ inputs=[dictate_audio, state],
+ outputs=[state, text_output, audio_plot],
+ show_progress=False,
+ concurrency_limit=1,
+ )
+
+ with gr.Accordion("HELP - 🎯 Dictate Basics", open=False):
+ with gr.Row():
+ gr.Markdown(
+ AllTalkHelpContent.WHISPER_HELP,
+ elem_classes="custom-markdown",
+ )
+ with gr.Row():
+ gr.Markdown(
+ AllTalkHelpContent.WHISPER_HELP1,
+ elem_classes="custom-markdown",
+ )
+ gr.Markdown(
+ AllTalkHelpContent.WHISPER_HELP2,
+ elem_classes="custom-markdown",
+ )
+
+ if config.gradio_pages.TTS_Generator_page:
with gr.Tab("TTS Generator"):
- gr.Markdown("""With the TTS Generator you can create incredibly long audio e.g. entire books. Yet to be migrated into Gradio""")
- gr.Markdown("""Please find it on the web address http://127.0.0.1:7851/static/tts_generator/tts_generator.html (Assuming you have not changed your IP Address)""")
-
+ # pylint: disable=line-too-long
+ gr.Markdown(
+ """### TTS Generator for long audio generation tasks. [Click here for access](http://127.0.0.1:7851/static/tts_generator/tts_generator.html)"""
+ )
+ with gr.Accordion("HELP - 🎯 TTS Generator Basics", open=False):
+ with gr.Row():
+ gr.Markdown(
+ AllTalkHelpContent.TTS_GENERATOR,
+ elem_classes="custom-markdown",
+ )
+ with gr.Row():
+ gr.Markdown(
+ AllTalkHelpContent.TTS_GENERATOR1,
+ elem_classes="custom-markdown",
+ )
+ gr.Markdown(
+ AllTalkHelpContent.TTS_GENERATOR2,
+ elem_classes="custom-markdown",
+ )
+
with gr.Tab("Global Settings"):
with gr.Tab("AllTalk Settings"):
with gr.Row():
- delete_output_wavs = gr.Dropdown(value=params["delete_output_wavs"], label="Del WAV's older than", choices=["Disabled", "1 Day", "2 Days", "3 Days", "4 Days", "5 Days", "6 Days", "7 Days", "14 Days", "21 Days", "28 Days"])
- api_port_number = gr.Number(value=int(params['api_def']['api_port_number']), label="API Port Number", precision=0)
- gradio_port_number = gr.Number(value=int(params["gradio_port_number"]), label="Gradio Port Number", precision=0)
- output_folder = gr.Textbox(value=params["output_folder"], label=f"Output Folder name (sub {branding})")
+ delete_output_wavs = gr.Dropdown(
+ value=config.delete_output_wavs,
+ label="Del WAV's older than",
+ choices=[
+ "Disabled",
+ "1 Day",
+ "2 Days",
+ "3 Days",
+ "4 Days",
+ "5 Days",
+ "6 Days",
+ "7 Days",
+ "14 Days",
+ "21 Days",
+ "28 Days",
+ ],
+ )
+ api_port_number = gr.Number(
+ value=config.api_def.api_port_number,
+ label="API Port Number",
+ precision=0,
+ )
+ gradio_port_number = gr.Number(
+ value=config.gradio_port_number,
+ label="Gradio Port Number",
+ precision=0,
+ )
+ settings_output_folder = gr.Textbox(
+ value=config.output_folder,
+ label=f"Output Folder name (sub {config.branding})",
+ )
with gr.Row():
- transcode_audio_format = gr.Dropdown(choices={"Disabled": "disabled", "aac": "aac", "flac": "flac", "mp3": "mp3", "opus": "opus", "wav": "wav"}, label="Audio Transcoding", value=params["transcode_audio_format"])
+ transcode_audio_format = gr.Dropdown(
+ choices={
+ "Disabled": "disabled",
+ "aac": "aac",
+ "flac": "flac",
+ "mp3": "mp3",
+ "opus": "opus",
+ "wav": "wav",
+ },
+ label="Audio Transcoding",
+ value=config.transcode_audio_format,
+ )
with gr.Row():
- themes_select = gr.Dropdown(loadThemes.get_list(), value=loadThemes.read_json(), label="Gradio Theme Selection", visible=True,)
- themes_select.change(fn=loadThemes.select_theme, inputs=[themes_select], outputs=[gr.Textbox(label="Gradio Selection Result")],)
+ themes_select = gr.Dropdown(
+ loadThemes.get_list(),
+ value=loadThemes.read_json(),
+ label="Gradio Theme Selection",
+ visible=True,
+ )
+
+ def update_theme_selection(theme_name):
+ updtheme_config = AlltalkConfig.get_instance()
+ updtheme_config.theme.clazz = theme_name
+ updtheme_config.save() # Save the updated configuration
+ return (
+ f"Theme '{theme_name}' has been selected and saved."
+ )
+
+ themes_select.change(
+ fn=update_theme_selection,
+ inputs=[themes_select],
+ outputs=[gr.Textbox(label="Gradio Selection Result")],
+ )
with gr.Row():
with gr.Column():
- gr_debug_tts = gr.CheckboxGroup(choices=debugging_choices, label="Debugging Options list", value=default_values)
+ gr_debug_tts = gr.CheckboxGroup(
+ choices=debugging_choices,
+ label="Debugging Options list",
+ value=default_values,
+ )
with gr.Column():
- gradio_interface = gr.Dropdown(choices={"Enabled": "true", "Disabled": "false"}, label="Gradio Interface", value="Enabled" if params["gradio_interface"] else "Disabled", info="**WARNING**: This will disable the AllTalk Gradio interface from loading. To re-enable the interface, go to the API address in a web browser and enable it there. http://127.0.0.1:7851/")
- gr.Markdown("### Disable Gradio Interface Tabs") # Adds a title
- gr.Markdown("Use the checkboxes below to enable or disable individual interface tabs or components.")
+ # pylint: disable=line-too-long
+ gradio_interface = gr.Dropdown(
+ choices={"Enabled": "true", "Disabled": "false"},
+ label="Gradio Interface",
+ value=(
+ "Enabled" if config.gradio_interface else "Disabled"
+ ),
+ info="**WARNING**: This will disable the AllTalk Gradio interface from loading. To re-enable the interface, go to the API address in a web browser and enable it there. http://127.0.0.1:7851/",
+ allow_custom_value=True,
+ )
with gr.Group():
+ gr.Markdown("### Disable Gradio Interface Tabs")
with gr.Row():
- generate_help_page = gr.Checkbox(label="Generate Help", value=params.get("gradio_pages", {}).get("Generate_Help_page", True))
- voice2rvc_page = gr.Checkbox(label="Voice2RVC", value=params.get("gradio_pages", {}).get("Voice2RVC_page", True))
- tts_generator_page = gr.Checkbox(label="TTS Generator", value=params.get("gradio_pages", {}).get("TTS_Generator_page", True))
- tts_engines_settings_page = gr.Checkbox(label="TTS Engines Settings", value=params.get("gradio_pages", {}).get("TTS_Engines_Settings_page", True))
- alltalk_documentation_page = gr.Checkbox(label="AllTalk Documentation", value=params.get("gradio_pages", {}).get("alltalk_documentation_page", True))
- api_documentation_page = gr.Checkbox(label="API Documentation", value=params.get("gradio_pages", {}).get("api_documentation_page", True))
-
+ generate_help_page = gr.Checkbox(
+ label="Generate Help",
+ value=config.gradio_pages.Generate_Help_page,
+ )
+ voice2rvc_page = gr.Checkbox(
+ label="Voice2RVC",
+ value=config.gradio_pages.Voice2RVC_page,
+ )
+ tts_generator_page = gr.Checkbox(
+ label="TTS Generator",
+ value=config.gradio_pages.TTS_Generator_page,
+ )
+ tts_engines_settings_page = gr.Checkbox(
+ label="TTS Engines Settings",
+ value=config.gradio_pages.TTS_Engines_Settings_page,
+ )
+ alltalk_documentation_page = gr.Checkbox(
+ label="AllTalk Documentation",
+ value=config.gradio_pages.alltalk_documentation_page,
+ )
+ api_documentation_page = gr.Checkbox(
+ label="API Documentation",
+ value=config.gradio_pages.api_documentation_page,
+ )
+
with gr.Row():
submit_button = gr.Button("Update Settings")
- output_message = gr.Textbox(label="Output Message", interactive=False, show_label=False)
+ output_message = gr.Textbox(
+ label="Output Message", interactive=False, show_label=False
+ )
+
+ with gr.Accordion("⚙️ HELP - AllTalk Settings Page", open=False):
+ with gr.Row():
+ gr.Markdown(
+ AllTalkHelpContent.ALLTALK_SETTINGS_PAGE1,
+ elem_classes="custom-markdown",
+ )
+ gr.Markdown(
+ AllTalkHelpContent.ALLTALK_SETTINGS_PAGE2,
+ elem_classes="custom-markdown",
+ )
+
+ with gr.Accordion("🔍 HELP - AllTalk Debug Settings", open=False):
+ with gr.Row():
+ gr.Markdown(
+ AllTalkHelpContent.DEBUG_HELP1,
+ elem_classes="custom-markdown",
+ )
+ gr.Markdown(
+ AllTalkHelpContent.DEBUG_HELP2,
+ elem_classes="custom-markdown",
+ )
# Update the function to include these new settings
submit_button.click(
- update_settings_at,
- inputs=[delete_output_wavs, gradio_interface, gradio_port_number, output_folder, api_port_number, gr_debug_tts, transcode_audio_format, generate_help_page, voice2rvc_page, tts_generator_page, tts_engines_settings_page, alltalk_documentation_page, api_documentation_page],
- outputs=output_message
+ update_settings_at,
+ inputs=[
+ delete_output_wavs,
+ gradio_interface,
+ gradio_port_number,
+ settings_output_folder,
+ api_port_number,
+ gr_debug_tts,
+ transcode_audio_format,
+ generate_help_page,
+ voice2rvc_page,
+ tts_generator_page,
+ tts_engines_settings_page,
+ alltalk_documentation_page,
+ api_documentation_page,
+ ],
+ outputs=output_message,
)
-
with gr.Tab("AllTalk API Defaults"):
- gr.Markdown("""## API Version Settings""")
- with gr.Group():
- with gr.Row():
- api_use_legacy_api = gr.Dropdown(choices=["AllTalk v2 API", "AllTalk v1 API (Legacy)"], label=f"{branding} API version", value="AllTalk v1 API (Legacy)" if params['api_def']['api_use_legacy_api'] else "AllTalk v2 API", scale=1)
- gr.Textbox(value="Determines the API version to use. The legacy API includes the full URL (protocol, IP address, and port) in the output responses, while the new API returns only the relative path of the output file. Default: AllTalk v2 API", interactive=False, show_label=False, lines=2, scale=4)
- with gr.Row():
- api_legacy_ip_address = gr.Textbox(value=params['api_def']['api_legacy_ip_address'], label="AllTalk v1 API IP address")
- gr.Textbox(value="Specifies the IP address to be included in the output responses when using the legacy API. Default: 127.0.0.1", interactive=False,show_label=False, lines=2, scale=4)
- gr.Markdown("""## API Default Settings""")
- with gr.Group():
- with gr.Row():
- api_length_stripping = gr.Slider(minimum=1, maximum=20, step=1, value=int(params['api_def']['api_length_stripping']), label="Strip sentences shorter than", scale=1)
- gr.Textbox(value="Defines the minimum length of a sentence (in characters) that will be processed for text-to-speech. Sentences shorter than the X characters value will be filtered out by the Narrator to remove unwanted text characters. Default: 3", interactive=False,show_label=False, lines=2, scale=4)
- with gr.Row():
- api_max_characters = gr.Slider(minimum=50, maximum=10000, step=50, value=int(params['api_def']['api_max_characters']), label="Maximum amount of characters")
- gr.Textbox(value="Sets the maximum number of characters allowed in a single text-to-speech generation request. Requests exceeding this limit will be rejected. Default: 2000", interactive=False,show_label=False, lines=2, scale=4)
- with gr.Row():
- api_text_filtering = gr.Dropdown(value=params['api_def']['api_text_filtering'], label="Text filtering", choices=["none", "standard", "html"])
- gr.Textbox(value="Determines the text filtering method applied to the input text before processing. Available options are 'none' (no filtering), 'standard' (basic filtering), and 'html' (HTML-specific filtering). Default: Standard", interactive=False,show_label=False, lines=2, scale=4)
- with gr.Row():
- api_language = gr.Dropdown(value=params['api_def']['api_language'], label="Language", choices=["ar", "zh", "cs", "nl", "en", "fr", "de", "hi", "hu", "it", "ja", "ko", "pl", "pt", "ru", "es", "tr"])
- gr.Textbox(value="Sets the default language for text-to-speech if no language is explicitly provided in the request. Default: en", interactive=False,show_label=False, lines=2, scale=4)
- with gr.Row():
- api_narrator_enabled = gr.Dropdown(
- choices={("Enabled", "true"), ("Disabled", "false"), ("Enabled (Silent)", "silent")},
- label="Narrator Enabled/Disable/Silent",
- value=("true" if params['api_def']['api_narrator_enabled'] == "true"
- else "false" if params['api_def']['api_narrator_enabled'] == "false"
- else "silent")
- )
- gr.Textbox(value="Determines whether the narrator functionality is enabled by default when not explicitly specified in the request. Please note, if you set `Enabled` or `Enabled (silent)` as the APi defaults, then all text will go into the narrator function unless `disabled` is sent as part of the TTS generation request, possibly resulting in silenced TTS. Default: Disabled", interactive=False,show_label=False, lines=2, scale=4)
- with gr.Row():
- api_text_not_inside = gr.Dropdown(choices={"character", "narrator", "silent"}, label="Narrator Text-not-inside", value="character" if params['api_def']['api_text_not_inside'] else "narrator")
- gr.Textbox(value="Defines how narrated text is split and processed when not explicitly specified in the request. The available options are 'character' (text is associated with the character) and 'narrator' (text is associated with the narrator). Default: Narrator", interactive=False,show_label=False, lines=2, scale=4)
- with gr.Row():
- api_output_file_name = gr.Textbox(value=params['api_def']['api_output_file_name'], label="Output file name")
- gr.Textbox(value="Specifies the default name for the output file when no filename is provided in the request. Default: myoutputfile", interactive=False,show_label=False, lines=2, scale=4)
- with gr.Row():
- api_output_file_timestamp = gr.Dropdown(choices={"Timestamp files", "Dont Timestamp (Over-write)"}, label="Include Timestamp", value="Timestamp files" if params['api_def']['api_output_file_timestamp'] else "Dont Timestamp (Over-write)")
- gr.Textbox(value="Determines whether a unique identifier (UUID) timestamp is appended to the generated text-to-speech output file. When enabled, each output file will have a unique timestamp, preventing overwriting of files. When disabled, files with the same name will be overwritten. Default: Timestamp files", interactive=False,show_label=False, lines=2, scale=4)
- with gr.Row():
- api_autoplay = gr.Dropdown(choices={"Play locally", "Play remotely"}, label="Play Locally or Remotely", value="Play remotely" if params['api_def']['api_autoplay'] else "Play locally")
- gr.Textbox(value="Specifies whether the generated audio should be played locally on the client-side or remotely on the server-side console/terminal. Default: Play locally", interactive=False,show_label=False, lines=2, scale=4)
- with gr.Row():
- api_autoplay_volume = gr.Slider(minimum=0.1, maximum=0.9, step=0.1, label="Remote play volume", value=float(params['api_def']['api_autoplay_volume']))
- gr.Textbox(value="Adjusts the volume level for audio playback when the 'Play Remotely' option is selected. The value ranges from 0.1 (lowest) to 0.9 (highest). Default: 0.9", interactive=False,show_label=False, lines=2, scale=4)
- gr.Markdown("""## API Allowed Text Filtering/Passthrough Settings""")
with gr.Row():
- api_allowed_filter = gr.Textbox(value=params['api_def']['api_allowed_filter'], label="Allowed text filter", show_label=False, lines=6, scale=1)
- gr.Textbox(value="""Defines the set of characters and Unicode ranges that are permitted to be processed by the AllTalk TTS system. This filter ensures that only valid and supported characters are passed to the TTS engine or AI model for generation. The allowed characters include ASCII letters and digits, punctuation, whitespace, and various Unicode ranges for different languages and scripts.
-
- a-zA-Z0-9: ASCII letters and digits
- .,;:!?: Punctuation characters
- '": Single and double quotes
- \s: Whitespace characters
- \-: Hyphen/dash
- $: Dollar sign
- \\u00C0-\\u00FF: Latin characters with diacritics (À-ÿ)
- \\u0400-\\u04FF: Cyrillic characters
- \\u0900-\\u097F: Devanagari characters
- \\u4E00-\\u9FFF: Chinese characters (CJK Unified Ideographs)
- \\u3400-\\u4DBF: Chinese characters (CJK Unified Ideographs Extension A)
- \\uF900-\\uFAFF: Chinese characters (CJK Compatibility Ideographs)
- \\u0600-\\u06FF: Arabic characters (Arabic)
- \\u0750-\\u077F: Arabic characters (Arabic Supplement)
- \\uFB50-\\uFDFF: Arabic characters (Arabic Presentation Forms-A)
- \\uFE70-\\uFEFF: Arabic characters (Arabic Presentation Forms-B)
- \\u3040-\\u309F: Hiragana characters (Japanese)
- \\u30A0-\\u30FF: Katakana characters (Japanese)
- \\uAC00-\\uD7A3: Hangul Syllables (Korean)
- \\u1100-\\u11FF: Hangul Jamo (Korean)
- \\u3130-\\u318F: Hangul Compatibility Jamo (Korean)
- \\u0150\\u0151\\u0170\\u0171: Hungarian characters
- \\u2018\\u2019: Left and right single quotation marks
- \\u201C\\u201D: Left and right double quotation marks
- \\u3001\\u3002: Ideographic comma and full stop
- \\uFF01\\uFF0C\\uFF1A\\uFF1B\\uFF1F: Fullwidth exclamation, comma, colon, semicolon & question mark
- """, interactive=False,show_label=False, lines=32, scale=1)
+ # API Version Settings
+ with gr.Column(scale=1):
+ with gr.Group():
+ with gr.Row():
+ api_use_legacy_api = gr.Dropdown(
+ choices=[
+ "AllTalk v2 API",
+ "AllTalk v1 API (Legacy)",
+ ],
+ label="API Version",
+ scale=1,
+ allow_custom_value=True,
+ value=config.api_def.api_use_legacy_api,
+ )
+ api_legacy_ip_address = gr.Textbox(
+ value=config.api_def.api_legacy_ip_address,
+ scale=1,
+ label="Legacy API IP Address",
+ )
+ # Request Processing
+ with gr.Column(scale=2):
+ with gr.Group():
+ with gr.Row():
+ api_length_stripping = gr.Slider(
+ minimum=1,
+ maximum=20,
+ step=1,
+ value=int(config.api_def.api_length_stripping),
+ scale=2,
+ label="Minimum Sentence Length",
+ )
+ api_max_characters = gr.Slider(
+ minimum=50,
+ maximum=10000,
+ step=50,
+ value=int(config.api_def.api_max_characters),
+ scale=2,
+ label="Maximum Request Characters",
+ )
+ api_text_filtering = gr.Dropdown(
+ value=config.api_def.api_text_filtering,
+ label="Text Filtering Mode",
+ scale=2,
+ choices=["none", "standard", "html"],
+ )
+ api_language = gr.Dropdown(
+ value=config.api_def.api_language,
+ label="Default Language",
+ scale=2,
+ choices=[
+ "ar",
+ "zh",
+ "cs",
+ "nl",
+ "en",
+ "fr",
+ "de",
+ "hi",
+ "hu",
+ "it",
+ "ja",
+ "ko",
+ "pl",
+ "pt",
+ "ru",
+ "es",
+ "tr",
+ ],
+ )
with gr.Row():
- submit_button = gr.Button("Update Settings")
- output_message = gr.Textbox(label="Output Message", interactive=False, show_label=False)
+ with gr.Column(scale=1):
+ with gr.Group():
+ with gr.Row():
+ api_narrator_enabled = gr.Dropdown(
+ choices=[
+ ("Enabled", "true"),
+ ("Disabled", "false"),
+ ("Enabled (Silent)", "silent"),
+ ],
+ label="Narrator Mode",
+ allow_custom_value=True,
+ value=(
+ "true"
+ if config.api_def.api_narrator_enabled
+ == "true"
+ else (
+ "silent"
+ if config.api_def.api_narrator_enabled
+ == "silent"
+ else "false"
+ )
+ ),
+ )
+ api_text_not_inside = gr.Dropdown(
+ choices=["character", "narrator", "silent"],
+ label="Text-Not-Inside Handling",
+ allow_custom_value=True,
+ value=config.api_def.api_text_not_inside,
+ )
+ with gr.Column(scale=2):
+ with gr.Group():
+ with gr.Row():
+ api_output_file_name = gr.Textbox(
+ value=config.api_def.api_output_file_name,
+ label="Default Filename",
+ )
+ api_output_file_timestamp = gr.Dropdown(
+ choices=[
+ "Timestamp files",
+ "Dont Timestamp (Over-write)",
+ ],
+ label="File Timestamping",
+ allow_custom_value=True,
+ value=(
+ "Timestamp files"
+ if config.api_def.api_output_file_timestamp
+ else "Dont Timestamp (Over-write)"
+ ),
+ )
+ api_autoplay = gr.Dropdown(
+ choices=["Play locally", "Play remotely"],
+ label="Playback Location",
+ allow_custom_value=True,
+ value=(
+ "Play remotely"
+ if config.api_def.api_autoplay
+ else "Play locally"
+ ),
+ )
+ api_autoplay_volume = gr.Slider(
+ minimum=0.1,
+ maximum=0.9,
+ step=0.1,
+ label="Remote Playback Volume",
+ value=float(config.api_def.api_autoplay_volume),
+ )
+ with gr.Row():
+ with gr.Column(scale=2):
+ with gr.Group():
+ with gr.Row():
+ api_allowed_filter = gr.Textbox(
+ value=config.api_def.api_allowed_filter,
+ label="Allowed Characters & Unicode Ranges",
+ lines=3,
+ )
+ with gr.Column(scale=1):
+ with gr.Group():
+ with gr.Row():
+ with gr.Column(scale=2):
+ output_message = gr.Textbox(
+ label="Status", interactive=False
+ )
+ submit_button = gr.Button("Update Settings")
+ # Help Accordions
+ with gr.Accordion("HELP - 🎯 Quick Start Guide", open=False):
+ gr.Markdown(
+ AllTalkHelpContent.API_DEFAULTS1,
+ elem_classes="custom-markdown",
+ )
+ with gr.Accordion(
+ "HELP - 🔄 Version & Compatibility Settings (API v1/v2, Legacy support)",
+ open=False,
+ ):
+ gr.Markdown(
+ AllTalkHelpContent.API_DEFAULTS2,
+ elem_classes="custom-markdown",
+ )
+ with gr.Accordion(
+ "HELP - 🎛️ Request Configuration (Character limits, language, file names)",
+ open=False,
+ ):
+ gr.Markdown(
+ AllTalkHelpContent.API_DEFAULTS3,
+ elem_classes="custom-markdown",
+ )
+ with gr.Accordion(
+ "HELP - 📝 Text Processing & Filtering (Filtering modes, character sets, language)",
+ open=False,
+ ):
+ gr.Markdown(
+ AllTalkHelpContent.API_DEFAULTS4,
+ elem_classes="custom-markdown",
+ )
+ with gr.Accordion(
+ "HELP - 🗣️ Narrator & Playback (Narrator modes, text handling, playback options",
+ open=False,
+ ):
+ gr.Markdown(
+ AllTalkHelpContent.API_DEFAULTS5,
+ elem_classes="custom-markdown",
+ )
+ with gr.Accordion(
+ "HELP - 📚 API Allowed Text Filtering/Passthrough", open=False
+ ):
+ gr.Markdown(
+ AllTalkHelpContent.API_DEFAULTS6,
+ elem_classes="custom-markdown",
+ )
+ with gr.Accordion(
+ "HELP - ❗ Troubleshooting & Best Practices", open=False
+ ):
+ gr.Markdown(
+ AllTalkHelpContent.API_DEFAULTS7,
+ elem_classes="custom-markdown",
+ )
- submit_button.click(update_settings_api, inputs=[api_length_stripping, api_legacy_ip_address, api_allowed_filter, api_max_characters, api_use_legacy_api, api_text_filtering, api_narrator_enabled, api_text_not_inside, api_language, api_output_file_name, api_output_file_timestamp, api_autoplay, api_autoplay_volume], outputs=output_message)
-
- def rvc_update_dropdowns():
- global at_settings
- at_settings = get_alltalk_settings() # Pull all the current settings from the AllTalk server, if its online.
- current_voices = at_settings["rvcvoices"]
- current_char = params["rvc_settings"]["rvc_char_model_file"]
- current_narr = params["rvc_settings"]["rvc_narr_model_file"]
- if current_char not in current_voices:
- current_char = current_voices[0] if current_voices else ""
- if current_narr not in current_voices:
- current_narr = current_voices[0] if current_voices else ""
- return (
- gr.Dropdown(choices=current_voices, value=current_char, interactive=True),
- gr.Dropdown(choices=current_voices, value=current_narr, interactive=True),
+ submit_button.click(
+ update_settings_api,
+ inputs=[
+ api_length_stripping,
+ api_legacy_ip_address,
+ api_allowed_filter,
+ api_max_characters,
+ api_use_legacy_api,
+ api_text_filtering,
+ api_narrator_enabled,
+ api_text_not_inside,
+ api_language,
+ api_output_file_name,
+ api_output_file_timestamp,
+ api_autoplay,
+ api_autoplay_volume,
+ ],
+ outputs=output_message,
)
- def gr_update_rvc_settings(rvc_enabled, rvc_char_model_file, rvc_narr_model_file, split_audio, autotune, pitch,
- filter_radius, index_rate, rms_mix_rate, protect, hop_length, f0method, embedder_model, training_data_size):
+ def rvc_update_dropdowns():
+ """Update RVC dropdowns with current values from state"""
+ debug_func_entry()
+
+ # Update state with current settings
+ get_alltalk_settings()
+
+ # Get values from state
+ current_voices = _state['srv_current_rvcvoices']
+ current_char = _state['srv_rvc_character_voice']
+ current_narr = _state['srv_rvc_narrator_voice']
+
+ # Handle default voices if they're not in available voices
+ if current_char not in current_voices:
+ current_char = current_voices[0] if current_voices else ""
+ if current_narr not in current_voices:
+ current_narr = current_voices[0] if current_voices else ""
+
+ return (
+ gr.Dropdown(choices=current_voices, value=current_char, interactive=True),
+ gr.Dropdown(choices=current_voices, value=current_narr, interactive=True)
+ )
+
+ def gr_update_rvc_settings(
+ rvc_enabled,
+ rvc_char_model_file,
+ rvc_narr_model_file,
+ split_audio,
+ autotune,
+ pitch,
+ filter_radius,
+ index_rate,
+ rms_mix_rate,
+ protect,
+ hop_length,
+ f0method,
+ embedder_model,
+ training_data_size,
+ ):
+ debug_func_entry()
progress = gr.Progress(track_tqdm=True)
- return update_rvc_settings(rvc_enabled, rvc_char_model_file, rvc_narr_model_file, split_audio, autotune, pitch,
- filter_radius, index_rate, rms_mix_rate, protect, hop_length, f0method, embedder_model, training_data_size, progress)
+ return update_rvc_settings(
+ rvc_enabled,
+ rvc_char_model_file,
+ rvc_narr_model_file,
+ split_audio,
+ autotune,
+ pitch,
+ filter_radius,
+ index_rate,
+ rms_mix_rate,
+ protect,
+ hop_length,
+ f0method,
+ embedder_model,
+ training_data_size,
+ progress,
+ )
with gr.Tab("RVC Settings"):
with gr.Row():
with gr.Column(scale=3):
with gr.Row():
- rvc_model_file_available = at_settings["rvcvoices"]
- rvc_char_model_file_default = params["rvc_settings"]["rvc_char_model_file"]
- if rvc_char_model_file_default not in rvc_model_file_available:
- rvc_char_model_file_default = rvc_model_file_available[0] if rvc_model_file_available else ""
- rvc_char_model_file_gr = gr.Dropdown(choices=rvc_model_file_available, label="Default Character Voice Model", info="Select the Character voice model used for conversion.", value=rvc_char_model_file_default, allow_custom_value=True,)
- rvc_narr_model_file_default = params["rvc_settings"]["rvc_narr_model_file"]
- if rvc_narr_model_file_default not in rvc_model_file_available:
- rvc_narr_model_file_default = rvc_model_file_available[0] if rvc_model_file_available else ""
- rvc_narr_model_file_gr = gr.Dropdown(choices=rvc_model_file_available, label="Default Narrator Voice Model", info="Select the Narrator voice model used for conversion.", value=rvc_narr_model_file_default, allow_custom_value=True,)
+ rvc_model_file_available = _state['srv_current_rvcvoices']
+ rvc_char_model_file_default = (
+ config.rvc_settings.rvc_char_model_file
+ )
+ if (
+ rvc_char_model_file_default
+ not in rvc_model_file_available
+ ):
+ rvc_char_model_file_default = (
+ rvc_model_file_available[0]
+ if rvc_model_file_available
+ else ""
+ )
+ rvc_char_model_file_gr = gr.Dropdown(
+ choices=rvc_model_file_available,
+ label="Default Character Voice Model",
+ info="Select the Character voice model used for conversion.",
+ value=rvc_char_model_file_default,
+ allow_custom_value=True,
+ )
+ rvc_narr_model_file_default = (
+ config.rvc_settings.rvc_narr_model_file
+ )
+ if (
+ rvc_narr_model_file_default
+ not in rvc_model_file_available
+ ):
+ rvc_narr_model_file_default = (
+ rvc_model_file_available[0]
+ if rvc_model_file_available
+ else ""
+ )
+ rvc_narr_model_file_gr = gr.Dropdown(
+ choices=rvc_model_file_available,
+ label="Default Narrator Voice Model",
+ info="Select the Narrator voice model used for conversion.",
+ value=rvc_narr_model_file_default,
+ allow_custom_value=True,
+ )
rvc_refresh_button = gr.Button("Refresh Model Choices")
with gr.Column(scale=0):
- rvc_enabled = gr.Checkbox(label="Enable RVC", info="RVC (Real-Time Voice Cloning) enhances TTS by replicating voice characteristics for characters or narrators, adding depth to synthesized speech.", value=params["rvc_settings"].get("rvc_enabled", False), interactive=True)
+ # pylint: disable=line-too-long
+ rvc_enabled = gr.Checkbox(
+ label="Enable RVC",
+ info="RVC (Real-Time Voice Cloning) enhances TTS by replicating voice characteristics for characters or narrators, adding depth to synthesized speech.",
+ value=config.rvc_settings.rvc_enabled,
+ interactive=True,
+ )
with gr.Row(equal_height=True):
with gr.Column():
- pitch = gr.Slider(minimum=-24, maximum=24, step=1, label="Pitch", info="Set the pitch of the audio, the higher the value, the higher the pitch.", value=params["rvc_settings"].get("pitch", 0), interactive=True,)
+ # pylint: disable=line-too-long
+ pitch = gr.Slider(
+ minimum=-24,
+ maximum=24,
+ step=1,
+ label="Pitch",
+ info="Set the pitch of the audio, the higher the value, the higher the pitch.",
+ value=config.rvc_settings.pitch,
+ interactive=True,
+ )
with gr.Column():
- hop_length = gr.Slider(minimum=1, maximum=512, step=1, label="Hop Length", info="Smaller hop lengths require more time for inference but tend to yield higher pitch accuracy.", value=params["rvc_settings"].get("hop_length", 128), interactive=True,)
- with gr.Row(equal_height=True):
- with gr.Column():
- training_data_size = gr.Slider(minimum=10000, maximum=100000, step=5000, label="Training Data Size", info="Determines the number of training data points used to train the FAISS index. Increasing the size may improve the quality but can also increase computation time.", value=params["rvc_settings"].get("training_data_size", 25000), interactive=True)
- with gr.Column():
- index_rate = gr.Slider(minimum=0, maximum=1, label="Index Influence Ratio", info="Sets the influence exerted by the index file on the final output. A higher value increases the impact of the index, potentially enhancing detail but also increasing the risk of artifacts.", value=params["rvc_settings"].get("index_rate", 0.75), interactive=True)
- with gr.Row(equal_height=True):
- with gr.Column():
- rms_mix_rate = gr.Slider(minimum=0, maximum=1, label="Volume Envelope", info="Substitute or blend with the volume envelope of the output. The closer the ratio is to 1, the more the output envelope is employed.", value=params["rvc_settings"].get("rms_mix_rate", 1), interactive=True,)
+ # pylint: disable=line-too-long
+ hop_length = gr.Slider(
+ minimum=1,
+ maximum=512,
+ step=1,
+ label="Hop Length",
+ info="Smaller hop lengths require more time for inference but tend to yield higher pitch accuracy.",
+ value=config.rvc_settings.hop_length,
+ interactive=True,
+ )
+ with gr.Row(equal_height=True):
with gr.Column():
- protect = gr.Slider(minimum=0, maximum=0.5, label="Protect Voiceless Consonants/Breath sounds", info="Prevents sound artifacts. Higher values (up to 0.5) provide stronger protection but may affect indexing.", value=params["rvc_settings"].get("protect", 0.5), interactive=True,)
- with gr.Column():
- filter_radius = gr.Slider(minimum=0, maximum=7, label="Filter Radius", info="If the number is greater than or equal to three, employing median filtering on the collected tone results has the potential to decrease respiration.", value=params["rvc_settings"].get("filter_radius", 3), step=1, interactive=True,)
+ # pylint: disable=line-too-long
+ training_data_size = gr.Slider(
+ minimum=10000,
+ maximum=100000,
+ step=5000,
+ label="Training Data Size",
+ info="Determines the number of training data points used to train the FAISS index. Increasing the size may improve the quality but can also increase computation time.",
+ value=config.rvc_settings.training_data_size,
+ interactive=True,
+ )
+ with gr.Column():
+ # pylint: disable=line-too-long
+ index_rate = gr.Slider(
+ minimum=0,
+ maximum=1,
+ label="Index Influence Ratio",
+ info="Sets the influence exerted by the index file on the final output. A higher value increases the impact of the index, potentially enhancing detail but also increasing the risk of artifacts.",
+ value=config.rvc_settings.index_rate,
+ interactive=True,
+ )
+ with gr.Row(equal_height=True):
+ with gr.Column():
+ # pylint: disable=line-too-long
+ rms_mix_rate = gr.Slider(
+ minimum=0,
+ maximum=1,
+ label="Volume Envelope",
+ info="Substitute or blend with the volume envelope of the output. The closer the ratio is to 1, the more the output envelope is employed.",
+ value=config.rvc_settings.rms_mix_rate,
+ interactive=True,
+ )
+ with gr.Column():
+ # pylint: disable=line-too-long
+ protect = gr.Slider(
+ minimum=0,
+ maximum=0.5,
+ label="Protect Voiceless Consonants/Breath sounds",
+ info="Prevents sound artifacts. Higher values (up to 0.5) provide stronger protection but may affect indexing.",
+ value=config.rvc_settings.protect,
+ interactive=True,
+ )
+ with gr.Column():
+ # pylint: disable=line-too-long
+ filter_radius = gr.Slider(
+ minimum=0,
+ maximum=7,
+ label="Filter Radius",
+ info="If the number is greater than or equal to three, employing median filtering on the collected tone results has the potential to decrease respiration.",
+ value=config.rvc_settings.filter_radius,
+ step=1,
+ interactive=True,
+ )
with gr.Row():
with gr.Column():
with gr.Row():
- embedder_model = gr.Radio(label="Embedder Model", info="Model used for learning speaker embedding.", choices=["hubert", "contentvec"], value=params["rvc_settings"].get("embedder_model", "hubert"), interactive=True,)
- with gr.Row():
- split_audio = gr.Checkbox(label="Split Audio", info="Split the audio into chunks for inference to obtain better results in some cases.", value=params["rvc_settings"].get("split_audio", False), interactive=True,)
- autotune = gr.Checkbox(label="Autotune", info="Apply a soft autotune to your inferences, recommended for singing conversions.", value=params["rvc_settings"].get("autotune", False), interactive=True,)
+ with gr.Row():
+ embedder_model = gr.Radio(
+ label="Embedder Model",
+ info="Model used for learning speaker embedding.",
+ choices=["hubert", "contentvec"],
+ value=config.rvc_settings.embedder_model,
+ interactive=True,
+ )
+ with gr.Row():
+ # pylint: disable=line-too-long
+ split_audio = gr.Checkbox(
+ label="Split Audio",
+ info="Split the audio into chunks for inference to obtain better results in some cases.",
+ value=config.rvc_settings.split_audio,
+ interactive=True,
+ )
+ # pylint: disable=line-too-long
+ autotune = gr.Checkbox(
+ label="Autotune",
+ info="Apply a soft autotune to your inferences, recommended for singing conversions.",
+ value=config.rvc_settings.autotune,
+ interactive=True,
+ )
with gr.Column():
- f0method = gr.Radio(label="Pitch Extraction Algorithm", info="Select the algorithm to be used for extracting the pitch (F0) during audio conversion. The default algorithm is rmvpe, which is generally recommended for most cases due to its balance of accuracy and performance.", choices=["crepe", "crepe-tiny", "dio", "fcpe", "harvest", "hybrid[rmvpe+fcpe]", "pm", "rmvpe"], value=params["rvc_settings"].get("f0method", "rmvpe"), interactive=True,)
+ # pylint: disable=line-too-long
+ f0method = gr.Radio(
+ label="Pitch Extraction Algorithm",
+ info="Select the algorithm to be used for extracting the pitch (F0) during audio conversion. The default algorithm is rmvpe, which is generally recommended for most cases due to its balance of accuracy and performance.",
+ choices=[
+ "crepe",
+ "crepe-tiny",
+ "dio",
+ "fcpe",
+ "harvest",
+ "hybrid[rmvpe+fcpe]",
+ "pm",
+ "rmvpe",
+ ],
+ value=config.rvc_settings.f0method,
+ interactive=True,
+ )
with gr.Row():
update_button = gr.Button("Update RVC Settings")
- update_output = gr.Textbox(label="Update Status", show_label=False)
- rvc_refresh_button.click(rvc_update_dropdowns, None, [rvc_char_model_file_gr, rvc_narr_model_file_gr])
- update_button.click(fn=gr_update_rvc_settings, inputs=[rvc_enabled, rvc_char_model_file_gr, rvc_narr_model_file_gr, split_audio, autotune, pitch, filter_radius, index_rate, rms_mix_rate, protect, hop_length, f0method, embedder_model, training_data_size], outputs=[update_output])
-
+ update_output = gr.Textbox(
+ label="Update Status", show_label=False
+ )
+ rvc_refresh_button.click(
+ rvc_update_dropdowns,
+ None,
+ [rvc_char_model_file_gr, rvc_narr_model_file_gr],
+ )
+ update_button.click(
+ fn=gr_update_rvc_settings,
+ inputs=[
+ rvc_enabled,
+ rvc_char_model_file_gr,
+ rvc_narr_model_file_gr,
+ split_audio,
+ autotune,
+ pitch,
+ filter_radius,
+ index_rate,
+ rms_mix_rate,
+ protect,
+ hop_length,
+ f0method,
+ embedder_model,
+ training_data_size,
+ ],
+ outputs=[update_output],
+ )
+
+ with gr.Accordion(
+ "🗣️ HELP - RVC Settings Page",
+ open=False,
+ elem_classes=["gr-accordion"],
+ ):
+ with gr.Row():
+ gr.Markdown(
+ AllTalkHelpContent.RVC_PAGE1,
+ elem_classes="custom-markdown",
+ )
+ gr.Markdown(
+ AllTalkHelpContent.RVC_PAGE2,
+ elem_classes="custom-markdown",
+ )
+
with gr.Tab("Text-generation-webui Settings"):
with gr.Row():
- activate = gr.Dropdown(choices={"Enabled": "true", "Disabled": "false"}, label="Activate TTS", value="Enabled" if params["tgwui"]["tgwui_activate_tts"] else "Disabled")
- autoplay = gr.Dropdown(choices={"Enabled": "true", "Disabled": "false"}, label="Autoplay TTS", value="Enabled" if params["tgwui"]["tgwui_autoplay_tts"] else "Disabled")
- show_text = gr.Dropdown(choices={"Enabled": "true", "Disabled": "false"}, label="Show Text", value="Enabled" if params["tgwui"]["tgwui_show_text"] else "Disabled")
- narrator_enabled = gr.Dropdown(choices=[("Enabled", "true"), ("Disabled", "false"), ("Enabled (Silent)", "silent")], label="Narrator enabled", value="true" if params["tgwui"]["tgwui_narrator_enabled"] == "true" else ("silent" if params["tgwui"]["tgwui_narrator_enabled"] == "silent" else "false"))
- language = gr.Dropdown(value=params["tgwui"]["tgwui_language"], label="Default Language", choices=languages_list)
+ activate = gr.Dropdown(
+ choices={"Enabled": "true", "Disabled": "false"},
+ label="Activate TTS",
+ value=(
+ "Enabled"
+ if config.tgwui.tgwui_activate_tts
+ else "Disabled"
+ ),
+ allow_custom_value=True,
+ )
+ autoplay = gr.Dropdown(
+ choices={"Enabled": "true", "Disabled": "false"},
+ label="Autoplay TTS",
+ value=(
+ "Enabled"
+ if config.tgwui.tgwui_autoplay_tts
+ else "Disabled"
+ ),
+ allow_custom_value=True,
+ )
+ show_text = gr.Dropdown(
+ choices={"Enabled": "true", "Disabled": "false"},
+ label="Show Text",
+ value=(
+ "Enabled"
+ if config.tgwui.tgwui_show_text
+ else "Disabled"
+ ),
+ allow_custom_value=True,
+ )
+ narrator_enabled = gr.Dropdown(
+ choices=[
+ ("Enabled", "true"),
+ ("Disabled", "false"),
+ ("Enabled (Silent)", "silent"),
+ ],
+ label="Narrator enabled",
+ value=(
+ "true"
+ if config.tgwui.tgwui_narrator_enabled == "true"
+ else (
+ "silent"
+ if config.tgwui.tgwui_narrator_enabled == "silent"
+ else "false"
+ )
+ ),
+ allow_custom_value=True,
+ )
+ language = gr.Dropdown(
+ value=config.tgwui.tgwui_language,
+ label="Default Language",
+ choices=_state['gradio_languages_list'],
+ allow_custom_value=True,
+ )
with gr.Row():
submit_button = gr.Button("Update Settings")
- output_message = gr.Textbox(label="Output Message", interactive=False, show_label=False)
-
- submit_button.click(update_settings_tg, inputs=[activate, autoplay, show_text, language, narrator_enabled], outputs=output_message)
+ output_message = gr.Textbox(
+ label="Output Message", interactive=False, show_label=False
+ )
+
+ submit_button.click(
+ update_settings_tgwui,
+ inputs=[
+ activate,
+ autoplay,
+ show_text,
+ language,
+ narrator_enabled,
+ ],
+ outputs=output_message,
+ )
disk_space_page = get_disk_interface()
disk_space_page()
-
- if params.get("gradio_pages", {}).get("TTS_Engines_Settings_page", True):
+
+ if config.gradio_pages.TTS_Engines_Settings_page:
with gr.Tab("TTS Engines Settings"):
- with gr.Tabs():
- for engine_name in engines_available:
+ with gr.Tabs():
+ for engine_name in _state.get('srv_engines_available', []):
+ if base_package:
+ module_name = (
+ f"{base_package}.system.tts_engines."
+ f"{engine_name}."
+ f"{engine_name}_settings_page"
+ )
+ else:
+ module_name = f"system.tts_engines.{engine_name}.{engine_name}_settings_page"
+
+ module = dynamic_import(module_name, base_package)
+ if module:
with gr.Tab(f"{engine_name.capitalize()} TTS"):
gr.Markdown(f"### {engine_name.capitalize()} TTS")
- globals()[f"{engine_name}_at_gradio_settings_page"](globals()[f"{engine_name}_model_config_data"])
- if params.get("gradio_pages", {}).get("alltalk_documentation_page", True):
- alltalk_documentation()
- if params.get("gradio_pages", {}).get("api_documentation_page", True):
- api_documentation()
+ getattr(
+ module,
+ f"{engine_name}_at_gradio_settings_page"
+ )(
+ globals()[f"{engine_name}_model_config_data"]
+ )
+
+ if config.gradio_pages.alltalk_documentation_page:
+ with gr.Tab("Documentation"):
+ with gr.Accordion("📖 HELP - Narrator Function", open=False):
+ gr.Markdown(
+ AllTalkHelpContent.NARRATOR, elem_classes="custom-markdown"
+ )
+ with gr.Row():
+ gr.Markdown(
+ AllTalkHelpContent.NARRATOR1,
+ elem_classes="custom-markdown",
+ )
+ gr.Markdown(
+ AllTalkHelpContent.NARRATOR2,
+ elem_classes="custom-markdown",
+ )
+ with gr.Accordion(
+ "🔌 HELP - API Standard TTS Generation Endpoints", open=False
+ ):
+ gr.Markdown(
+ AllTalkHelpContent.API_STANDARD,
+ elem_classes="custom-markdown",
+ )
+ with gr.Accordion(
+ "🔌 HELP - API Streaming TTS Generation Endpoints", open=False
+ ):
+ gr.Markdown(
+ AllTalkHelpContent.API_STREAMING,
+ elem_classes="custom-markdown",
+ )
+ with gr.Accordion(
+ "🔌 HELP - API Server Control Endpoints", open=False
+ ):
+ gr.Markdown(
+ AllTalkHelpContent.API_CONTROL,
+ elem_classes="custom-markdown",
+ )
+ with gr.Accordion(
+ "🔌 HELP - API Server Status Endpoints", open=False
+ ):
+ gr.Markdown(
+ AllTalkHelpContent.API_STATUS,
+ elem_classes="custom-markdown",
+ )
+ with gr.Accordion(
+ "🔌 HELP - API OpenAI V1 Speech Endpoints", open=False
+ ):
+ gr.Markdown(
+ AllTalkHelpContent.API_OPENAPI,
+ elem_classes="custom-markdown",
+ )
+
with gr.Tab("About this project"):
- alltalk_about()
+ with gr.Row():
+ gr.Markdown(
+ AllTalkHelpContent.WELCOME1, elem_classes="custom-markdown"
+ )
+ gr.Markdown(
+ AllTalkHelpContent.WELCOME2, elem_classes="custom-markdown"
+ )
+
return app
+ # pylint: disable=redefined-outer-name
if __name__ == "__main__":
app = alltalk_gradio().queue()
- app.launch(server_name="0.0.0.0", server_port=params['gradio_port_number'], prevent_thread_lock=True, quiet=True)
+ app.launch(
+ server_name="0.0.0.0",
+ server_port=config.gradio_port_number,
+ prevent_thread_lock=True,
+ quiet=True,
+ )
if not running_in_standalone:
app = alltalk_gradio().queue()
- app.launch(server_name="0.0.0.0", server_port=params['gradio_port_number'], prevent_thread_lock=True, quiet=True)
+ app.launch(
+ server_name="0.0.0.0",
+ server_port=config.gradio_port_number,
+ prevent_thread_lock=True,
+ quiet=True,
+ )
#########################################
# START-UP # Final Splash before Gradio #
#########################################
-print(f"[{branding}TTS] Please use \033[91mCtrl+C\033[0m when exiting AllTalk otherwise a")
-print(f"[{branding}TTS] subprocess may continue running in the background.")
-print(f"[{branding}TTS]")
-print(f"[{branding}TTS] {branding}Server Ready")
+print_message("Please use \033[91mCtrl+C\033[0m when exiting AllTalk otherwise a")
+print_message("subprocess may continue running in the background.")
+print_message("")
+print_message("Server Ready")
###############################################################################################
# START-UP # Loop to keep the script from exiting out if its being run as a standalone script #
diff --git a/system/TGWUI Extension/script.py b/system/TGWUI Extension/script.py
deleted file mode 100644
index 976e360..0000000
--- a/system/TGWUI Extension/script.py
+++ /dev/null
@@ -1,696 +0,0 @@
-import re
-import json
-import time
-import random
-import logging
-import requests
-import threading
-import gradio as gr
-from pathlib import Path
-from modules import chat, shared, ui_chat
-from modules.utils import gradio
-from requests.exceptions import RequestException, ConnectionError
-# Store the current disable level
-current_disable_level = logging.getLogger().manager.disable
-
-####################################################################
-# Load in the confignew.json and put the main settings into params #
-####################################################################
-this_dir = Path(__file__).parent.resolve()
-def load_config(file_path):
- with open(file_path, "r") as config_file:
- config = json.load(config_file)
- return config
-# Set confiig file path
-config_file_path = this_dir / "tgwui_remote_config.json"
-# Load the params dictionary from the tgwui_remote_config.json file
-params = load_config(config_file_path)
-# Set branding
-branding = params['branding']
-
-################################
-# Print Spashscreen to console #
-################################
-print(f"[{branding}Startup]\033[94m _ _ _ \033[1;35m_____ _ _ \033[0m _____ _____ ____ ")
-print(f"[{branding}Startup]\033[94m / \ | | |\033[1;35m_ _|_ _| | | __ \033[0m |_ _|_ _/ ___| ")
-print(f"[{branding}Startup]\033[94m / _ \ | | |\033[1;35m | |/ _` | | |/ / \033[0m | | | | \___ \ ")
-print(f"[{branding}Startup]\033[94m / ___ \| | |\033[1;35m | | (_| | | < \033[0m | | | | ___) |")
-print(f"[{branding}Startup]\033[94m /_/ \_\_|_|\033[1;35m |_|\__,_|_|_|\_\ \033[0m |_| |_| |____/ ")
-print(f"[{branding}Startup]")
-print(f"[{branding}Startup] \033[92m{branding}startup Mode : \033[93mText-Gen-webui Remote\033[0m")
-print(f"[{branding}Startup]")
-
-##########################
-# Setup global variables #
-##########################
-# STARTUP VARIABLE - Import languges file for Gradio to be able to display them in the interface
-with open(this_dir / "languages.json", encoding="utf8") as f:
- languages = json.load(f)
-# Create a global lock for tracking TTS generation occuring
-process_lock = threading.Lock()
-# Pull the values for IP address, port and protocol for communication with the AllTalk renote server
-alltalk_protocol = params["api_def"]["api_alltalk_protocol"]
-alltalk_ip_port = params["api_def"]["api_alltalk_ip_port"]
-# Pull the connection timeout value for communication requests with the AllTalk remote server
-connection_timeout = params["api_def"]["api_connection_timeout"]
-# Create a few base global variables that are required
-models_available = None # Gets populated with the list of all models/engines available on the AllTalk Server
-alltalk_settings = None # Gets populated with the list of all settings currently set on the AllTalk Server
-current_model_loaded = None # Gets populated with the name of the current loaded TTS engine/model on the AllTalk Server
-# Used to detect if a model is loaded in to AllTalk server to block TTS genereation if needed.
-tts_model_loaded = None
-
-#################################################
-# Pull all the settings from the AllTalk Server #
-#################################################
-def get_alltalk_settings():
- global current_model_loaded, models_available
- voices_url = f"{alltalk_protocol}{alltalk_ip_port}/api/voices"
- settings_url = f"{alltalk_protocol}{alltalk_ip_port}/api/currentsettings"
- rvcvoices_url = f"{alltalk_protocol}{alltalk_ip_port}/api/rvcvoices"
-
- try:
- voices_response = requests.get(voices_url, timeout=connection_timeout)
- rvcvoices_response = requests.get(rvcvoices_url, timeout=connection_timeout)
- settings_response = requests.get(settings_url, timeout=connection_timeout)
-
- if voices_response.status_code == 200 and settings_response.status_code == 200:
- voices_data = voices_response.json()
- rvcvoices_data = rvcvoices_response.json()
- settings_data = settings_response.json()
-
- models_available = [model["name"] for model in settings_data["models_available"]]
- current_model_loaded = settings_data["current_model_loaded"]
-
- return {
- "voices": sorted(voices_data["voices"]),
- "rvcvoices": (rvcvoices_data["rvcvoices"]),
- "models_available": models_available,
- "current_model_loaded": current_model_loaded,
- "manufacturer_name": settings_data.get("manufacturer_name", ""),
- "deepspeed_capable": settings_data.get("deepspeed_capable", False),
- "deepspeed_available": settings_data.get("deepspeed_available", False),
- "deepspeed_enabled": settings_data.get("deepspeed_enabled", False),
- "generationspeed_capable": settings_data.get("generationspeed_capable", False),
- "generationspeed_set": settings_data.get("generationspeed_set", 1.0),
- "lowvram_capable": settings_data.get("lowvram_capable", False),
- "lowvram_enabled": settings_data.get("lowvram_enabled", False),
- "pitch_capable": settings_data.get("pitch_capable", False),
- "pitch_set": settings_data.get("pitch_enabled", False),
- "repetitionpenalty_capable": settings_data.get("repetitionpenalty_capable", False),
- "repetitionpenalty_set": settings_data.get("repetitionpenalty_set", 10.0),
- "streaming_capable": settings_data.get("streaming_capable", False),
- "temperature_capable": settings_data.get("temperature_capable", False),
- "temperature_set": settings_data.get("temperature_set", 0.75),
- "ttsengines_installed": settings_data.get("ttsengines_installed", False),
- "languages_capable": settings_data.get("languages_capable", False),
- "multivoice_capable": settings_data.get("multivoice_capable", False),
- "multimodel_capable": settings_data.get("multimodel_capable", False),
- }
- else:
- print(f"[{branding}Server] \033[91mWarning\033[0m Failed to retrieve {branding}settings from API.")
- if voices_response.status_code != 200:
- print(f"[{branding}Server] \033[91mWarning\033[0m Failed to retrieve voices from API. Status code:\n{voices_response.status_code}")
- if settings_response.status_code != 200:
- print(f"[{branding}Server] \033[91mWarning\033[0m Failed to retrieve current settings from API. Status code:\n{settings_response.status_code}")
- return {
- "voices": ["Please Refresh Settings"],
- "rvcvoices": ["Please Refresh Settings"],
- "models_available": ["Please Refresh Settings"],
- "current_model_loaded": "Please Refresh Settings",
- "manufacturer_name": "",
- "deepspeed_capable": False,
- "deepspeed_available": False,
- "deepspeed_enabled": False,
- "generationspeed_capable": False,
- "generationspeed_set": 1.0,
- "lowvram_capable": False,
- "lowvram_enabled": False,
- "pitch_capable": False,
- "pitch_set": 0,
- "repetitionpenalty_capable": False,
- "repetitionpenalty_set": 10.0,
- "streaming_capable": False,
- "temperature_capable": False,
- "temperature_set": 0.75,
- "ttsengines_installed": False,
- "languages_capable": False,
- "multivoice_capable": False,
- "multimodel_capable": False,
- }
- except (RequestException, ConnectionError) as e:
- print(f"[{branding}Server] \033[91mWarning\033[0m Unable to connect to the {branding}server. Status code:\n{str(e)}")
- return {
- "voices": ["Please Refresh Settings"],
- "rvcvoices": ["Please Refresh Settings"],
- "models_available": ["Please Refresh Settings"],
- "current_model_loaded": "Please Refresh Settings",
- "manufacturer_name": "",
- "deepspeed_capable": False,
- "deepspeed_available": False,
- "deepspeed_enabled": False,
- "generationspeed_capable": False,
- "generationspeed_set": 1.0,
- "lowvram_capable": False,
- "lowvram_enabled": False,
- "pitch_capable": False,
- "pitch_set": 0,
- "repetitionpenalty_capable": False,
- "repetitionpenalty_set": 10.0,
- "streaming_capable": False,
- "temperature_capable": False,
- "temperature_set": 0.75,
- "ttsengines_installed": False,
- "languages_capable": False,
- "multivoice_capable": False,
- "multimodel_capable": False,
- }
-
-#############################
-#### TTS STOP GENERATION ####
-#############################
-def stop_generate_tts():
- api_url = f"{alltalk_protocol}{alltalk_ip_port}/api/stop-generation"
- try:
- response = requests.put(api_url, timeout=connection_timeout)
- if response.status_code == 200:
- return response.json()["message"]
- else:
- print(f"[{branding}Server] \033[91mWarning\033[0m Failed to stop generation. Status code:\n{response.status_code}")
- return {"message": "Failed to stop generation"}
- except (RequestException, ConnectionError) as e:
- print(f"[{branding}Server] \033[91mWarning\033[0m Unable to connect to the {branding}server. Status code:\n{str(e)}")
- return {"message": "Failed to stop generation"}
-
-#####################################
-#### MODEL LOADING AND UNLOADING ####
-#####################################
-# MODEL - Swap model based on Gradio selection
-def send_reload_request(value_sent):
- try:
- url = f"{alltalk_protocol}{alltalk_ip_port}/api/reload"
- payload = {"tts_method": value_sent}
- response = requests.post(url, params=payload)
- response.raise_for_status() # Raises an HTTPError for bad responses
- except requests.exceptions.RequestException as e:
- # Handle the HTTP request error
- print(f"[{branding}Server] \033[91mWarning\033[0m Error during request to webserver process: Status code:\n{e}")
- return {"status": "error", "message": str(e)}
-
-####################################################################################################
-# TGWUI # Saves all settings back to the config file for the REMOTE version of the TGWUI extension #
-####################################################################################################
-def tgwui_save_settings():
- settings = {
- "branding": params['branding'],
- "tgwui": {
- "tgwui_activate_tts": params["tgwui"]['tgwui_activate_tts'],
- "tgwui_autoplay_tts": params["tgwui"]['tgwui_autoplay_tts'],
- "tgwui_narrator_enabled": params["tgwui"]['tgwui_narrator_enabled'],
- "tgwui_non_quoted_text_is": params["tgwui"]["tgwui_non_quoted_text_is"],
- "tgwui_deepspeed_enabled": params["tgwui"]['tgwui_deepspeed_enabled'],
- "tgwui_language": params["tgwui"]['tgwui_language'],
- "tgwui_lowvram_enabled": params["tgwui"]['tgwui_lowvram_enabled'],
- "tgwui_pitch_set": params["tgwui"]['tgwui_pitch_set'],
- "tgwui_temperature_set": params["tgwui"]['tgwui_temperature_set'],
- "tgwui_repetitionpenalty_set": params["tgwui"]['tgwui_repetitionpenalty_set'],
- "tgwui_generationspeed_set": params["tgwui"]['tgwui_generationspeed_set'],
- "tgwui_narrator_voice": params["tgwui"]['tgwui_narrator_voice'],
- "tgwui_show_text": params["tgwui"]['tgwui_show_text'],
- "tgwui_character_voice": params["tgwui"]['tgwui_character_voice'],
- "tgwui_rvc_char_voice": params["tgwui"]['tgwui_rvc_char_voice'],
- "tgwui_rvc_narr_voice": params["tgwui"]['tgwui_rvc_narr_voice']
- },
- "api_def": {
- "api_use_legacy_api": params['api_def']['api_use_legacy_api'],
- "api_alltalk_protocol": alltalk_protocol,
- "api_alltalk_ip_port": alltalk_ip_port,
- "api_connection_timeout": 5,
- }
- }
-
- with open(config_file_path, "w") as file:
- json.dump(settings, file, indent=4)
-
-#############################################
-# Low VRAM change request to enable/disable #
-#############################################
-# LOW VRAM - Gradio Checkbox handling
-def send_lowvram_request(value_sent):
- try:
- params["tgwui"]["tts_model_loaded"] = False
- if value_sent:
- audio_path = this_dir / "lowvramenabled.wav"
- else:
- audio_path = this_dir / "lowvramdisabled.wav"
- url = f"{alltalk_protocol}{alltalk_ip_port}/api/lowvramsetting?new_low_vram_value={value_sent}"
- headers = {"Content-Type": "application/json"}
- response = requests.post(url, headers=headers)
- response.raise_for_status() # Raises an HTTPError for bad responses
- json_response = response.json()
- # Check if the low VRAM request was successful
- if json_response.get("status") == "lowvram-success":
- # Update any relevant variables or perform other actions on success
- params["tgwui"]["tts_model_loaded"] = True
- return f''
- except requests.exceptions.RequestException as e:
- # Handle the HTTP request error
- print(f"[{branding}Server] \033[91mWarning\033[0m Error during request to webserver process: Status code:\n{e}")
- return {"status": "error", "message": str(e)}
-
-###################
-#### DeepSpeed ####
-###################
-def send_deepspeed_request(value_sent):
- try:
- params["tgwui"]["tts_model_loaded"] = False
- if value_sent:
- audio_path = this_dir / "deepspeedenabled.wav"
- else:
- audio_path = this_dir / "deepspeeddisabled.wav"
- url = f"{alltalk_protocol}{alltalk_ip_port}/api/deepspeed?new_deepspeed_value={value_sent}"
- headers = {"Content-Type": "application/json"}
- response = requests.post(url, headers=headers)
- response.raise_for_status() # Raises an HTTPError for bad responses
- json_response = response.json()
- # Check if the deepspeed request was successful
- if json_response.get("status") == "deepspeed-success":
- # Update any relevant variables or perform other actions on success
- params["tgwui"]["tts_model_loaded"] = True
- return f''
- except requests.exceptions.RequestException as e:
- # Handle the HTTP request error
- print(f"[{branding}Server] \033[91mWarning\033[0m Error during request to webserver process: Status code:\n{e}")
- return {"status": "error", "message": str(e)}
-
-#################################
-#### TTS STANDARD GENERATION ####
-#################################
-def send_and_generate(gen_text, gen_character_voice, gen_rvccharacter_voice, gen_narrator_voice, gen_rvcnarrator_voice, gen_narrator_activated, gen_textnotinisde, gen_repetition, gen_language, gen_filter, gen_speed, gen_pitch, gen_autoplay, gen_autoplay_vol, gen_file_name, gen_temperature, gen_filetimestamp, gen_stream, gen_stopcurrentgen):
- api_url = f"{alltalk_protocol}{alltalk_ip_port}/api/tts-generate"
- if gen_stopcurrentgen:
- stop_generate_tts() # Call for the current generation to stop
- tgwui_save_settings() # Save the current TGWUI settings to the config file
- if gen_stream == "true":
- api_url = f"{alltalk_protocol}{alltalk_ip_port}/api/tts-generate-streaming"
- encoded_text = requests.utils.quote(gen_text)
- streaming_url = f"{api_url}?text={encoded_text}&voice={gen_character_voice}&language={gen_language}&output_file={gen_file_name}"
- return streaming_url, str("TTS Streaming Audio Generated")
- else:
- data = {
- "text_input": gen_text,
- "text_filtering": gen_filter,
- "character_voice_gen": gen_character_voice,
- "rvccharacter_voice_gen": gen_rvccharacter_voice,
- "narrator_enabled": str(gen_narrator_activated).lower(),
- "narrator_voice_gen": gen_narrator_voice,
- "rvcnarrator_voice_gen": gen_rvcnarrator_voice,
- "text_not_inside": gen_textnotinisde,
- "language": gen_language,
- "output_file_name": str(gen_file_name),
- "output_file_timestamp": str(gen_filetimestamp).lower(),
- "autoplay": str(gen_autoplay).lower(),
- "autoplay_volume": str(gen_autoplay_vol),
- "speed": str(gen_speed),
- "pitch": str(gen_pitch),
- "temperature": str(gen_temperature),
- "repetition_penalty": str(gen_repetition),
- }
- #print(f"Debug: Generate request param:", data) if params["debug_tts"] else None
- try:
- response = requests.post(api_url, data=data)
- response.raise_for_status() # Raise an exception for 4xx or 5xx status codes
- result = response.json()
- if gen_autoplay == "true":
- return None, str("TTS Audio Generated (Played remotely)")
- else:
- if params["api_def"]["api_use_legacy_api"]:
- return result['output_file_url'], str("TTS Audio Generated")
- else:
- # Prepend the URL and PORT to the output_file_url
- output_file_url = f"{alltalk_protocol}{alltalk_ip_port}{result['output_file_url']}"
- return output_file_url, str("TTS Audio Generated")
- except (RequestException, ConnectionError) as e:
- print(f"[{branding}Server] \033[91mWarning\033[0m Error occurred during the API request: Status code:\n{str(e)}")
- return None, str("Error occurred during the API request")
-
-####################################################################
-# TGWUI # Text-generation-webui sends text for TTS generation here #
-####################################################################
-def output_modifier(string, state):
- if not params["tgwui"]["tgwui_activate_tts"]:
- return string
- # Strip out Images
- img_info = ""
- cleaned_text, img_info = tgwui_extract_and_remove_images(string)
- if cleaned_text is None:
- return
- # Get current settings
- language_code = languages.get(params["tgwui"]["tgwui_language"])
- character_voice = params["tgwui"]["tgwui_character_voice"]
- rvc_character_voice = params["tgwui"]["tgwui_rvc_char_voice"]
- narrator_voice = params["tgwui"]["tgwui_narrator_voice"]
- rvc_narrator_voice = params["tgwui"]["tgwui_rvc_narr_voice"]
- narrator_enabled = params["tgwui"]["tgwui_narrator_enabled"]
- text_not_inside = params["tgwui"]["tgwui_non_quoted_text_is"]
- repetition_policy = params["tgwui"]["tgwui_repetitionpenalty_set"]
- text_filtering = "html"
- speed = params["tgwui"]["tgwui_generationspeed_set"]
- pitch = params["tgwui"]["tgwui_pitch_set"]
- autoplay = False # This is never used in TGWUI so a base False setting is sent
- autoplay_volume = 0.8 # This is never used in TGWUI so a base False setting is sent
- temperature = params["tgwui"]["tgwui_temperature_set"]
- # Lock and process TTS request
- if process_lock.acquire(blocking=False):
- try:
- if "character_menu" in state:
- output_file = state["character_menu"]
- else:
- output_file = str("TTSOUT_")
- generate_response, status_message = send_and_generate(cleaned_text, character_voice, rvc_character_voice, narrator_voice, rvc_narrator_voice, narrator_enabled, text_not_inside, repetition_policy, language_code, text_filtering, speed, pitch, autoplay, autoplay_volume, output_file, temperature, True, False, False)
- if status_message == "TTS Audio Generated":
- # Handle Gradio and playback
- autoplay = "autoplay" if params["tgwui"]["tgwui_autoplay_tts"] else ""
- string = f''
- if params["tgwui"]["tgwui_show_text"]:
- string += tgwui_reinsert_images(cleaned_text, img_info)
- shared.processing_message = "*Is typing...*"
- return string
- else:
- print(f"[{branding}Server] \033[91mWarning\033[0m Audio generation failed. Status code:\n{status_message}")
- finally:
- # Always release the lock, whether an exception occurs or not
- process_lock.release()
- else:
- # The lock is already acquired
- print(f"[{branding}Model] \033[91mWarning\033[0m Audio generation is already in progress. Please wait.")
- return
-
-###########################################################################################################
-# TGWUI # Strips out images from the TGWUI string if needed and re-inserts the image after TTS generation #
-###########################################################################################################
-img_pattern = r']*src\s*=\s*["\'][^"\'>]+["\'][^>]*>'
-
-def tgwui_extract_and_remove_images(text):
- """
- Extracts all image data from the text and removes it for clean TTS processing.
- Returns the cleaned text and the extracted image data.
- """
- img_matches = re.findall(img_pattern, text)
- img_info = "\n".join(img_matches) # Store extracted image data
- cleaned_text = re.sub(img_pattern, '', text) # Remove images from text
- return cleaned_text, img_info
-
-def tgwui_reinsert_images(text, img_info):
- """
- Reinserts the previously extracted image data back into the text.
- """
- if img_info: # Check if there are images to reinsert
- text += f"\n\n{img_info}"
- return text
-
-################################################################
-# TGWUI # Used to generate a preview voice sample within TGWUI #
-################################################################
-def random_sentence():
- with open(this_dir / "harvard_sentences.txt") as f:
- return random.choice(list(f)).rstrip()
-
-def voice_preview(string):
- if not params["tgwui"]["tgwui_activate_tts"]:
- return string
- language_code = languages.get(params["tgwui"]["tgwui_language"])
- if not string:
- string = random_sentence()
- generate_response, status_message = send_and_generate(string, params["tgwui"]["tgwui_character_voice"], params["tgwui"]["tgwui_rvc_char_voice"], params["tgwui"]["tgwui_narrator_voice"], params["tgwui"]["tgwui_rvc_narr_voice"], False, "character", params["tgwui"]["tgwui_repetitionpenalty_set"], language_code, "standard", params["tgwui"]["tgwui_generationspeed_set"], params["tgwui"]["tgwui_pitch_set"], False, 0.8, "previewvoice", params["tgwui"]["tgwui_temperature_set"], False, False, False)
- if status_message == "TTS Audio Generated":
- # Handle Gradio and playback
- autoplay = "autoplay" if params["tgwui"]["tgwui_autoplay_tts"] else ""
- return f''
- else:
- # Handle the case where audio generation was not successful
- return f"[{branding}Server] Audio generation failed. Status code:\n{status_message}"
-
-###################################################################
-# TGWUI # Used to inform TGWUI that TTS is disabled/not activated #
-###################################################################
-def state_modifier(state):
- if not params["tgwui"]["tgwui_activate_tts"]:
- return state
- state["stream"] = False
- return state
-
-###################################################################
-# TGWUI # Sends message to TGWUI interface during TTS generation #
-###################################################################
-def input_modifier(string, state):
- if not params["tgwui"]["tgwui_activate_tts"]:
- return string
- shared.processing_message = "*Is recording a voice message...*"
- return string
-
-########################################################################
-# TGWUI # Used to delete historic TTS audios from TGWUI chat interface #
-########################################################################
-def remove_tts_from_history(history):
- for i, entry in enumerate(history["internal"]):
- history["visible"][i] = [history["visible"][i][0], entry[1]]
- return history
-
-def toggle_text_in_history(history):
- for i, entry in enumerate(history["visible"]):
- visible_reply = entry[1]
- if visible_reply.startswith("\n\n{reply}",
- ]
- else:
- history["visible"][i] = [
- history["visible"][i][0],
- f"{visible_reply.split('')[0]}",
- ]
- return history
-
-def history_modifier(history):
- # Remove autoplay from the last reply
- if len(history["internal"]) > 0:
- history["visible"][-1] = [
- history["visible"][-1][0],
- history["visible"][-1][1].replace("controls autoplay>", "controls>"),
- ]
- return history
-
-###########################################################
-# TGWUI # Update IP address and Port from TGWUI interface #
-###########################################################
-def tgwui_update_alltalk_ip_port(value_sent):
- global alltalk_ip_port
- alltalk_ip_port = value_sent
-
-###########################################################
-# TGWUI # Update http/https protocol from TGWUI interface #
-###########################################################
-# Update Protocol from Gradio
-def tgwui_update_alltalk_protocol(value_sent):
- global alltalk_protocol
- alltalk_protocol = value_sent
-
-###########################################################################################################################
-# TGWUI # Debounces model updates when tgwui_update_dropdowns is called, preventing an update from causing a model reload #
-###########################################################################################################################
-def tgwui_handle_ttsmodel_dropdown_change(model_name):
- if not getattr(tgwui_handle_ttsmodel_dropdown_change, "skip_reload", False):
- send_reload_request(model_name)
- tgwui_handle_ttsmodel_dropdown_change.skip_reload = False
-
-##################################################################################################
-# TGWUI # Pulls the current AllTalk Server settings & updates gradio when Refresh button pressed #
-##################################################################################################
-def tgwui_update_dropdowns():
- global at_settings
- at_settings = get_alltalk_settings() # Pull all the current settings from the AllTalk server, if its online.
- current_voices = at_settings["voices"]
- rvccurrent_voices = at_settings["rvcvoices"]
- rvccurrent_character_voice = params["tgwui"]["tgwui_rvc_char_voice"]
- rvccurrent_narrator_voice = params["tgwui"]["tgwui_rvc_char_voice"]
- current_models_available = sorted(at_settings["models_available"])
- current_model_loaded = at_settings["current_model_loaded"]
- current_character_voice = params["tgwui"]["tgwui_character_voice"]
- current_narrator_voice = params["tgwui"]["tgwui_narrator_voice"]
- current_lowvram_capable = at_settings["lowvram_capable"]
- current_lowvram_enabled = at_settings["lowvram_enabled"]
- current_temperature_capable = at_settings["temperature_capable"]
- current_repetitionpenalty_capable = at_settings["repetitionpenalty_capable"]
- current_generationspeed_capable = at_settings["generationspeed_capable"]
- current_pitch_capable = at_settings["pitch_capable"]
- current_deepspeed_capable = at_settings["deepspeed_capable"]
- current_deepspeed_enabled = at_settings["deepspeed_enabled"]
- current_non_quoted_text_is = params.get("tgwui")["tgwui_non_quoted_text_is"] # Use the correct parameter path
- current_languages_capable = at_settings["languages_capable"]
- if at_settings["languages_capable"]:
- language_label = "Languages"
- else:
- language_label = "Model not multi language"
- if current_character_voice not in current_voices:
- current_character_voice = current_voices[0] if current_voices else ""
- params["tgwui"]["tgwui_character_voice"] = current_character_voice
- if current_narrator_voice not in current_voices:
- current_narrator_voice = current_voices[0] if current_voices else ""
- params["tgwui"]["tgwui_narrator_voice"] = current_narrator_voice
- if rvccurrent_character_voice not in rvccurrent_voices:
- rvccurrent_character_voice = rvccurrent_voices[0] if rvccurrent_voices else ""
- params["tgwui"]["tgwui_rvc_char_voice"] = rvccurrent_character_voice
- if rvccurrent_narrator_voice not in rvccurrent_voices:
- rvccurrent_narrator_voice = rvccurrent_voices[0] if rvccurrent_voices else ""
- params["tgwui"]["tgwui_rvc_char_voice"] = rvccurrent_narrator_voice
- tgwui_handle_ttsmodel_dropdown_change.skip_reload = True # Debounce tgwui_tts_dropdown_gr and stop it sending a model reload when it is updated.
-
- return (
- gr.Checkbox(interactive=current_lowvram_capable, value=current_lowvram_enabled),
- gr.Checkbox(interactive=current_deepspeed_capable, value=current_deepspeed_enabled),
- gr.Dropdown(choices=current_voices, value=current_character_voice),
- gr.Dropdown(choices=rvccurrent_voices, value=rvccurrent_character_voice, interactive=True),
- gr.Dropdown(choices=current_voices, value=current_narrator_voice),
- gr.Dropdown(choices=rvccurrent_voices, value=rvccurrent_narrator_voice, interactive=True),
- gr.Dropdown(choices=current_models_available, value=current_model_loaded),
- gr.Dropdown(interactive=current_temperature_capable),
- gr.Dropdown(interactive=current_repetitionpenalty_capable),
- gr.Dropdown(interactive=current_languages_capable, label=language_label),
- gr.Dropdown(interactive=current_generationspeed_capable),
- gr.Dropdown(interactive=current_pitch_capable),
- gr.Dropdown(value=current_non_quoted_text_is),
- )
-
-###############################################################
-# TGWUI # Gradio interface & layout for Text-generation-webui #
-###############################################################
-def ui():
- global alltalk_settings
- alltalk_settings = get_alltalk_settings() # Pull all the current settings from the AllTalk server, if its online.
- with gr.Accordion(params["branding"] + " TTS (Text-gen-webui Remote)"):
- tgwui_available_voices_gr = alltalk_settings["voices"]
- tgwui_rvc_available_voices_gr = alltalk_settings["rvcvoices"]
- # Activate alltalk_tts, Enable autoplay, Show text
- with gr.Row():
- tgwui_activate_tts_gr = gr.Checkbox(value=params["tgwui"]["tgwui_activate_tts"], label="Enable TGWUI TTS")
- tgwui_autoplay_gr = gr.Checkbox(value=params["tgwui"]["tgwui_autoplay_tts"], label="Autoplay TTS Generated")
- tgwui_show_text_gr = gr.Checkbox(value=params["tgwui"]["tgwui_show_text"], label="Show Text in chat")
-
- # Low vram enable, Deepspeed enable, Link
- with gr.Row():
- tgwui_lowvram_enabled_gr = gr.Checkbox(value=alltalk_settings["lowvram_enabled"] if alltalk_settings["lowvram_capable"] else False, label="Enable Low VRAM Mode", interactive=alltalk_settings["lowvram_capable"])
- tgwui_lowvram_enabled_play_gr = gr.HTML(visible=False)
- tgwui_deepspeed_enabled_gr = gr.Checkbox(value=params["tgwui"]["tgwui_deepspeed_enabled"], label="Enable DeepSpeed", interactive=alltalk_settings["deepspeed_capable"],)
- tgwui_deepspeed_enabled_play_gr = gr.HTML(visible=False)
- tgwui_empty_space_gr = gr.HTML(f"
AllTalk Server & Documentation Link")
-
- # Model, Language, Character voice
- with gr.Row():
- tgwui_tts_dropdown_gr = gr.Dropdown(choices=models_available, label="TTS Engine/Model", value=current_model_loaded,)
- tgwui_language_gr = gr.Dropdown(languages.keys(), label="Languages" if alltalk_settings["languages_capable"] else "Model not multi language", interactive=alltalk_settings["languages_capable"], value=params["tgwui"]["tgwui_language"])
- tgwui_narrator_enabled_gr = gr.Dropdown(choices=[("Enabled", "true"), ("Disabled", "false"), ("Silent", "silent")], label="Narrator Enable", value="true" if params.get("tgwui_narrator_enabled") == "true" else ("silent" if params.get("tgwui_narrator_enabled") == "silent" else "false"))
-
- # Narrator
- with gr.Row():
- tgwui_available_voices_gr = alltalk_settings["voices"]
- tgwui_default_voice_gr = params["tgwui"]["tgwui_character_voice"]
- if tgwui_default_voice_gr not in tgwui_available_voices_gr:
- tgwui_default_voice_gr = tgwui_available_voices_gr[0] if tgwui_available_voices_gr else ""
- tgwui_character_voice_gr = gr.Dropdown(choices=tgwui_available_voices_gr, label="Character Voice", value=tgwui_default_voice_gr, allow_custom_value=True,)
- tgwui_narr_voice_gr = params["tgwui"]["tgwui_narrator_voice"]
- if tgwui_narr_voice_gr not in tgwui_available_voices_gr:
- tgwui_narr_voice_gr = tgwui_available_voices_gr[0] if tgwui_available_voices_gr else ""
- tgwui_narrator_voice_gr = gr.Dropdown(choices=tgwui_available_voices_gr, label="Narrator Voice", value=tgwui_narr_voice_gr, allow_custom_value=True,)
- tgwui_non_quoted_text_is_gr = gr.Dropdown(choices=[("Character", "character"), ("Narrator", "narrator"), ("Silent", "silent")], label='Narrator unmarked text is', value=params.get("tgwui_non_quoted_text_is", "character"))
-
- # RVC voices
- with gr.Row():
- tgwui_rvc_default_voice_gr = params["tgwui"]["tgwui_rvc_char_voice"]
- tgwui_rvc_narrator_voice_gr = params["tgwui"]["tgwui_rvc_narr_voice"]
- if tgwui_rvc_default_voice_gr not in tgwui_rvc_available_voices_gr:
- tgwui_rvc_default_voice_gr = tgwui_rvc_available_voices_gr[0] if tgwui_rvc_available_voices_gr else ""
- tgwui_rvc_char_voice_gr = gr.Dropdown(choices=tgwui_rvc_available_voices_gr, label="RVC Character Voice", value=tgwui_rvc_default_voice_gr, allow_custom_value=True,)
- if tgwui_rvc_narrator_voice_gr not in tgwui_rvc_available_voices_gr:
- tgwui_rvc_narrator_voice_gr = tgwui_rvc_available_voices_gr[0] if tgwui_rvc_available_voices_gr else ""
- tgwui_rvc_narr_voice_gr = gr.Dropdown(choices=tgwui_rvc_available_voices_gr, label="RVC Narrator Voice", value=tgwui_rvc_narrator_voice_gr, allow_custom_value=True,)
-
- # Temperature, Repetition Penalty, Speed, pitch
- with gr.Row():
- tgwui_temperature_set_gr = gr.Slider(minimum=0.05, maximum=1, step=0.05, label="Temperature", value=params["tgwui"]["tgwui_temperature_set"], interactive=alltalk_settings["temperature_capable"])
- tgwui_repetitionpenalty_set_gr = gr.Slider(minimum=0.5, maximum=20, step=0.5, label="Repetition Penalty", value=params["tgwui"]["tgwui_repetitionpenalty_set"], interactive=alltalk_settings["repetitionpenalty_capable"])
- with gr.Row():
- tgwui_generationspeed_set_gr = gr.Slider(minimum=0.30, maximum=2.00, step=0.10, label="TTS Speed", value=params["tgwui"]["tgwui_generationspeed_set"], interactive=alltalk_settings["generationspeed_capable"])
- tgwui_pitch_set_gr = gr.Slider(minimum=-10, maximum=10, step=1, label="Pitch", value=params["tgwui"]["tgwui_pitch_set"], interactive=alltalk_settings["pitch_capable"])
-
- # Preview speech
- with gr.Row():
- tgwui_preview_text_gr = gr.Text(show_label=False, placeholder="Preview text", elem_id="silero_preview_text", scale=2,)
- tgwui_preview_play_gr = gr.Button("Generate Preview", scale=1)
- tgwui_preview_audio_gr = gr.HTML(visible=False)
-
- with gr.Row():
- tgwui_protocol_gr = gr.Dropdown(choices=["http://", "https://"], label="AllTalk Server Protocol", value=alltalk_protocol)
- tgwui_protocol_gr.change(tgwui_update_alltalk_protocol, tgwui_protocol_gr, None)
- tgwui_ip_address_port_gr = gr.Textbox(label="AllTalk Server IP:Port", value=alltalk_ip_port)
- tgwui_ip_address_port_gr.change(tgwui_update_alltalk_ip_port, tgwui_ip_address_port_gr, None)
- tgwui_refresh_settings_gr = gr.Button("Refresh settings & voices")
- tgwui_refresh_settings_gr.click(tgwui_update_dropdowns, None, [tgwui_lowvram_enabled_gr, tgwui_deepspeed_enabled_gr, tgwui_character_voice_gr, tgwui_rvc_char_voice_gr, tgwui_narrator_voice_gr, tgwui_rvc_narr_voice_gr, tgwui_tts_dropdown_gr, tgwui_temperature_set_gr, tgwui_repetitionpenalty_set_gr, tgwui_language_gr, tgwui_generationspeed_set_gr, tgwui_pitch_set_gr, tgwui_non_quoted_text_is_gr])
-
- with gr.Row():
- tgwui_convert_gr = gr.Button("Remove old TTS audio and leave only message texts")
- tgwui_convert_cancel_gr = gr.Button("Cancel", visible=False)
- tgwui_convert_confirm_gr = gr.Button("Confirm (cannot be undone)", variant="stop", visible=False)
- tgwui_stop_generation_gr = gr.Button("Stop current TTS generation")
- tgwui_stop_generation_gr.click(stop_generate_tts, None, None,)
-
- # Convert history with confirmation
- convert_arr = [tgwui_convert_confirm_gr, tgwui_convert_gr, tgwui_convert_cancel_gr]
- tgwui_convert_gr.click(lambda: [gr.update(visible=True), gr.update(visible=False), gr.update(visible=True),], None, convert_arr,)
- tgwui_convert_confirm_gr.click(lambda: [ gr.update(visible=False), gr.update(visible=True), gr.update(visible=False),], None, convert_arr,
- ).then(remove_tts_from_history, gradio("history"), gradio("history")
- ).then(chat.save_history, gradio("history", "unique_id", "character_menu", "mode"), None,
- ).then(chat.redraw_html, gradio(ui_chat.reload_arr), gradio("display"))
- tgwui_convert_cancel_gr.click(lambda: [gr.update(visible=False), gr.update(visible=True), gr.update(visible=False),], None, convert_arr,)
-
- # Toggle message text in history
- tgwui_show_text_gr.change(lambda x: params["tgwui"].update({"tgwui_show_text": x}), tgwui_show_text_gr, None
- ).then(toggle_text_in_history, gradio("history"), gradio("history")
- ).then(chat.save_history, gradio("history", "unique_id", "character_menu", "mode"), None,
- ).then(chat.redraw_html, gradio(ui_chat.reload_arr), gradio("display"))
-
- # Event functions to update the parameters in the backend
- tgwui_activate_tts_gr.change(lambda x: params["tgwui"].update({"tgwui_activate_tts": x}), tgwui_activate_tts_gr, None)
- tgwui_autoplay_gr.change(lambda x: params["tgwui"].update({"tgwui_autoplay_tts": x}), tgwui_autoplay_gr, None)
- tgwui_lowvram_enabled_gr.change(lambda x: params["tgwui"].update({"tgwui_lowvram_enabled": x}), tgwui_lowvram_enabled_gr, None)
- tgwui_lowvram_enabled_gr.change(lambda x: send_lowvram_request(x), tgwui_lowvram_enabled_gr, tgwui_lowvram_enabled_play_gr, None)
-
- # Trigger the send_reload_request function when the dropdown value changes
- tgwui_tts_dropdown_gr.change(tgwui_handle_ttsmodel_dropdown_change, tgwui_tts_dropdown_gr, None)
-
- tgwui_deepspeed_enabled_gr.change(lambda x: params["tgwui"].update({"tgwui_deepspeed_enabled": x}), tgwui_deepspeed_enabled_gr, None)
- tgwui_deepspeed_enabled_gr.change(send_deepspeed_request, tgwui_deepspeed_enabled_gr, tgwui_deepspeed_enabled_play_gr, None)
- tgwui_character_voice_gr.change(lambda x: params["tgwui"].update({"tgwui_character_voice": x}), tgwui_character_voice_gr, None)
- tgwui_language_gr.change(lambda x: params["tgwui"].update({"tgwui_language": x}), tgwui_language_gr, None)
-
- # TSS Settings
- tgwui_temperature_set_gr.change(lambda x: params["tgwui"].update({"tgwui_temperature_set": x}), tgwui_temperature_set_gr, None)
- tgwui_repetitionpenalty_set_gr.change(lambda x: params["tgwui"].update({"tgwui_repetitionpenalty_set": x}), tgwui_repetitionpenalty_set_gr, None)
- tgwui_generationspeed_set_gr.change(lambda x: params["tgwui"].update({"tgwui_generationspeed_set": x}), tgwui_generationspeed_set_gr, None)
- tgwui_pitch_set_gr.change(lambda x: params["tgwui"].update({"tgwui_pitch_set": x}), tgwui_pitch_set_gr, None)
-
- # Narrator selection actions
- tgwui_narrator_enabled_gr.change(lambda x: params["tgwui"].update({"tgwui_narrator_enabled": x}), tgwui_narrator_enabled_gr, None)
- tgwui_non_quoted_text_is_gr.change(lambda x: params["tgwui"].update({"tgwui_non_quoted_text_is": x}), tgwui_non_quoted_text_is_gr, None)
- tgwui_narrator_voice_gr.change(lambda x: params["tgwui"].update({"tgwui_narrator_voice": x}), tgwui_narrator_voice_gr, None)
-
- # RVC selection actions
- tgwui_rvc_char_voice_gr.change(lambda x: params["tgwui"].update({"tgwui_rvc_char_voice": x}), tgwui_rvc_char_voice_gr, None)
- tgwui_rvc_narr_voice_gr.change(lambda x: params["tgwui"].update({"tgwui_rvc_narr_voice": x}), tgwui_rvc_narr_voice_gr, None)
-
- # Play preview
- tgwui_preview_text_gr.submit(voice_preview, tgwui_preview_text_gr, tgwui_preview_audio_gr)
- tgwui_preview_play_gr.click(voice_preview, tgwui_preview_text_gr, tgwui_preview_audio_gr)
-
diff --git a/system/TGWUI Extension/AT TGWUI Remote Instructions.txt b/system/TGWUI_Extension/AT TGWUI Remote Instructions.txt
similarity index 99%
rename from system/TGWUI Extension/AT TGWUI Remote Instructions.txt
rename to system/TGWUI_Extension/AT TGWUI Remote Instructions.txt
index 3e98301..ef94ced 100644
--- a/system/TGWUI Extension/AT TGWUI Remote Instructions.txt
+++ b/system/TGWUI_Extension/AT TGWUI Remote Instructions.txt
@@ -1,17 +1,17 @@
-Text-generation-webui remote extension for AllTalk!
-
-This folder contains everything you need to set up a Text-generation-webui extension that can communicate with an AllTalk server remotely.
-
-To get started, follow these simple steps:
-
-1) In your Text-generation-webui's extensions folder, create a new folder and name it "alltalk_tts".
-2) Take all the files from this current folder and copy them into the newly created "alltalk_tts" folder.
-3) Start & Open Text-generation-webui and look for "alltalk_tts" in the list of extensions. Select alltalk_tts, save your settings and restart Text-generation-webui.
-4) Text-generation-webui will restart and load the AllTalk extension. You may see an error/warning as the extension will not yet have the IP address details of your remote AllTalk server.
-5) In the extension settings, enter the correct IP address and port of your AllTalk server. If your AllTalk server is running on a remote machine accessed through a secure tunnel, make sure to use "https" instead of "http".
-6) After entering the server details, click on the "Refresh settings & voices" button. If the settings are correct, the extension will download all the available settings from the AllTalk server.
-7) The first time you successfully generate speech using the extension, the settings will be automatically saved to a local configuration file.
-
-Please note that if the AllTalk server you're connected to doesn't support certain settings/features, those options will be greyed out and unavailable in the extension.
-
+Text-generation-webui remote extension for AllTalk!
+
+This folder contains everything you need to set up a Text-generation-webui extension that can communicate with an AllTalk server remotely.
+
+To get started, follow these simple steps:
+
+1) In your Text-generation-webui's extensions folder, create a new folder and name it "alltalk_tts".
+2) Take all the files from this current folder and copy them into the newly created "alltalk_tts" folder.
+3) Start & Open Text-generation-webui and look for "alltalk_tts" in the list of extensions. Select alltalk_tts, save your settings and restart Text-generation-webui.
+4) Text-generation-webui will restart and load the AllTalk extension. You may see an error/warning as the extension will not yet have the IP address details of your remote AllTalk server.
+5) In the extension settings, enter the correct IP address and port of your AllTalk server. If your AllTalk server is running on a remote machine accessed through a secure tunnel, make sure to use "https" instead of "http".
+6) After entering the server details, click on the "Refresh settings & voices" button. If the settings are correct, the extension will download all the available settings from the AllTalk server.
+7) The first time you successfully generate speech using the extension, the settings will be automatically saved to a local configuration file.
+
+Please note that if the AllTalk server you're connected to doesn't support certain settings/features, those options will be greyed out and unavailable in the extension.
+
Please use the "Refresh settings & voices" button to make sure the extension is in sync with your AllTalk server.
\ No newline at end of file
diff --git a/system/TGWUI Extension/deepspeeddisabled.wav b/system/TGWUI_Extension/deepspeeddisabled.wav
similarity index 100%
rename from system/TGWUI Extension/deepspeeddisabled.wav
rename to system/TGWUI_Extension/deepspeeddisabled.wav
diff --git a/system/TGWUI Extension/deepspeedenabled.wav b/system/TGWUI_Extension/deepspeedenabled.wav
similarity index 100%
rename from system/TGWUI Extension/deepspeedenabled.wav
rename to system/TGWUI_Extension/deepspeedenabled.wav
diff --git a/system/TGWUI Extension/harvard_sentences.txt b/system/TGWUI_Extension/harvard_sentences.txt
similarity index 97%
rename from system/TGWUI Extension/harvard_sentences.txt
rename to system/TGWUI_Extension/harvard_sentences.txt
index 958d7f3..1947d9c 100644
--- a/system/TGWUI Extension/harvard_sentences.txt
+++ b/system/TGWUI_Extension/harvard_sentences.txt
@@ -1,720 +1,720 @@
-The birch canoe slid on the smooth planks.
-Glue the sheet to the dark blue background.
-It's easy to tell the depth of a well.
-These days a chicken leg is a rare dish.
-Rice is often served in round bowls.
-The juice of lemons makes fine punch.
-The box was thrown beside the parked truck.
-The hogs were fed chopped corn and garbage.
-Four hours of steady work faced us.
-A large size in stockings is hard to sell.
-The boy was there when the sun rose.
-A rod is used to catch pink salmon.
-The source of the huge river is the clear spring.
-Kick the ball straight and follow through.
-Help the woman get back to her feet.
-A pot of tea helps to pass the evening.
-Smoky fires lack flame and heat.
-The soft cushion broke the man's fall.
-The salt breeze came across from the sea.
-The girl at the booth sold fifty bonds.
-The small pup gnawed a hole in the sock.
-The fish twisted and turned on the bent hook.
-Press the pants and sew a button on the vest.
-The swan dive was far short of perfect.
-The beauty of the view stunned the young boy.
-Two blue fish swam in the tank.
-Her purse was full of useless trash.
-The colt reared and threw the tall rider.
-It snowed, rained, and hailed the same morning.
-Read verse out loud for pleasure.
-Hoist the load to your left shoulder.
-Take the winding path to reach the lake.
-Note closely the size of the gas tank.
-Wipe the grease off his dirty face.
-Mend the coat before you go out.
-The wrist was badly strained and hung limp.
-The stray cat gave birth to kittens.
-The young girl gave no clear response.
-The meal was cooked before the bell rang.
-What joy there is in living.
-A king ruled the state in the early days.
-The ship was torn apart on the sharp reef.
-Sickness kept him home the third week.
-The wide road shimmered in the hot sun.
-The lazy cow lay in the cool grass.
-Lift the square stone over the fence.
-The rope will bind the seven books at once.
-Hop over the fence and plunge in.
-The friendly gang left the drug store.
-Mesh wire keeps chicks inside.
-The frosty air passed through the coat.
-The crooked maze failed to fool the mouse.
-Adding fast leads to wrong sums.
-The show was a flop from the very start.
-A saw is a tool used for making boards.
-The wagon moved on well oiled wheels.
-March the soldiers past the next hill.
-A cup of sugar makes sweet fudge.
-Place a rosebush near the porch steps.
-Both lost their lives in the raging storm.
-We talked of the side show in the circus.
-Use a pencil to write the first draft.
-He ran half way to the hardware store.
-The clock struck to mark the third period.
-A small creek cut across the field.
-Cars and busses stalled in snow drifts.
-The set of china hit the floor with a crash.
-This is a grand season for hikes on the road.
-The dune rose from the edge of the water.
-Those words were the cue for the actor to leave.
-A yacht slid around the point into the bay.
-The two met while playing on the sand.
-The ink stain dried on the finished page.
-The walled town was seized without a fight.
-The lease ran out in sixteen weeks.
-A tame squirrel makes a nice pet.
-The horn of the car woke the sleeping cop.
-The heart beat strongly and with firm strokes.
-The pearl was worn in a thin silver ring.
-The fruit peel was cut in thick slices.
-The Navy attacked the big task force.
-See the cat glaring at the scared mouse.
-There are more than two factors here.
-The hat brim was wide and too droopy.
-The lawyer tried to lose his case.
-The grass curled around the fence post.
-Cut the pie into large parts.
-Men strive but seldom get rich.
-Always close the barn door tight.
-He lay prone and hardly moved a limb.
-The slush lay deep along the street.
-A wisp of cloud hung in the blue air.
-A pound of sugar costs more than eggs.
-The fin was sharp and cut the clear water.
-The play seems dull and quite stupid.
-Bail the boat to stop it from sinking.
-The term ended in late June that year.
-A tusk is used to make costly gifts.
-Ten pins were set in order.
-The bill was paid every third week.
-Oak is strong and also gives shade.
-Cats and dogs each hate the other.
-The pipe began to rust while new.
-Open the crate but don't break the glass.
-Add the sum to the product of these three.
-Thieves who rob friends deserve jail.
-The ripe taste of cheese improves with age.
-Act on these orders with great speed.
-The hog crawled under the high fence.
-Move the vat over the hot fire.
-The bark of the pine tree was shiny and dark.
-Leaves turn brown and yellow in the fall.
-The pennant waved when the wind blew.
-Split the log with a quick, sharp blow.
-Burn peat after the logs give out.
-He ordered peach pie with ice cream.
-Weave the carpet on the right hand side.
-Hemp is a weed found in parts of the tropics.
-A lame back kept his score low.
-We find joy in the simplest things.
-Type out three lists of orders.
-The harder he tried the less he got done.
-The boss ran the show with a watchful eye.
-The cup cracked and spilled its contents.
-Paste can cleanse the most dirty brass.
-The slang word for raw whiskey is booze.
-It caught its hind paw in a rusty trap.
-The wharf could be seen at the farther shore.
-Feel the heat of the weak dying flame.
-The tiny girl took off her hat.
-A cramp is no small danger on a swim.
-He said the same phrase thirty times.
-Pluck the bright rose without leaves.
-Two plus seven is less than ten.
-The glow deepened in the eyes of the sweet girl.
-Bring your problems to the wise chief.
-Write a fond note to the friend you cherish.
-Clothes and lodging are free to new men.
-We frown when events take a bad turn.
-Port is a strong wine with a smoky taste.
-The young kid jumped the rusty gate.
-Guess the results from the first scores.
-A salt pickle tastes fine with ham.
-The just claim got the right verdict.
-These thistles bend in a high wind.
-Pure bred poodles have curls.
-The tree top waved in a graceful way.
-The spot on the blotter was made by green ink.
-Mud was spattered on the front of his white shirt.
-The cigar burned a hole in the desk top.
-The empty flask stood on the tin tray.
-A speedy man can beat this track mark.
-He broke a new shoelace that day.
-The coffee stand is too high for the couch.
-The urge to write short stories is rare.
-The pencils have all been used.
-The pirates seized the crew of the lost ship.
-We tried to replace the coin but failed.
-She sewed the torn coat quite neatly.
-The sofa cushion is red and of light weight.
-The jacket hung on the back of the wide chair.
-At that high level the air is pure.
-Drop the two when you add the figures.
-A filing case is now hard to buy.
-An abrupt start does not win the prize.
-Wood is best for making toys and blocks.
-The office paint was a dull, sad tan.
-He knew the skill of the great young actress.
-A rag will soak up spilled water.
-A shower of dirt fell from the hot pipes.
-Steam hissed from the broken valve.
-The child almost hurt the small dog.
-There was a sound of dry leaves outside.
-The sky that morning was clear and bright blue.
-Torn scraps littered the stone floor.
-Sunday is the best part of the week.
-The doctor cured him with these pills.
-The new girl was fired today at noon.
-They felt gay when the ship arrived in port.
-Add the store's account to the last cent.
-Acid burns holes in wool cloth.
-Fairy tales should be fun to write.
-Eight miles of woodland burned to waste.
-The third act was dull and tired the players.
-A young child should not suffer fright.
-Add the column and put the sum here.
-We admire and love a good cook.
-There the flood mark is ten inches.
-He carved a head from the round block of marble.
-She has a smart way of wearing clothes.
-The fruit of a fig tree is apple-shaped.
-Corn cobs can be used to kindle a fire.
-Where were they when the noise started.
-The paper box is full of thumb tacks.
-Sell your gift to a buyer at a good gain.
-The tongs lay beside the ice pail.
-The petals fall with the next puff of wind.
-Bring your best compass to the third class.
-They could laugh although they were sad.
-Farmers came in to thresh the oat crop.
-The brown house was on fire to the attic.
-The lure is used to catch trout and flounder.
-Float the soap on top of the bath water.
-A blue crane is a tall wading bird.
-A fresh start will work such wonders.
-The club rented the rink for the fifth night.
-After the dance, they went straight home.
-The hostess taught the new maid to serve.
-He wrote his last novel there at the inn.
-Even the worst will beat his low score.
-The cement had dried when he moved it.
-The loss of the second ship was hard to take.
-The fly made its way along the wall.
-Do that with a wooden stick.
-Live wires should be kept covered.
-The large house had hot water taps.
-It is hard to erase blue or red ink.
-Write at once or you may forget it.
-The doorknob was made of bright clean brass.
-The wreck occurred by the bank on Main Street.
-A pencil with black lead writes best.
-Coax a young calf to drink from a bucket.
-Schools for ladies teach charm and grace.
-The lamp shone with a steady green flame.
-They took the axe and the saw to the forest.
-The ancient coin was quite dull and worn.
-The shaky barn fell with a loud crash.
-Jazz and swing fans like fast music.
-Rake the rubbish up and then burn it.
-Slash the gold cloth into fine ribbons.
-Try to have the court decide the case.
-They are pushed back each time they attack.
-He broke his ties with groups of former friends.
-They floated on the raft to sun their white backs.
-The map had an X that meant nothing.
-Whitings are small fish caught in nets.
-Some ads serve to cheat buyers.
-Jerk the rope and the bell rings weakly.
-A waxed floor makes us lose balance.
-Madam, this is the best brand of corn.
-On the islands the sea breeze is soft and mild.
-The play began as soon as we sat down.
-This will lead the world to more sound and fury.
-Add salt before you fry the egg.
-The rush for funds reached its peak Tuesday.
-The birch looked stark white and lonesome.
-The box is held by a bright red snapper.
-To make pure ice, you freeze water.
-The first worm gets snapped early.
-Jump the fence and hurry up the bank.
-Yell and clap as the curtain slides back.
-They are men who walk the middle of the road.
-Both brothers wear the same size.
-In some form or other we need fun.
-The prince ordered his head chopped off.
-The houses are built of red clay bricks.
-Ducks fly north but lack a compass.
-Fruit flavors are used in fizz drinks.
-These pills do less good than others.
-Canned pears lack full flavor.
-The dark pot hung in the front closet.
-Carry the pail to the wall and spill it there.
-The train brought our hero to the big town.
-We are sure that one war is enough.
-Gray paint stretched for miles around.
-The rude laugh filled the empty room.
-High seats are best for football fans.
-Tea served from the brown jug is tasty.
-A dash of pepper spoils beef stew.
-A zestful food is the hot-cross bun.
-The horse trotted around the field at a brisk pace.
-Find the twin who stole the pearl necklace.
-Cut the cord that binds the box tightly.
-The red tape bound the smuggled food.
-Look in the corner to find the tan shirt.
-The cold drizzle will halt the bond drive.
-Nine men were hired to dig the ruins.
-The junk yard had a mouldy smell.
-The flint sputtered and lit a pine torch.
-Soak the cloth and drown the sharp odor.
-The shelves were bare of both jam or crackers.
-A joy to every child is the swan boat.
-All sat frozen and watched the screen.
-A cloud of dust stung his tender eyes.
-To reach the end he needs much courage.
-Shape the clay gently into block form.
-A ridge on a smooth surface is a bump or flaw.
-Hedge apples may stain your hands green.
-Quench your thirst, then eat the crackers.
-Tight curls get limp on rainy days.
-The mute muffled the high tones of the horn.
-The gold ring fits only a pierced ear.
-The old pan was covered with hard fudge.
-Watch the log float in the wide river.
-The node on the stalk of wheat grew daily.
-The heap of fallen leaves was set on fire.
-Write fast if you want to finish early.
-His shirt was clean but one button was gone.
-The barrel of beer was a brew of malt and hops.
-Tin cans are absent from store shelves.
-Slide the box into that empty space.
-The plant grew large and green in the window.
-The beam dropped down on the workmen's head.
-Pink clouds floated with the breeze.
-She danced like a swan, tall and graceful.
-The tube was blown and the tire flat and useless.
-It is late morning on the old wall clock.
-Let's all join as we sing the last chorus.
-The last switch cannot be turned off.
-The fight will end in just six minutes.
-The store walls were lined with colored frocks.
-The peace league met to discuss their plans.
-The rise to fame of a person takes luck.
-Paper is scarce, so write with much care.
-The quick fox jumped on the sleeping cat.
-The nozzle of the fire hose was bright brass.
-Screw the round cap on as tight as needed.
-Time brings us many changes.
-The purple tie was ten years old.
-Men think and plan and sometimes act.
-Fill the ink jar with sticky glue.
-He smoke a big pipe with strong contents.
-We need grain to keep our mules healthy.
-Pack the records in a neat thin case.
-The crunch of feet in the snow was the only sound.
-The copper bowl shone in the sun's rays.
-Boards will warp unless kept dry.
-The plush chair leaned against the wall.
-Glass will clink when struck by metal.
-Bathe and relax in the cool green grass.
-Nine rows of soldiers stood in line.
-The beach is dry and shallow at low tide.
-The idea is to sew both edges straight.
-The kitten chased the dog down the street.
-Pages bound in cloth make a book.
-Try to trace the fine lines of the painting.
-Women form less than half of the group.
-The zones merge in the central part of town.
-A gem in the rough needs work to polish.
-Code is used when secrets are sent.
-Most of the news is easy for us to hear.
-He used the lathe to make brass objects.
-The vane on top of the pole revolved in the wind.
-Mince pie is a dish served to children.
-The clan gathered on each dull night.
-Let it burn, it gives us warmth and comfort.
-A castle built from sand fails to endure.
-A child's wit saved the day for us.
-Tack the strip of carpet to the worn floor.
-Next Tuesday we must vote.
-Pour the stew from the pot into the plate.
-Each penny shone like new.
-The man went to the woods to gather sticks.
-The dirt piles were lines along the road.
-The logs fell and tumbled into the clear stream.
-Just hoist it up and take it away.
-A ripe plum is fit for a king's palate.
-Our plans right now are hazy.
-Brass rings are sold by these natives.
-It takes a good trap to capture a bear.
-Feed the white mouse some flower seeds.
-The thaw came early and freed the stream.
-He took the lead and kept it the whole distance.
-The key you designed will fit the lock.
-Plead to the council to free the poor thief.
-Better hash is made of rare beef.
-This plank was made for walking on.
-The lake sparkled in the red hot sun.
-He crawled with care along the ledge.
-Tend the sheep while the dog wanders.
-It takes a lot of help to finish these.
-Mark the spot with a sign painted red.
-Take two shares as a fair profit.
-The fur of cats goes by many names.
-North winds bring colds and fevers.
-He asks no person to vouch for him.
-Go now and come here later.
-A sash of gold silk will trim her dress.
-Soap can wash most dirt away.
-That move means the game is over.
-He wrote down a long list of items.
-A siege will crack the strong defense.
-Grape juice and water mix well.
-Roads are paved with sticky tar.
-Fake stones shine but cost little.
-The drip of the rain made a pleasant sound.
-Smoke poured out of every crack.
-Serve the hot rum to the tired heroes.
-Much of the story makes good sense.
-The sun came up to light the eastern sky.
-Heave the line over the port side.
-A lathe cuts and trims any wood.
-It's a dense crowd in two distinct ways.
-His hip struck the knee of the next player.
-The stale smell of old beer lingers.
-The desk was firm on the shaky floor.
-It takes heat to bring out the odor.
-Beef is scarcer than some lamb.
-Raise the sail and steer the ship northward.
-A cone costs five cents on Mondays.
-A pod is what peas always grow in.
-Jerk the dart from the cork target.
-No cement will hold hard wood.
-We now have a new base for shipping.
-A list of names is carved around the base.
-The sheep were led home by a dog.
-Three for a dime, the young peddler cried.
-The sense of smell is better than that of touch.
-No hardship seemed to keep him sad.
-Grace makes up for lack of beauty.
-Nudge gently but wake her now.
-The news struck doubt into restless minds.
-Once we stood beside the shore.
-A chink in the wall allowed a draft to blow.
-Fasten two pins on each side.
-A cold dip restores health and zest.
-He takes the oath of office each March.
-The sand drifts over the sill of the old house.
-The point of the steel pen was bent and twisted.
-There is a lag between thought and act.
-Seed is needed to plant the spring corn.
-Draw the chart with heavy black lines.
-The boy owed his pal thirty cents.
-The chap slipped into the crowd and was lost.
-Hats are worn to tea and not to dinner.
-The ramp led up to the wide highway.
-Beat the dust from the rug onto the lawn.
-Say it slowly but make it ring clear.
-The straw nest housed five robins.
-Screen the porch with woven straw mats.
-This horse will nose his way to the finish.
-The dry wax protects the deep scratch.
-He picked up the dice for a second roll.
-These coins will be needed to pay his debt.
-The nag pulled the frail cart along.
-Twist the valve and release hot steam.
-The vamp of the shoe had a gold buckle.
-The smell of burned rags itches my nose.
-New pants lack cuffs and pockets.
-The marsh will freeze when cold enough.
-They slice the sausage thin with a knife.
-The bloom of the rose lasts a few days.
-A gray mare walked before the colt.
-Breakfast buns are fine with a hot drink.
-Bottles hold four kinds of rum.
-The man wore a feather in his felt hat.
-He wheeled the bike past the winding road.
-Drop the ashes on the worn old rug.
-The desk and both chairs were painted tan.
-Throw out the used paper cup and plate.
-A clean neck means a neat collar.
-The couch cover and hall drapes were blue.
-The stems of the tall glasses cracked and broke.
-The wall phone rang loud and often.
-The clothes dried on a thin wooden rack.
-Turn on the lantern which gives us light.
-The cleat sank deeply into the soft turf.
-The bills were mailed promptly on the tenth of the month.
-To have is better than to wait and hope.
-The price is fair for a good antique clock.
-The music played on while they talked.
-Dispense with a vest on a day like this.
-The bunch of grapes was pressed into wine.
-He sent the figs, but kept the ripe cherries.
-The hinge on the door creaked with old age.
-The screen before the fire kept in the sparks.
-Fly by night, and you waste little time.
-Thick glasses helped him read the print.
-Birth and death mark the limits of life.
-The chair looked strong but had no bottom.
-The kite flew wildly in the high wind.
-A fur muff is stylish once more.
-The tin box held priceless stones.
-We need an end of all such matter.
-The case was puzzling to the old and wise.
-The bright lanterns were gay on the dark lawn.
-We don't get much money but we have fun.
-The youth drove with zest, but little skill.
-Five years he lived with a shaggy dog.
-A fence cuts through the corner lot.
-The way to save money is not to spend much.
-Shut the hatch before the waves push it in.
-The odor of spring makes young hearts jump.
-Crack the walnut with your sharp side teeth.
-He offered proof in the form of a large chart.
-Send the stuff in a thick paper bag.
-A quart of milk is water for the most part.
-They told wild tales to frighten him.
-The three story house was built of stone.
-In the rear of the ground floor was a large passage.
-A man in a blue sweater sat at the desk.
-Oats are a food eaten by horse and man.
-Their eyelids droop for want of sleep.
-A sip of tea revives his tired friend.
-There are many ways to do these things.
-Tuck the sheet under the edge of the mat.
-A force equal to that would move the earth.
-We like to see clear weather.
-The work of the tailor is seen on each side.
-Take a chance and win a china doll.
-Shake the dust from your shoes, stranger.
-She was kind to sick old people.
-The square wooden crate was packed to be shipped.
-The dusty bench stood by the stone wall.
-We dress to suit the weather of most days.
-Smile when you say nasty words.
-A bowl of rice is free with chicken stew.
-The water in this well is a source of good health.
-Take shelter in this tent, but keep still.
-That guy is the writer of a few banned books.
-The little tales they tell are false.
-The door was barred, locked, and bolted as well.
-Ripe pears are fit for a queen's table.
-A big wet stain was on the round carpet.
-The kite dipped and swayed, but stayed aloft.
-The pleasant hours fly by much too soon.
-The room was crowded with a wild mob.
-This strong arm shall shield your honor.
-She blushed when he gave her a white orchid.
-The beetle droned in the hot June sun.
-Press the pedal with your left foot.
-Neat plans fail without luck.
-The black trunk fell from the landing.
-The bank pressed for payment of the debt.
-The theft of the pearl pin was kept secret.
-Shake hands with this friendly child.
-The vast space stretched into the far distance.
-A rich farm is rare in this sandy waste.
-His wide grin earned many friends.
-Flax makes a fine brand of paper.
-Hurdle the pit with the aid of a long pole.
-A strong bid may scare your partner stiff.
-Even a just cause needs power to win.
-Peep under the tent and see the clowns.
-The leaf drifts along with a slow spin.
-Cheap clothes are flashy but don't last.
-A thing of small note can cause despair.
-Flood the mails with requests for this book.
-A thick coat of black paint covered all.
-The pencil was cut to be sharp at both ends.
-Those last words were a strong statement.
-He wrote his name boldly at the top of the sheet.
-Dill pickles are sour but taste fine.
-Down that road is the way to the grain farmer.
-Either mud or dust are found at all times.
-The best method is to fix it in place with clips.
-If you mumble your speech will be lost.
-At night the alarm roused him from a deep sleep.
-Read just what the meter says.
-Fill your pack with bright trinkets for the poor.
-The small red neon lamp went out.
-Clams are small, round, soft, and tasty.
-The fan whirled its round blades softly.
-The line where the edges join was clean.
-Breathe deep and smell the piny air.
-It matters not if he reads these words or those.
-A brown leather bag hung from its strap.
-A toad and a frog are hard to tell apart.
-A white silk jacket goes with any shoes.
-A break in the dam almost caused a flood.
-Paint the sockets in the wall dull green.
-The child crawled into the dense grass.
-Bribes fail where honest men work.
-Trample the spark, else the flames will spread.
-The hilt of the sword was carved with fine designs.
-A round hole was drilled through the thin board.
-Footprints showed the path he took up the beach.
-She was waiting at my front lawn.
-A vent near the edge brought in fresh air.
-Prod the old mule with a crooked stick.
-It is a band of steel three inches wide.
-The pipe ran almost the length of the ditch.
-It was hidden from sight by a mass of leaves and shrubs.
-The weight of the package was seen on the high scale.
-Wake and rise, and step into the green outdoors.
-The green light in the brown box flickered.
-The brass tube circled the high wall.
-The lobes of her ears were pierced to hold rings.
-Hold the hammer near the end to drive the nail.
-Next Sunday is the twelfth of the month.
-Every word and phrase he speaks is true.
-He put his last cartridge into the gun and fired.
-They took their kids from the public school.
-Drive the screw straight into the wood.
-Keep the hatch tight and the watch constant.
-Sever the twine with a quick snip of the knife.
-Paper will dry out when wet.
-Slide the catch back and open the desk.
-Help the weak to preserve their strength.
-A sullen smile gets few friends.
-Stop whistling and watch the boys march.
-Jerk the cord, and out tumbles the gold.
-Slide the tray across the glass top.
-The cloud moved in a stately way and was gone.
-Light maple makes for a swell room.
-Set the piece here and say nothing.
-Dull stories make her laugh.
-A stiff cord will do to fasten your shoe.
-Get the trust fund to the bank early.
-Choose between the high road and the low.
-A plea for funds seems to come again.
-He lent his coat to the tall gaunt stranger.
-There is a strong chance it will happen once more.
-The duke left the park in a silver coach.
-Greet the new guests and leave quickly.
-When the frost has come it is time for turkey.
-Sweet words work better than fierce.
-A thin stripe runs down the middle.
-A six comes up more often than a ten.
-Lush fern grow on the lofty rocks.
-The ram scared the school children off.
-The team with the best timing looks good.
-The farmer swapped his horse for a brown ox.
-Sit on the perch and tell the others what to do.
-A steep trail is painful for our feet.
-The early phase of life moves fast.
-Green moss grows on the northern side.
-Tea in thin china has a sweet taste.
-Pitch the straw through the door of the stable.
-The latch on the back gate needed a nail.
-The goose was brought straight from the old market.
-The sink is the thing in which we pile dishes.
-A whiff of it will cure the most stubborn cold.
-The facts don't always show who is right.
-She flaps her cape as she parades the street.
-The loss of the cruiser was a blow to the fleet.
-Loop the braid to the left and then over.
-Plead with the lawyer to drop the lost cause.
-Calves thrive on tender spring grass.
-Post no bills on this office wall.
-Tear a thin sheet from the yellow pad.
-A cruise in warm waters in a sleek yacht is fun.
-A streak of color ran down the left edge.
-It was done before the boy could see it.
-Crouch before you jump or miss the mark.
-Pack the kits and don't forget the salt.
-The square peg will settle in the round hole.
-Fine soap saves tender skin.
-Poached eggs and tea must suffice.
-Bad nerves are jangled by a door slam.
-Ship maps are different from those for planes.
-Dimes showered down from all sides.
-They sang the same tunes at each party.
-The sky in the west is tinged with orange red.
-The pods of peas ferment in bare fields.
-The horse balked and threw the tall rider.
-The hitch between the horse and cart broke.
-Pile the coal high in the shed corner.
-A gold vase is both rare and costly.
-The knife was hung inside its bright sheath.
-The rarest spice comes from the far East.
-The roof should be tilted at a sharp slant.
-A smatter of French is worse than none.
-The mule trod the treadmill day and night.
-The aim of the contest is to raise a great fund.
-To send it now in large amounts is bad.
-There is a fine hard tang in salty air.
-Cod is the main business of the north shore.
-The slab was hewn from heavy blocks of slate.
-Dunk the stale biscuits into strong drink.
-Hang tinsel from both branches.
-Cap the jar with a tight brass cover.
-The poor boy missed the boat again.
-Be sure to set the lamp firmly in the hole.
-Pick a card and slip it under the pack.
-A round mat will cover the dull spot.
-The first part of the plan needs changing.
-A good book informs of what we ought to know.
-The mail comes in three batches per day.
-You cannot brew tea in a cold pot.
-Dots of light betrayed the black cat.
-Put the chart on the mantel and tack it down.
-The night shift men rate extra pay.
-The red paper brightened the dim stage.
-See the player scoot to third base.
-Slide the bill between the two leaves.
-Many hands help get the job done.
-We don't like to admit our small faults.
-No doubt about the way the wind blows.
-Dig deep in the earth for pirate's gold.
-The steady drip is worse than a drenching rain.
-A flat pack takes less luggage space.
-Green ice frosted the punch bowl.
-A stuffed chair slipped from the moving van.
-The stitch will serve but needs to be shortened.
-A thin book fits in the side pocket.
-The gloss on top made it unfit to read.
-The hail pattered on the burnt brown grass.
-Seven seals were stamped on great sheets.
-Our troops are set to strike heavy blows.
-The store was jammed before the sale could start.
-It was a bad error on the part of the new judge.
-One step more and the board will collapse.
-Take the match and strike it against your shoe.
-The pot boiled, but the contents failed to jell.
-The baby puts his right foot in his mouth.
-The bombs left most of the town in ruins.
-Stop and stare at the hard working man.
-The streets are narrow and full of sharp turns.
-The pup jerked the leash as he saw a feline shape.
-Open your book to the first page.
-Fish evade the net and swim off.
-Dip the pail once and let it settle.
-Will you please answer that phone.
-The big red apple fell to the ground.
-The curtain rose and the show was on.
-The young prince became heir to the throne.
-He sent the boy on a short errand.
-Leave now and you will arrive on time.
-The corner store was robbed last night.
-A gold ring will please most any girl.
-The long journey home took a year.
-She saw a cat in the neighbor's house.
-A pink shell was found on the sandy beach.
-Small children came to see him.
-The grass and bushes were wet with dew.
-The blind man counted his old coins.
-A severe storm tore down the barn.
-She called his name many times.
+The birch canoe slid on the smooth planks.
+Glue the sheet to the dark blue background.
+It's easy to tell the depth of a well.
+These days a chicken leg is a rare dish.
+Rice is often served in round bowls.
+The juice of lemons makes fine punch.
+The box was thrown beside the parked truck.
+The hogs were fed chopped corn and garbage.
+Four hours of steady work faced us.
+A large size in stockings is hard to sell.
+The boy was there when the sun rose.
+A rod is used to catch pink salmon.
+The source of the huge river is the clear spring.
+Kick the ball straight and follow through.
+Help the woman get back to her feet.
+A pot of tea helps to pass the evening.
+Smoky fires lack flame and heat.
+The soft cushion broke the man's fall.
+The salt breeze came across from the sea.
+The girl at the booth sold fifty bonds.
+The small pup gnawed a hole in the sock.
+The fish twisted and turned on the bent hook.
+Press the pants and sew a button on the vest.
+The swan dive was far short of perfect.
+The beauty of the view stunned the young boy.
+Two blue fish swam in the tank.
+Her purse was full of useless trash.
+The colt reared and threw the tall rider.
+It snowed, rained, and hailed the same morning.
+Read verse out loud for pleasure.
+Hoist the load to your left shoulder.
+Take the winding path to reach the lake.
+Note closely the size of the gas tank.
+Wipe the grease off his dirty face.
+Mend the coat before you go out.
+The wrist was badly strained and hung limp.
+The stray cat gave birth to kittens.
+The young girl gave no clear response.
+The meal was cooked before the bell rang.
+What joy there is in living.
+A king ruled the state in the early days.
+The ship was torn apart on the sharp reef.
+Sickness kept him home the third week.
+The wide road shimmered in the hot sun.
+The lazy cow lay in the cool grass.
+Lift the square stone over the fence.
+The rope will bind the seven books at once.
+Hop over the fence and plunge in.
+The friendly gang left the drug store.
+Mesh wire keeps chicks inside.
+The frosty air passed through the coat.
+The crooked maze failed to fool the mouse.
+Adding fast leads to wrong sums.
+The show was a flop from the very start.
+A saw is a tool used for making boards.
+The wagon moved on well oiled wheels.
+March the soldiers past the next hill.
+A cup of sugar makes sweet fudge.
+Place a rosebush near the porch steps.
+Both lost their lives in the raging storm.
+We talked of the side show in the circus.
+Use a pencil to write the first draft.
+He ran half way to the hardware store.
+The clock struck to mark the third period.
+A small creek cut across the field.
+Cars and busses stalled in snow drifts.
+The set of china hit the floor with a crash.
+This is a grand season for hikes on the road.
+The dune rose from the edge of the water.
+Those words were the cue for the actor to leave.
+A yacht slid around the point into the bay.
+The two met while playing on the sand.
+The ink stain dried on the finished page.
+The walled town was seized without a fight.
+The lease ran out in sixteen weeks.
+A tame squirrel makes a nice pet.
+The horn of the car woke the sleeping cop.
+The heart beat strongly and with firm strokes.
+The pearl was worn in a thin silver ring.
+The fruit peel was cut in thick slices.
+The Navy attacked the big task force.
+See the cat glaring at the scared mouse.
+There are more than two factors here.
+The hat brim was wide and too droopy.
+The lawyer tried to lose his case.
+The grass curled around the fence post.
+Cut the pie into large parts.
+Men strive but seldom get rich.
+Always close the barn door tight.
+He lay prone and hardly moved a limb.
+The slush lay deep along the street.
+A wisp of cloud hung in the blue air.
+A pound of sugar costs more than eggs.
+The fin was sharp and cut the clear water.
+The play seems dull and quite stupid.
+Bail the boat to stop it from sinking.
+The term ended in late June that year.
+A tusk is used to make costly gifts.
+Ten pins were set in order.
+The bill was paid every third week.
+Oak is strong and also gives shade.
+Cats and dogs each hate the other.
+The pipe began to rust while new.
+Open the crate but don't break the glass.
+Add the sum to the product of these three.
+Thieves who rob friends deserve jail.
+The ripe taste of cheese improves with age.
+Act on these orders with great speed.
+The hog crawled under the high fence.
+Move the vat over the hot fire.
+The bark of the pine tree was shiny and dark.
+Leaves turn brown and yellow in the fall.
+The pennant waved when the wind blew.
+Split the log with a quick, sharp blow.
+Burn peat after the logs give out.
+He ordered peach pie with ice cream.
+Weave the carpet on the right hand side.
+Hemp is a weed found in parts of the tropics.
+A lame back kept his score low.
+We find joy in the simplest things.
+Type out three lists of orders.
+The harder he tried the less he got done.
+The boss ran the show with a watchful eye.
+The cup cracked and spilled its contents.
+Paste can cleanse the most dirty brass.
+The slang word for raw whiskey is booze.
+It caught its hind paw in a rusty trap.
+The wharf could be seen at the farther shore.
+Feel the heat of the weak dying flame.
+The tiny girl took off her hat.
+A cramp is no small danger on a swim.
+He said the same phrase thirty times.
+Pluck the bright rose without leaves.
+Two plus seven is less than ten.
+The glow deepened in the eyes of the sweet girl.
+Bring your problems to the wise chief.
+Write a fond note to the friend you cherish.
+Clothes and lodging are free to new men.
+We frown when events take a bad turn.
+Port is a strong wine with a smoky taste.
+The young kid jumped the rusty gate.
+Guess the results from the first scores.
+A salt pickle tastes fine with ham.
+The just claim got the right verdict.
+These thistles bend in a high wind.
+Pure bred poodles have curls.
+The tree top waved in a graceful way.
+The spot on the blotter was made by green ink.
+Mud was spattered on the front of his white shirt.
+The cigar burned a hole in the desk top.
+The empty flask stood on the tin tray.
+A speedy man can beat this track mark.
+He broke a new shoelace that day.
+The coffee stand is too high for the couch.
+The urge to write short stories is rare.
+The pencils have all been used.
+The pirates seized the crew of the lost ship.
+We tried to replace the coin but failed.
+She sewed the torn coat quite neatly.
+The sofa cushion is red and of light weight.
+The jacket hung on the back of the wide chair.
+At that high level the air is pure.
+Drop the two when you add the figures.
+A filing case is now hard to buy.
+An abrupt start does not win the prize.
+Wood is best for making toys and blocks.
+The office paint was a dull, sad tan.
+He knew the skill of the great young actress.
+A rag will soak up spilled water.
+A shower of dirt fell from the hot pipes.
+Steam hissed from the broken valve.
+The child almost hurt the small dog.
+There was a sound of dry leaves outside.
+The sky that morning was clear and bright blue.
+Torn scraps littered the stone floor.
+Sunday is the best part of the week.
+The doctor cured him with these pills.
+The new girl was fired today at noon.
+They felt gay when the ship arrived in port.
+Add the store's account to the last cent.
+Acid burns holes in wool cloth.
+Fairy tales should be fun to write.
+Eight miles of woodland burned to waste.
+The third act was dull and tired the players.
+A young child should not suffer fright.
+Add the column and put the sum here.
+We admire and love a good cook.
+There the flood mark is ten inches.
+He carved a head from the round block of marble.
+She has a smart way of wearing clothes.
+The fruit of a fig tree is apple-shaped.
+Corn cobs can be used to kindle a fire.
+Where were they when the noise started.
+The paper box is full of thumb tacks.
+Sell your gift to a buyer at a good gain.
+The tongs lay beside the ice pail.
+The petals fall with the next puff of wind.
+Bring your best compass to the third class.
+They could laugh although they were sad.
+Farmers came in to thresh the oat crop.
+The brown house was on fire to the attic.
+The lure is used to catch trout and flounder.
+Float the soap on top of the bath water.
+A blue crane is a tall wading bird.
+A fresh start will work such wonders.
+The club rented the rink for the fifth night.
+After the dance, they went straight home.
+The hostess taught the new maid to serve.
+He wrote his last novel there at the inn.
+Even the worst will beat his low score.
+The cement had dried when he moved it.
+The loss of the second ship was hard to take.
+The fly made its way along the wall.
+Do that with a wooden stick.
+Live wires should be kept covered.
+The large house had hot water taps.
+It is hard to erase blue or red ink.
+Write at once or you may forget it.
+The doorknob was made of bright clean brass.
+The wreck occurred by the bank on Main Street.
+A pencil with black lead writes best.
+Coax a young calf to drink from a bucket.
+Schools for ladies teach charm and grace.
+The lamp shone with a steady green flame.
+They took the axe and the saw to the forest.
+The ancient coin was quite dull and worn.
+The shaky barn fell with a loud crash.
+Jazz and swing fans like fast music.
+Rake the rubbish up and then burn it.
+Slash the gold cloth into fine ribbons.
+Try to have the court decide the case.
+They are pushed back each time they attack.
+He broke his ties with groups of former friends.
+They floated on the raft to sun their white backs.
+The map had an X that meant nothing.
+Whitings are small fish caught in nets.
+Some ads serve to cheat buyers.
+Jerk the rope and the bell rings weakly.
+A waxed floor makes us lose balance.
+Madam, this is the best brand of corn.
+On the islands the sea breeze is soft and mild.
+The play began as soon as we sat down.
+This will lead the world to more sound and fury.
+Add salt before you fry the egg.
+The rush for funds reached its peak Tuesday.
+The birch looked stark white and lonesome.
+The box is held by a bright red snapper.
+To make pure ice, you freeze water.
+The first worm gets snapped early.
+Jump the fence and hurry up the bank.
+Yell and clap as the curtain slides back.
+They are men who walk the middle of the road.
+Both brothers wear the same size.
+In some form or other we need fun.
+The prince ordered his head chopped off.
+The houses are built of red clay bricks.
+Ducks fly north but lack a compass.
+Fruit flavors are used in fizz drinks.
+These pills do less good than others.
+Canned pears lack full flavor.
+The dark pot hung in the front closet.
+Carry the pail to the wall and spill it there.
+The train brought our hero to the big town.
+We are sure that one war is enough.
+Gray paint stretched for miles around.
+The rude laugh filled the empty room.
+High seats are best for football fans.
+Tea served from the brown jug is tasty.
+A dash of pepper spoils beef stew.
+A zestful food is the hot-cross bun.
+The horse trotted around the field at a brisk pace.
+Find the twin who stole the pearl necklace.
+Cut the cord that binds the box tightly.
+The red tape bound the smuggled food.
+Look in the corner to find the tan shirt.
+The cold drizzle will halt the bond drive.
+Nine men were hired to dig the ruins.
+The junk yard had a mouldy smell.
+The flint sputtered and lit a pine torch.
+Soak the cloth and drown the sharp odor.
+The shelves were bare of both jam or crackers.
+A joy to every child is the swan boat.
+All sat frozen and watched the screen.
+A cloud of dust stung his tender eyes.
+To reach the end he needs much courage.
+Shape the clay gently into block form.
+A ridge on a smooth surface is a bump or flaw.
+Hedge apples may stain your hands green.
+Quench your thirst, then eat the crackers.
+Tight curls get limp on rainy days.
+The mute muffled the high tones of the horn.
+The gold ring fits only a pierced ear.
+The old pan was covered with hard fudge.
+Watch the log float in the wide river.
+The node on the stalk of wheat grew daily.
+The heap of fallen leaves was set on fire.
+Write fast if you want to finish early.
+His shirt was clean but one button was gone.
+The barrel of beer was a brew of malt and hops.
+Tin cans are absent from store shelves.
+Slide the box into that empty space.
+The plant grew large and green in the window.
+The beam dropped down on the workmen's head.
+Pink clouds floated with the breeze.
+She danced like a swan, tall and graceful.
+The tube was blown and the tire flat and useless.
+It is late morning on the old wall clock.
+Let's all join as we sing the last chorus.
+The last switch cannot be turned off.
+The fight will end in just six minutes.
+The store walls were lined with colored frocks.
+The peace league met to discuss their plans.
+The rise to fame of a person takes luck.
+Paper is scarce, so write with much care.
+The quick fox jumped on the sleeping cat.
+The nozzle of the fire hose was bright brass.
+Screw the round cap on as tight as needed.
+Time brings us many changes.
+The purple tie was ten years old.
+Men think and plan and sometimes act.
+Fill the ink jar with sticky glue.
+He smoke a big pipe with strong contents.
+We need grain to keep our mules healthy.
+Pack the records in a neat thin case.
+The crunch of feet in the snow was the only sound.
+The copper bowl shone in the sun's rays.
+Boards will warp unless kept dry.
+The plush chair leaned against the wall.
+Glass will clink when struck by metal.
+Bathe and relax in the cool green grass.
+Nine rows of soldiers stood in line.
+The beach is dry and shallow at low tide.
+The idea is to sew both edges straight.
+The kitten chased the dog down the street.
+Pages bound in cloth make a book.
+Try to trace the fine lines of the painting.
+Women form less than half of the group.
+The zones merge in the central part of town.
+A gem in the rough needs work to polish.
+Code is used when secrets are sent.
+Most of the news is easy for us to hear.
+He used the lathe to make brass objects.
+The vane on top of the pole revolved in the wind.
+Mince pie is a dish served to children.
+The clan gathered on each dull night.
+Let it burn, it gives us warmth and comfort.
+A castle built from sand fails to endure.
+A child's wit saved the day for us.
+Tack the strip of carpet to the worn floor.
+Next Tuesday we must vote.
+Pour the stew from the pot into the plate.
+Each penny shone like new.
+The man went to the woods to gather sticks.
+The dirt piles were lines along the road.
+The logs fell and tumbled into the clear stream.
+Just hoist it up and take it away.
+A ripe plum is fit for a king's palate.
+Our plans right now are hazy.
+Brass rings are sold by these natives.
+It takes a good trap to capture a bear.
+Feed the white mouse some flower seeds.
+The thaw came early and freed the stream.
+He took the lead and kept it the whole distance.
+The key you designed will fit the lock.
+Plead to the council to free the poor thief.
+Better hash is made of rare beef.
+This plank was made for walking on.
+The lake sparkled in the red hot sun.
+He crawled with care along the ledge.
+Tend the sheep while the dog wanders.
+It takes a lot of help to finish these.
+Mark the spot with a sign painted red.
+Take two shares as a fair profit.
+The fur of cats goes by many names.
+North winds bring colds and fevers.
+He asks no person to vouch for him.
+Go now and come here later.
+A sash of gold silk will trim her dress.
+Soap can wash most dirt away.
+That move means the game is over.
+He wrote down a long list of items.
+A siege will crack the strong defense.
+Grape juice and water mix well.
+Roads are paved with sticky tar.
+Fake stones shine but cost little.
+The drip of the rain made a pleasant sound.
+Smoke poured out of every crack.
+Serve the hot rum to the tired heroes.
+Much of the story makes good sense.
+The sun came up to light the eastern sky.
+Heave the line over the port side.
+A lathe cuts and trims any wood.
+It's a dense crowd in two distinct ways.
+His hip struck the knee of the next player.
+The stale smell of old beer lingers.
+The desk was firm on the shaky floor.
+It takes heat to bring out the odor.
+Beef is scarcer than some lamb.
+Raise the sail and steer the ship northward.
+A cone costs five cents on Mondays.
+A pod is what peas always grow in.
+Jerk the dart from the cork target.
+No cement will hold hard wood.
+We now have a new base for shipping.
+A list of names is carved around the base.
+The sheep were led home by a dog.
+Three for a dime, the young peddler cried.
+The sense of smell is better than that of touch.
+No hardship seemed to keep him sad.
+Grace makes up for lack of beauty.
+Nudge gently but wake her now.
+The news struck doubt into restless minds.
+Once we stood beside the shore.
+A chink in the wall allowed a draft to blow.
+Fasten two pins on each side.
+A cold dip restores health and zest.
+He takes the oath of office each March.
+The sand drifts over the sill of the old house.
+The point of the steel pen was bent and twisted.
+There is a lag between thought and act.
+Seed is needed to plant the spring corn.
+Draw the chart with heavy black lines.
+The boy owed his pal thirty cents.
+The chap slipped into the crowd and was lost.
+Hats are worn to tea and not to dinner.
+The ramp led up to the wide highway.
+Beat the dust from the rug onto the lawn.
+Say it slowly but make it ring clear.
+The straw nest housed five robins.
+Screen the porch with woven straw mats.
+This horse will nose his way to the finish.
+The dry wax protects the deep scratch.
+He picked up the dice for a second roll.
+These coins will be needed to pay his debt.
+The nag pulled the frail cart along.
+Twist the valve and release hot steam.
+The vamp of the shoe had a gold buckle.
+The smell of burned rags itches my nose.
+New pants lack cuffs and pockets.
+The marsh will freeze when cold enough.
+They slice the sausage thin with a knife.
+The bloom of the rose lasts a few days.
+A gray mare walked before the colt.
+Breakfast buns are fine with a hot drink.
+Bottles hold four kinds of rum.
+The man wore a feather in his felt hat.
+He wheeled the bike past the winding road.
+Drop the ashes on the worn old rug.
+The desk and both chairs were painted tan.
+Throw out the used paper cup and plate.
+A clean neck means a neat collar.
+The couch cover and hall drapes were blue.
+The stems of the tall glasses cracked and broke.
+The wall phone rang loud and often.
+The clothes dried on a thin wooden rack.
+Turn on the lantern which gives us light.
+The cleat sank deeply into the soft turf.
+The bills were mailed promptly on the tenth of the month.
+To have is better than to wait and hope.
+The price is fair for a good antique clock.
+The music played on while they talked.
+Dispense with a vest on a day like this.
+The bunch of grapes was pressed into wine.
+He sent the figs, but kept the ripe cherries.
+The hinge on the door creaked with old age.
+The screen before the fire kept in the sparks.
+Fly by night, and you waste little time.
+Thick glasses helped him read the print.
+Birth and death mark the limits of life.
+The chair looked strong but had no bottom.
+The kite flew wildly in the high wind.
+A fur muff is stylish once more.
+The tin box held priceless stones.
+We need an end of all such matter.
+The case was puzzling to the old and wise.
+The bright lanterns were gay on the dark lawn.
+We don't get much money but we have fun.
+The youth drove with zest, but little skill.
+Five years he lived with a shaggy dog.
+A fence cuts through the corner lot.
+The way to save money is not to spend much.
+Shut the hatch before the waves push it in.
+The odor of spring makes young hearts jump.
+Crack the walnut with your sharp side teeth.
+He offered proof in the form of a large chart.
+Send the stuff in a thick paper bag.
+A quart of milk is water for the most part.
+They told wild tales to frighten him.
+The three story house was built of stone.
+In the rear of the ground floor was a large passage.
+A man in a blue sweater sat at the desk.
+Oats are a food eaten by horse and man.
+Their eyelids droop for want of sleep.
+A sip of tea revives his tired friend.
+There are many ways to do these things.
+Tuck the sheet under the edge of the mat.
+A force equal to that would move the earth.
+We like to see clear weather.
+The work of the tailor is seen on each side.
+Take a chance and win a china doll.
+Shake the dust from your shoes, stranger.
+She was kind to sick old people.
+The square wooden crate was packed to be shipped.
+The dusty bench stood by the stone wall.
+We dress to suit the weather of most days.
+Smile when you say nasty words.
+A bowl of rice is free with chicken stew.
+The water in this well is a source of good health.
+Take shelter in this tent, but keep still.
+That guy is the writer of a few banned books.
+The little tales they tell are false.
+The door was barred, locked, and bolted as well.
+Ripe pears are fit for a queen's table.
+A big wet stain was on the round carpet.
+The kite dipped and swayed, but stayed aloft.
+The pleasant hours fly by much too soon.
+The room was crowded with a wild mob.
+This strong arm shall shield your honor.
+She blushed when he gave her a white orchid.
+The beetle droned in the hot June sun.
+Press the pedal with your left foot.
+Neat plans fail without luck.
+The black trunk fell from the landing.
+The bank pressed for payment of the debt.
+The theft of the pearl pin was kept secret.
+Shake hands with this friendly child.
+The vast space stretched into the far distance.
+A rich farm is rare in this sandy waste.
+His wide grin earned many friends.
+Flax makes a fine brand of paper.
+Hurdle the pit with the aid of a long pole.
+A strong bid may scare your partner stiff.
+Even a just cause needs power to win.
+Peep under the tent and see the clowns.
+The leaf drifts along with a slow spin.
+Cheap clothes are flashy but don't last.
+A thing of small note can cause despair.
+Flood the mails with requests for this book.
+A thick coat of black paint covered all.
+The pencil was cut to be sharp at both ends.
+Those last words were a strong statement.
+He wrote his name boldly at the top of the sheet.
+Dill pickles are sour but taste fine.
+Down that road is the way to the grain farmer.
+Either mud or dust are found at all times.
+The best method is to fix it in place with clips.
+If you mumble your speech will be lost.
+At night the alarm roused him from a deep sleep.
+Read just what the meter says.
+Fill your pack with bright trinkets for the poor.
+The small red neon lamp went out.
+Clams are small, round, soft, and tasty.
+The fan whirled its round blades softly.
+The line where the edges join was clean.
+Breathe deep and smell the piny air.
+It matters not if he reads these words or those.
+A brown leather bag hung from its strap.
+A toad and a frog are hard to tell apart.
+A white silk jacket goes with any shoes.
+A break in the dam almost caused a flood.
+Paint the sockets in the wall dull green.
+The child crawled into the dense grass.
+Bribes fail where honest men work.
+Trample the spark, else the flames will spread.
+The hilt of the sword was carved with fine designs.
+A round hole was drilled through the thin board.
+Footprints showed the path he took up the beach.
+She was waiting at my front lawn.
+A vent near the edge brought in fresh air.
+Prod the old mule with a crooked stick.
+It is a band of steel three inches wide.
+The pipe ran almost the length of the ditch.
+It was hidden from sight by a mass of leaves and shrubs.
+The weight of the package was seen on the high scale.
+Wake and rise, and step into the green outdoors.
+The green light in the brown box flickered.
+The brass tube circled the high wall.
+The lobes of her ears were pierced to hold rings.
+Hold the hammer near the end to drive the nail.
+Next Sunday is the twelfth of the month.
+Every word and phrase he speaks is true.
+He put his last cartridge into the gun and fired.
+They took their kids from the public school.
+Drive the screw straight into the wood.
+Keep the hatch tight and the watch constant.
+Sever the twine with a quick snip of the knife.
+Paper will dry out when wet.
+Slide the catch back and open the desk.
+Help the weak to preserve their strength.
+A sullen smile gets few friends.
+Stop whistling and watch the boys march.
+Jerk the cord, and out tumbles the gold.
+Slide the tray across the glass top.
+The cloud moved in a stately way and was gone.
+Light maple makes for a swell room.
+Set the piece here and say nothing.
+Dull stories make her laugh.
+A stiff cord will do to fasten your shoe.
+Get the trust fund to the bank early.
+Choose between the high road and the low.
+A plea for funds seems to come again.
+He lent his coat to the tall gaunt stranger.
+There is a strong chance it will happen once more.
+The duke left the park in a silver coach.
+Greet the new guests and leave quickly.
+When the frost has come it is time for turkey.
+Sweet words work better than fierce.
+A thin stripe runs down the middle.
+A six comes up more often than a ten.
+Lush fern grow on the lofty rocks.
+The ram scared the school children off.
+The team with the best timing looks good.
+The farmer swapped his horse for a brown ox.
+Sit on the perch and tell the others what to do.
+A steep trail is painful for our feet.
+The early phase of life moves fast.
+Green moss grows on the northern side.
+Tea in thin china has a sweet taste.
+Pitch the straw through the door of the stable.
+The latch on the back gate needed a nail.
+The goose was brought straight from the old market.
+The sink is the thing in which we pile dishes.
+A whiff of it will cure the most stubborn cold.
+The facts don't always show who is right.
+She flaps her cape as she parades the street.
+The loss of the cruiser was a blow to the fleet.
+Loop the braid to the left and then over.
+Plead with the lawyer to drop the lost cause.
+Calves thrive on tender spring grass.
+Post no bills on this office wall.
+Tear a thin sheet from the yellow pad.
+A cruise in warm waters in a sleek yacht is fun.
+A streak of color ran down the left edge.
+It was done before the boy could see it.
+Crouch before you jump or miss the mark.
+Pack the kits and don't forget the salt.
+The square peg will settle in the round hole.
+Fine soap saves tender skin.
+Poached eggs and tea must suffice.
+Bad nerves are jangled by a door slam.
+Ship maps are different from those for planes.
+Dimes showered down from all sides.
+They sang the same tunes at each party.
+The sky in the west is tinged with orange red.
+The pods of peas ferment in bare fields.
+The horse balked and threw the tall rider.
+The hitch between the horse and cart broke.
+Pile the coal high in the shed corner.
+A gold vase is both rare and costly.
+The knife was hung inside its bright sheath.
+The rarest spice comes from the far East.
+The roof should be tilted at a sharp slant.
+A smatter of French is worse than none.
+The mule trod the treadmill day and night.
+The aim of the contest is to raise a great fund.
+To send it now in large amounts is bad.
+There is a fine hard tang in salty air.
+Cod is the main business of the north shore.
+The slab was hewn from heavy blocks of slate.
+Dunk the stale biscuits into strong drink.
+Hang tinsel from both branches.
+Cap the jar with a tight brass cover.
+The poor boy missed the boat again.
+Be sure to set the lamp firmly in the hole.
+Pick a card and slip it under the pack.
+A round mat will cover the dull spot.
+The first part of the plan needs changing.
+A good book informs of what we ought to know.
+The mail comes in three batches per day.
+You cannot brew tea in a cold pot.
+Dots of light betrayed the black cat.
+Put the chart on the mantel and tack it down.
+The night shift men rate extra pay.
+The red paper brightened the dim stage.
+See the player scoot to third base.
+Slide the bill between the two leaves.
+Many hands help get the job done.
+We don't like to admit our small faults.
+No doubt about the way the wind blows.
+Dig deep in the earth for pirate's gold.
+The steady drip is worse than a drenching rain.
+A flat pack takes less luggage space.
+Green ice frosted the punch bowl.
+A stuffed chair slipped from the moving van.
+The stitch will serve but needs to be shortened.
+A thin book fits in the side pocket.
+The gloss on top made it unfit to read.
+The hail pattered on the burnt brown grass.
+Seven seals were stamped on great sheets.
+Our troops are set to strike heavy blows.
+The store was jammed before the sale could start.
+It was a bad error on the part of the new judge.
+One step more and the board will collapse.
+Take the match and strike it against your shoe.
+The pot boiled, but the contents failed to jell.
+The baby puts his right foot in his mouth.
+The bombs left most of the town in ruins.
+Stop and stare at the hard working man.
+The streets are narrow and full of sharp turns.
+The pup jerked the leash as he saw a feline shape.
+Open your book to the first page.
+Fish evade the net and swim off.
+Dip the pail once and let it settle.
+Will you please answer that phone.
+The big red apple fell to the ground.
+The curtain rose and the show was on.
+The young prince became heir to the throne.
+He sent the boy on a short errand.
+Leave now and you will arrive on time.
+The corner store was robbed last night.
+A gold ring will please most any girl.
+The long journey home took a year.
+She saw a cat in the neighbor's house.
+A pink shell was found on the sandy beach.
+Small children came to see him.
+The grass and bushes were wet with dew.
+The blind man counted his old coins.
+A severe storm tore down the barn.
+She called his name many times.
When you hear the bell, come quickly.
\ No newline at end of file
diff --git a/system/TGWUI Extension/languages.json b/system/TGWUI_Extension/languages.json
similarity index 94%
rename from system/TGWUI Extension/languages.json
rename to system/TGWUI_Extension/languages.json
index 3895d7f..3b780e8 100644
--- a/system/TGWUI Extension/languages.json
+++ b/system/TGWUI_Extension/languages.json
@@ -1,18 +1,18 @@
-{
- "Arabic": "ar",
- "Chinese": "zh-cn",
- "Czech": "cs",
- "Dutch": "nl",
- "English": "en",
- "French": "fr",
- "German": "de",
- "Hungarian": "hu",
- "Italian": "it",
- "Japanese": "ja",
- "Korean": "ko",
- "Polish": "pl",
- "Portuguese": "pt",
- "Russian": "ru",
- "Spanish": "es",
- "Turkish": "tr"
+{
+ "Arabic": "ar",
+ "Chinese": "zh-cn",
+ "Czech": "cs",
+ "Dutch": "nl",
+ "English": "en",
+ "French": "fr",
+ "German": "de",
+ "Hungarian": "hu",
+ "Italian": "it",
+ "Japanese": "ja",
+ "Korean": "ko",
+ "Polish": "pl",
+ "Portuguese": "pt",
+ "Russian": "ru",
+ "Spanish": "es",
+ "Turkish": "tr"
}
\ No newline at end of file
diff --git a/system/TGWUI Extension/lowvramdisabled.wav b/system/TGWUI_Extension/lowvramdisabled.wav
similarity index 100%
rename from system/TGWUI Extension/lowvramdisabled.wav
rename to system/TGWUI_Extension/lowvramdisabled.wav
diff --git a/system/TGWUI Extension/lowvramenabled.wav b/system/TGWUI_Extension/lowvramenabled.wav
similarity index 100%
rename from system/TGWUI Extension/lowvramenabled.wav
rename to system/TGWUI_Extension/lowvramenabled.wav
diff --git a/system/TGWUI_Extension/script.py b/system/TGWUI_Extension/script.py
new file mode 100644
index 0000000..b6ca8f4
--- /dev/null
+++ b/system/TGWUI_Extension/script.py
@@ -0,0 +1,1391 @@
+import re
+import sys
+import json
+import time
+import inspect
+import random
+import logging
+import requests
+import threading
+import gradio as gr
+from pathlib import Path
+from modules import chat, shared, ui_chat
+from modules.utils import gradio
+from requests.exceptions import RequestException, ConnectionError
+
+# Store the current disable level
+current_disable_level = logging.getLogger().manager.disable
+this_dir = Path(__file__).parent.resolve()
+
+class TGWUIModeManager:
+ def __init__(self):
+ self.debug_tgwui = False # General Debugging setting. Default is `False`
+ self.debug_func = False # Print function name as its used. Default is `False`
+ self.is_local = self._detect_mode()
+ self._setup_config()
+ self._setup_server_connection()
+
+ def _detect_mode(self):
+ """Detect if we're running as part of AllTalk installation"""
+ try:
+ current_dir = Path(__file__).parent
+ alltalk_root = current_dir.parent.parent
+
+ # Just check if we're in the right place, don't import
+ required_files = [
+ alltalk_root / "confignew.json",
+ alltalk_root / "script.py"
+ ]
+ required_folders = [
+ alltalk_root / "system",
+ alltalk_root / "models",
+ alltalk_root / "outputs",
+ alltalk_root / "voices"
+ ]
+
+ files_exist = all(f.exists() for f in required_files)
+ folders_exist = all(f.is_dir() for f in required_folders)
+
+ return files_exist and folders_exist
+
+ except (ImportError, FileNotFoundError) as e:
+ # Don't try to use mode_manager here since it doesn't exist yet
+ print(f"Error detecting mode: {str(e)}")
+ return False
+
+ def _setup_config(self):
+ """Load appropriate configuration based on mode"""
+ if self.is_local:
+ # We're in TGWUI mode, use default settings
+ self.branding = "AllTalk "
+ self.server = {
+ "protocol": "http://",
+ "address": "127.0.0.1:7851", # Default port
+ "timeout": 5
+ }
+ # Set complete default config structure
+ self.config = {
+ "api_def": {
+ "api_port_number": 7851,
+ "api_use_legacy_api": False
+ },
+ "tgwui": {
+ "tgwui_activate_tts": True,
+ "tgwui_autoplay_tts": True,
+ "tgwui_narrator_enabled": "false",
+ "tgwui_non_quoted_text_is": "narrator",
+ "tgwui_deepspeed_enabled": False,
+ "tgwui_language": "English",
+ "tgwui_lowvram_enabled": False,
+ "tgwui_pitch_set": 0,
+ "tgwui_temperature_set": 0.75,
+ "tgwui_repetitionpenalty_set": 10,
+ "tgwui_generationspeed_set": 1,
+ "tgwui_narrator_voice": "Please Refresh Settings",
+ "tgwui_show_text": True,
+ "tgwui_character_voice": "Please Refresh Settings",
+ "tgwui_rvc_char_voice": "Disabled",
+ "tgwui_rvc_char_pitch": 0,
+ "tgwui_rvc_narr_voice": "Disabled",
+ "tgwui_rvc_narr_pitch": 0
+ },
+ "remote_connection": { # Add this section for local mode too
+ "use_legacy_api": False,
+ "server_protocol": "http://",
+ "server_address": "127.0.0.1:7851",
+ "connection_timeout": 5
+ }
+ }
+ else:
+ config_path = Path(__file__).parent / "tgwui_remote_config.json"
+ try:
+ with open(config_path, "r") as f:
+ self.config = json.load(f)
+ except (FileNotFoundError, json.JSONDecodeError) as e:
+ print(f"Error loading remote config ({str(e)}), using defaults")
+ self.config = {
+ "branding": "AllTalk ",
+ "tgwui": {
+ "tgwui_activate_tts": True,
+ "tgwui_autoplay_tts": False,
+ "tgwui_show_text": True,
+ "tgwui_narrator_enabled": "false",
+ "tgwui_non_quoted_text_is": "narrator",
+ "tgwui_deepspeed_enabled": False,
+ "tgwui_language": "English",
+ "tgwui_lowvram_enabled": False,
+ "tgwui_pitch_set": 0,
+ "tgwui_temperature_set": 0.75,
+ "tgwui_repetitionpenalty_set": 10,
+ "tgwui_generationspeed_set": 1.0,
+ "tgwui_narrator_voice": "Please Refresh Settings",
+ "tgwui_character_voice": "Please Refresh Settings",
+ "tgwui_rvc_char_voice": "Disabled",
+ "tgwui_rvc_char_pitch": 0,
+ "tgwui_rvc_narr_voice": "Disabled",
+ "tgwui_rvc_narr_pitch": 0
+ },
+ "remote_connection": {
+ "server_protocol": "http://",
+ "server_address": "127.0.0.1:7851",
+ "connection_timeout": 5,
+ "use_legacy_api": False
+ }
+ }
+
+ self.branding = self.config['branding']
+ self.server = {
+ "protocol": self.config['remote_connection']['server_protocol'],
+ "address": self.config['remote_connection']['server_address'],
+ "timeout": self.config['remote_connection']['connection_timeout']
+ }
+
+ def _setup_server_connection(self):
+ """Initialize API connection settings"""
+ self.server_url = f"{self.server['protocol']}{self.server['address']}"
+
+ def get_api_url(self, endpoint):
+ """Get full API URL for a given endpoint"""
+ return f"{self.server_url}/api/{endpoint}"
+
+ def save_settings(self):
+ """Save current settings to appropriate location"""
+ if self.is_local:
+ # We're in TGWUI mode, we don't need to save to AllTalk's config
+ # The settings are maintained by TGWUI
+ pass
+ else:
+ # In remote mode, save to local config file
+ settings = {
+ "branding": self.branding,
+ "tgwui": self.config["tgwui"],
+ "remote_connection": {
+ "server_protocol": self.server["protocol"],
+ "server_address": self.server["address"],
+ "connection_timeout": self.server["timeout"],
+ "use_legacy_api": self.config['remote_connection']['use_legacy_api']
+ }
+ }
+ config_path = Path(__file__).parent / "tgwui_remote_config.json"
+ with open(config_path, "w") as f:
+ json.dump(settings, f, indent=4)
+
+# Initialize mode manager at startup
+mode_manager = TGWUIModeManager()
+
+##########################
+# Central print function #
+##########################
+def print_func(message, message_type="standard", component="TTS", debug_type=None):
+ """Centralized print function for AllTalk messages
+
+ Args:
+ message (str): The message to print
+ message_type (str): Type of message ("standard", "warning", "error", "debug")
+ component (str): Component identifier ("TTS", "ENG", "Server", etc.)
+ debug_type (str, optional): Can be supplied on the call to specify things like:
+ - The function that is sending the debug request.
+ - The operation taking place
+ """
+ prefix = f"[{mode_manager.branding}{component}] "
+
+ if message_type == "warning":
+ print(f"{prefix}\033[91mWarning\033[0m {message}")
+ elif message_type == "error":
+ print(f"{prefix}\033[91mError\033[0m {message}")
+ elif message_type == "debug":
+ if debug_type is None:
+ debug_type = ""
+ color = "\033[92m" if "Function entry" in message else "\033[93m"
+ print(f"{prefix}\033[94mDebug\033[0m {color}{debug_type}\033[0m {message}")
+ else: # standard message
+ print(f"{prefix}{message}")
+
+def debug_func_entry():
+ """Print debug message for function entry if debug_func is enabled/true"""
+ if mode_manager.debug_func:
+ print_func("Function entry", "debug", debug_type=inspect.currentframe().f_back.f_code.co_name)
+
+# Load languages
+with open(Path(__file__).parent / "languages.json", encoding="utf8") as f:
+ languages = json.load(f)
+
+# Setup other globals
+process_lock = threading.Lock()
+models_available = None
+alltalk_settings = None
+current_model_loaded = None
+tts_model_loaded = None
+
+def get_alltalk_settings():
+ if mode_manager.debug_func:
+ print_func("Function entry", "debug", debug_type=inspect.currentframe().f_code.co_name)
+ global current_model_loaded, models_available
+
+ def log_error(message, status_code=None):
+ """Helper function for consistent error logging"""
+ if status_code:
+ print_func(f"{message} Status code:\n{status_code}", message_type="warning")
+ else:
+ print_func(f"{message}", message_type="warning")
+
+ try:
+ # Make all API requests
+ api_calls = {
+ "voices": requests.get(
+ mode_manager.get_api_url("voices"),
+ timeout=mode_manager.server["timeout"]
+ ),
+ "rvc": requests.get(
+ mode_manager.get_api_url("rvcvoices"),
+ timeout=mode_manager.server["timeout"]
+ ),
+ "settings": requests.get(
+ mode_manager.get_api_url("currentsettings"),
+ timeout=mode_manager.server["timeout"]
+ )
+ }
+
+ # Check if all responses are successful
+ if all(response.status_code == 200 for response in api_calls.values()):
+ try:
+ voices_data = api_calls["voices"].json()
+ rvcvoices_data = api_calls["rvc"].json()
+ settings_data = api_calls["settings"].json()
+
+ # Update global variables
+ models_available = [model["name"] for model in settings_data["models_available"]]
+ current_model_loaded = settings_data["current_model_loaded"]
+
+ # Update our local config with received values
+ if mode_manager.is_local:
+ mode_manager.config["tgwui"].update({
+ "tgwui_character_voice": voices_data["voices"][0] if voices_data["voices"] else "Please Refresh Settings",
+ "tgwui_narrator_voice": voices_data["voices"][0] if voices_data["voices"] else "Please Refresh Settings",
+ "tgwui_rvc_char_voice": rvcvoices_data["rvcvoices"][0] if rvcvoices_data["rvcvoices"] else "Disabled",
+ "tgwui_rvc_narr_voice": rvcvoices_data["rvcvoices"][0] if rvcvoices_data["rvcvoices"] else "Disabled",
+ })
+
+ return AllTalkServerSettings.from_api_response(
+ voices_data,
+ rvcvoices_data,
+ settings_data
+ )
+ except json.JSONDecodeError as e:
+ log_error(f"Failed to decode JSON response: {e}")
+ return AllTalkServerSettings()
+ else:
+ # Log specific failures
+ log_error(f"Failed to retrieve {mode_manager.branding}settings from API.")
+ for name, response in api_calls.items():
+ if response.status_code != 200:
+ log_error(f"Failed to retrieve {name} from API", response.status_code)
+ return AllTalkServerSettings()
+
+ except (RequestException, ConnectionError) as e:
+ log_error(f"Unable to connect to the {mode_manager.branding}server", str(e))
+ return AllTalkServerSettings()
+
+class AllTalkServerSettings:
+ def __init__(self):
+ # Voice and model settings
+ self.voices = ["Please Refresh Settings"]
+ self.rvcvoices = ["Please Refresh Settings"]
+ self.models_available = ["Please Refresh Settings"]
+ self.current_model_loaded = "Please Refresh Settings"
+ self.manufacturer_name = ""
+
+ # Engine capabilities
+ self.deepspeed_capable = False
+ self.deepspeed_available = False
+ self.deepspeed_enabled = False
+ self.languages_capable = False
+ self.multivoice_capable = False
+ self.multimodel_capable = False
+ self.streaming_capable = False
+ self.ttsengines_installed = False
+
+ # Generation settings
+ self.generationspeed_capable = False
+ self.generationspeed_set = 1.0
+ self.lowvram_capable = False
+ self.lowvram_enabled = False
+ self.pitch_capable = False
+ self.pitch_set = 0
+ self.repetitionpenalty_capable = False
+ self.repetitionpenalty_set = 10.0
+ self.temperature_capable = False
+ self.temperature_set = 0.75
+
+ @classmethod
+ def from_api_response(cls, voices_data, rvcvoices_data, settings_data):
+ """Create settings instance from API response data"""
+ debug_func_entry()
+ if mode_manager.debug_func:
+ print_func("Function entry", "debug", debug_type=inspect.currentframe().f_code.co_name)
+ settings = cls()
+
+ # Update voice and model information
+ settings.voices = sorted(voices_data["voices"])
+ settings.rvcvoices = rvcvoices_data["rvcvoices"]
+ settings.models_available = [model["name"] for model in settings_data["models_available"]]
+ settings.current_model_loaded = settings_data["current_model_loaded"]
+ settings.manufacturer_name = settings_data.get("manufacturer_name", "")
+
+ # Update capabilities and settings
+ settings_mapping = {
+ # Capabilities
+ "deepspeed_capable": ("deepspeed_capable", False),
+ "deepspeed_available": ("deepspeed_available", False),
+ "deepspeed_enabled": ("deepspeed_enabled", False),
+ "languages_capable": ("languages_capable", False),
+ "multivoice_capable": ("multivoice_capable", False),
+ "multimodel_capable": ("multimodel_capable", False),
+ "streaming_capable": ("streaming_capable", False),
+ "ttsengines_installed": ("ttsengines_installed", False),
+
+ # Generation settings
+ "generationspeed_capable": ("generationspeed_capable", False),
+ "generationspeed_set": ("generationspeed_set", 1.0),
+ "lowvram_capable": ("lowvram_capable", False),
+ "lowvram_enabled": ("lowvram_enabled", False),
+ "pitch_capable": ("pitch_capable", False),
+ "pitch_set": ("pitch_enabled", 0),
+ "repetitionpenalty_capable": ("repetitionpenalty_capable", False),
+ "repetitionpenalty_set": ("repetitionpenalty_set", 10.0),
+ "temperature_capable": ("temperature_capable", False),
+ "temperature_set": ("temperature_set", 0.75),
+ }
+
+ # Update all settings from the mapping
+ for attr, (key, default) in settings_mapping.items():
+ setattr(settings, attr, settings_data.get(key, default))
+
+ return settings
+
+
+# Print Spashscreen to console if running as Remote Extension
+if not mode_manager.is_local:
+ print_func("\033[94m _ _ _ \033[1;35m_____ _ _ \033[0m _____ _____ ____ ")# pylint: disable=line-too-long anomalous-backslash-in-string
+ print_func("\033[94m / \ | | |\033[1;35m_ _|_ _| | | __ \033[0m |_ _|_ _/ ___| ")# pylint: disable=line-too-long anomalous-backslash-in-string
+ print_func("\033[94m / _ \ | | |\033[1;35m | |/ _` | | |/ / \033[0m | | | | \___ \ ")# pylint: disable=line-too-long anomalous-backslash-in-string
+ print_func("\033[94m / ___ \| | |\033[1;35m | | (_| | | < \033[0m | | | | ___) |")# pylint: disable=line-too-long anomalous-backslash-in-string
+ print_func("\033[94m /_/ \_\_|_|\033[1;35m |_|\__,_|_|_|\_\ \033[0m |_| |_| |____/ ")# pylint: disable=line-too-long anomalous-backslash-in-string
+ print_func("")
+ print_func(f"\033[92m{mode_manager.branding}startup Mode : \033[93mText-Gen-webui Remote\033[0m")
+ print_func("")
+
+def stop_generate_tts():
+ """Sends request to stop current TTS generation"""
+ debug_func_entry()
+ api_url = mode_manager.get_api_url("stop-generation")
+
+ if mode_manager.debug_tgwui:
+ print_func("Attempting to stop TTS generation", message_type="debug", debug_type="stop_generate_tts")
+
+ try:
+ response = requests.put(api_url, timeout=mode_manager.server["timeout"])
+ if response.status_code == 200:
+ return response.json()["message"]
+ else:
+ print_func(f"Failed to stop generation. Status code:\n{response.status_code}", message_type="warning")
+ return {"message": "Failed to stop generation"}
+ except (RequestException, ConnectionError) as e:
+ print_func(f"Unable to connect to the {mode_manager.branding}server. Status code:\n{str(e)}", message_type="warning")
+ return {"message": "Failed to stop generation"}
+
+def send_reload_request(value_sent):
+ """Requests AllTalk server to load a different TTS model"""
+ debug_func_entry()
+ global tts_model_loaded
+ if not process_lock.acquire(blocking=False):
+ return {"status": "error", "message": "Server is currently busy. Try again in a few seconds."}
+ try:
+ tts_model_loaded = False
+ url = mode_manager.get_api_url("reload")
+ payload = {"tts_method": value_sent}
+
+ if mode_manager.debug_tgwui:
+ print_func(f"Requesting model reload to: {value_sent}", message_type="debug", debug_type="send_reload_request")
+
+ response = requests.post(url, params=payload)
+ response.raise_for_status()
+ tts_model_loaded = True
+ except requests.exceptions.RequestException as e:
+ print_func(f"Error during request to webserver process: Status code:\n{e}", message_type="warning")
+ tts_model_loaded = True
+ return {"status": "error", "message": str(e)}
+ finally:
+ process_lock.release()
+
+def send_lowvram_request(value_sent):
+ """Toggles low VRAM mode in AllTalk server"""
+ debug_func_entry()
+ global tts_model_loaded
+ if not process_lock.acquire(blocking=False):
+ return {"status": "error", "message": "Server is currently busy. Try again in a few seconds."}
+ try:
+ tts_model_loaded = False
+ audio_path = this_dir / ("lowvramenabled.wav" if value_sent else "lowvramdisabled.wav")
+
+ if mode_manager.debug_tgwui:
+ print_func(f"Setting low VRAM mode to: {value_sent}", message_type="debug", debug_type="send_lowvram_request")
+
+ url = mode_manager.get_api_url(f"lowvramsetting?new_low_vram_value={value_sent}")
+ response = requests.post(url, headers={"Content-Type": "application/json"})
+ response.raise_for_status()
+
+ json_response = response.json()
+ if json_response.get("status") == "lowvram-success":
+ tts_model_loaded = True
+
+ return f''
+ except requests.exceptions.RequestException as e:
+ print_func(f"Error during request to webserver process: Status code:\n{e}", message_type="warning")
+ return {"status": "error", "message": str(e)}
+ finally:
+ process_lock.release()
+
+def send_deepspeed_request(value_sent):
+ """Toggles DeepSpeed acceleration in AllTalk server"""
+ debug_func_entry()
+ global tts_model_loaded
+ if not process_lock.acquire(blocking=False):
+ return {"status": "error", "message": "Server is currently busy. Try again in a few seconds."}
+ try:
+ tts_model_loaded = False
+ audio_path = this_dir / ("deepspeedenabled.wav" if value_sent else "deepspeeddisabled.wav")
+
+ if mode_manager.debug_tgwui:
+ print_func(f"Setting DeepSpeed to: {value_sent}", message_type="debug", debug_type="send_deepspeed_request")
+
+ url = mode_manager.get_api_url(f"deepspeed?new_deepspeed_value={value_sent}")
+ response = requests.post(url, headers={"Content-Type": "application/json"})
+ response.raise_for_status()
+
+ json_response = response.json()
+ if json_response.get("status") == "deepspeed-success":
+ tts_model_loaded = True
+
+ process_lock.release()
+ return f''
+ except requests.exceptions.RequestException as e:
+ print_func(f"Error during request to webserver process: Status code:\n{e}", message_type="warning")
+ return {"status": "error", "message": str(e)}
+ finally:
+ process_lock.release()
+
+def send_and_generate(gen_text, gen_character_voice, gen_rvccharacter_voice, gen_rvccharacter_pitch, gen_narrator_voice, gen_rvcnarrator_voice,
+ gen_rvcnarrator_pitch, gen_narrator_activated, gen_textnotinisde, gen_repetition, gen_language, gen_filter, gen_speed,
+ gen_pitch, gen_autoplay, gen_autoplay_vol, gen_file_name, gen_temperature, gen_filetimestamp,
+ gen_stream, gen_stopcurrentgen):
+ """Sends text to AllTalk server for TTS generation"""
+ debug_func_entry()
+ api_url = mode_manager.get_api_url("tts-generate")
+
+ if mode_manager.debug_tgwui:
+ print_func("Starting TTS generation request", message_type="debug", debug_type="send_and_generate")
+
+ if gen_stopcurrentgen:
+ stop_generate_tts()
+
+ mode_manager.save_settings()
+
+ if gen_stream == "true":
+ api_url = mode_manager.get_api_url("tts-generate-streaming")
+ encoded_text = requests.utils.quote(gen_text)
+ streaming_url = f"{api_url}?text={encoded_text}&voice={gen_character_voice}&language={gen_language}&output_file={gen_file_name}"
+ return streaming_url, str("TTS Streaming Audio Generated")
+
+ data = {
+ "text_input": gen_text,
+ "text_filtering": gen_filter,
+ "character_voice_gen": gen_character_voice,
+ "rvccharacter_voice_gen": gen_rvccharacter_voice,
+ "rvccharacter_pitch": gen_rvccharacter_pitch,
+ "narrator_enabled": str(gen_narrator_activated).lower(),
+ "narrator_voice_gen": gen_narrator_voice,
+ "rvcnarrator_voice_gen": gen_rvcnarrator_voice,
+ "rvcnarrator_pitch": gen_rvcnarrator_pitch,
+ "text_not_inside": gen_textnotinisde,
+ "language": gen_language,
+ "output_file_name": str(gen_file_name),
+ "output_file_timestamp": str(gen_filetimestamp).lower(),
+ "autoplay": str(gen_autoplay).lower(),
+ "autoplay_volume": str(gen_autoplay_vol),
+ "speed": str(gen_speed),
+ "pitch": str(gen_pitch),
+ "temperature": str(gen_temperature),
+ "repetition_penalty": str(gen_repetition),
+ }
+
+ if mode_manager.debug_tgwui:
+ print_func("Generation request parameters:", message_type="debug", debug_type="send_and_generate")
+ for key, value in data.items():
+ print_func(f"{key}: {value}", message_type="debug", debug_type="send_and_generate")
+
+ try:
+ response = requests.post(api_url, data=data)
+ response.raise_for_status()
+ result = response.json()
+
+ if gen_autoplay == "true":
+ return None, str("TTS Audio Generated (Played remotely)")
+
+ if mode_manager.config["remote_connection"]["use_legacy_api"]:
+ return result['output_file_url'], str("TTS Audio Generated")
+ else:
+ output_file_url = f"{mode_manager.server_url}{result['output_file_url']}"
+ return output_file_url, str("TTS Audio Generated")
+ except (RequestException, ConnectionError) as e:
+ print_func(f"Error occurred during the API request: Status code:\n{str(e)}", message_type="warning")
+ return None, str("Error occurred during the API request")
+
+def output_modifier(string, state):
+ """Modifies TGWUI output to include TTS audio"""
+ debug_func_entry()
+ if not mode_manager.config["tgwui"]["tgwui_activate_tts"]:
+ return string
+
+ if mode_manager.debug_tgwui:
+ print_func("Processing text for TTS generation", message_type="debug", debug_type="output_modifier")
+
+ # Strip out Images
+ img_info = ""
+ cleaned_text, img_info = tgwui_extract_and_remove_images(string)
+ if cleaned_text is None:
+ return
+
+ # Get current settings
+ language_code = languages.get(mode_manager.config["tgwui"]["tgwui_language"])
+ character_voice = mode_manager.config["tgwui"]["tgwui_character_voice"]
+ rvc_character_voice = mode_manager.config["tgwui"]["tgwui_rvc_char_voice"]
+ rvc_character_pitch = mode_manager.config["tgwui"]["tgwui_rvc_char_pitch"]
+ narrator_voice = mode_manager.config["tgwui"]["tgwui_narrator_voice"]
+ rvc_narrator_voice = mode_manager.config["tgwui"]["tgwui_rvc_narr_voice"]
+ rvc_narrator_pitch = mode_manager.config["tgwui"]["tgwui_rvc_narr_pitch"]
+ narrator_enabled = mode_manager.config["tgwui"]["tgwui_narrator_enabled"]
+ text_not_inside = mode_manager.config["tgwui"]["tgwui_non_quoted_text_is"]
+ repetition_policy = mode_manager.config["tgwui"]["tgwui_repetitionpenalty_set"]
+ speed = mode_manager.config["tgwui"]["tgwui_generationspeed_set"]
+ pitch = mode_manager.config["tgwui"]["tgwui_pitch_set"]
+ temperature = mode_manager.config["tgwui"]["tgwui_temperature_set"]
+
+ if mode_manager.debug_tgwui:
+ print_func(f"Using character voice: {character_voice}, narrator voice: {narrator_voice}",
+ message_type="debug", debug_type="output_modifier")
+ print_func(f"Text length: {len(cleaned_text)} characters",
+ message_type="debug", debug_type="output_modifier")
+
+ # Lock and process TTS request
+ if process_lock.acquire(blocking=False):
+ try:
+ output_file = state["character_menu"] if "character_menu" in state else str("TTSOUT_")
+ # Ensure the file name matches the allowed pattern of AllTalk
+ output_file = re.sub(r"[^a-zA-Z0-9_]", "", output_file)
+
+ generate_response, status_message = send_and_generate(
+ cleaned_text, character_voice, rvc_character_voice, rvc_character_pitch, narrator_voice,
+ rvc_narrator_voice, rvc_narrator_pitch, narrator_enabled, text_not_inside, repetition_policy,
+ language_code, "html", speed, pitch, False, 0.8, output_file,
+ temperature, True, False, False
+ )
+
+ if status_message == "TTS Audio Generated":
+ autoplay = "autoplay" if mode_manager.config["tgwui"]["tgwui_autoplay_tts"] else ""
+ string = f''
+
+ if mode_manager.config["tgwui"]["tgwui_show_text"]:
+ string += tgwui_reinsert_images(cleaned_text, img_info)
+
+ shared.processing_message = "*Is typing...*"
+ return string
+ else:
+ print_func(f"Audio generation failed. Status code:\n{status_message}", message_type="warning")
+ finally:
+ process_lock.release()
+ else:
+ print_func("Audio generation is already in progress. Please wait.", message_type="warning")
+ return
+
+@debounce
+def voice_preview(string):
+ """Generates a preview of the selected voice settings"""
+ # debug_func_entry()
+ print("in voice preview function")
+ if not mode_manager.config["tgwui"]["tgwui_activate_tts"]:
+ return string
+
+ if mode_manager.debug_tgwui:
+ print_func("Generating voice preview", message_type="debug", debug_type="voice_preview")
+
+ language_code = languages.get(mode_manager.config["tgwui"]["tgwui_language"])
+ if not string:
+ string = random_sentence()
+
+ if mode_manager.debug_tgwui:
+ print_func(f"Preview text: {string}", message_type="debug", debug_type="voice_preview")
+
+ generate_response, status_message = send_and_generate(
+ string,
+ mode_manager.config["tgwui"]["tgwui_character_voice"],
+ mode_manager.config["tgwui"]["tgwui_rvc_char_voice"],
+ mode_manager.config["tgwui"]["tgwui_rvc_char_pitch"],
+ mode_manager.config["tgwui"]["tgwui_narrator_voice"],
+ mode_manager.config["tgwui"]["tgwui_rvc_narr_voice"],
+ mode_manager.config["tgwui"]["tgwui_rvc_narr_pitch"],
+ False, "character",
+ mode_manager.config["tgwui"]["tgwui_repetitionpenalty_set"],
+ language_code, "standard",
+ mode_manager.config["tgwui"]["tgwui_generationspeed_set"],
+ mode_manager.config["tgwui"]["tgwui_pitch_set"],
+ False, 0.8, "previewvoice",
+ mode_manager.config["tgwui"]["tgwui_temperature_set"],
+ False, False, False
+ )
+
+ if status_message == "TTS Audio Generated":
+ autoplay = "autoplay" if mode_manager.config["tgwui"]["tgwui_autoplay_tts"] else ""
+ return f''
+ else:
+ return f"[{mode_manager.branding}Server] Audio generation failed. Status code:\n{status_message}"
+
+img_pattern = r']*src\s*=\s*["\'][^"\'>]+["\'][^>]*>'
+
+def tgwui_extract_and_remove_images(text):
+ """Extracts and removes image tags from text for clean TTS processing"""
+ debug_func_entry()
+ img_matches = re.findall(img_pattern, text)
+ img_info = "\n".join(img_matches)
+ cleaned_text = re.sub(img_pattern, '', text)
+
+ if mode_manager.debug_tgwui:
+ print_func(f"Found {len(img_matches)} images in text", message_type="debug", debug_type="extract_images")
+
+ return cleaned_text, img_info
+
+def tgwui_reinsert_images(text, img_info):
+ """Reinserts the previously extracted image data back into the text"""
+ debug_func_entry()
+ if img_info: # Check if there are images to reinsert
+ text += f"\n\n{img_info}"
+ return text
+
+################################################################
+# TGWUI # Used to generate a preview voice sample within TGWUI #
+################################################################
+def random_sentence():
+ """Returns a random sentence from Harvard sentences file for voice preview"""
+ debug_func_entry()
+ with open(this_dir / "harvard_sentences.txt") as f:
+ return random.choice(list(f)).rstrip()
+
+###################################################################
+# TGWUI # Used to inform TGWUI that TTS is disabled/not activated #
+###################################################################
+def state_modifier(state):
+ """Modifies TGWUI state to disable streaming when TTS is active"""
+ debug_func_entry()
+ if not mode_manager.config["tgwui"]["tgwui_activate_tts"]:
+ return state
+
+ if mode_manager.debug_tgwui:
+ print_func("Disabling streaming for TTS", message_type="debug", debug_type="state_modifier")
+
+ state["stream"] = False
+ return state
+
+###################################################################
+# TGWUI # Sends message to TGWUI interface during TTS generation #
+###################################################################
+def input_modifier(string, state):
+ """Updates TGWUI interface message during TTS processing"""
+ debug_func_entry()
+ if not mode_manager.config["tgwui"]["tgwui_activate_tts"]:
+ return string
+
+ if mode_manager.debug_tgwui:
+ print_func("Setting processing message for TTS", message_type="debug", debug_type="input_modifier")
+
+ shared.processing_message = "*Is recording a voice message...*"
+ return string
+
+########################################################################
+# TGWUI # Used to delete historic TTS audios from TGWUI chat interface #
+########################################################################
+def remove_tts_from_history(history):
+ '''Removes audio from the chat with TGWUI's chat interface'''
+ debug_func_entry()
+ for i, entry in enumerate(history["internal"]):
+ history["visible"][i] = [history["visible"][i][0], entry[1]]
+ return history
+
+def toggle_text_in_history(history):
+ '''Hides text from the chat with TGWUI's chat interface'''
+ debug_func_entry()
+ for i, entry in enumerate(history["visible"]):
+ visible_reply = entry[1]
+ if visible_reply.startswith("\n\n{reply}",
+ ]
+ else:
+ history["visible"][i] = [
+ history["visible"][i][0],
+ f"{visible_reply.split('')[0]}",
+ ]
+ return history
+
+def history_modifier(history):
+ '''Controls/Enables autoplay of TTS in TGWUI interface'''
+ debug_func_entry()
+ # Remove autoplay from the last reply
+ if len(history["internal"]) > 0:
+ history["visible"][-1] = [
+ history["visible"][-1][0],
+ history["visible"][-1][1].replace("controls autoplay>", "controls>"),
+ ]
+ return history
+
+def tgwui_update_alltalk_protocol(value_sent):
+ """Updates the server protocol setting"""
+ debug_func_entry()
+ if mode_manager.debug_tgwui:
+ print_func(f"Updating server protocol to: {value_sent}",
+ message_type="debug", debug_type="update_protocol")
+ mode_manager.server["protocol"] = value_sent
+
+def tgwui_update_alltalk_ip_port(value_sent):
+ """Updates the server address settings"""
+ debug_func_entry()
+ if mode_manager.debug_tgwui:
+ print_func(f"Updating server address to: {value_sent}",
+ message_type="debug", debug_type="update_ip_port")
+ mode_manager.server["address"] = value_sent
+
+def tgwui_handle_ttsmodel_dropdown_change(model_name):
+ """Handles model changes from Gradio dropdown, with debounce protection"""
+ debug_func_entry()
+ if not getattr(tgwui_handle_ttsmodel_dropdown_change, "skip_reload", False):
+ if mode_manager.debug_tgwui:
+ print_func(f"Switching model to: {model_name}", message_type="debug", debug_type="model_change")
+ send_reload_request(model_name)
+ else:
+ if mode_manager.debug_tgwui:
+ print_func("Skipping model reload (debounced)", message_type="debug", debug_type="model_change")
+ tgwui_handle_ttsmodel_dropdown_change.skip_reload = False
+
+def tgwui_update_dropdowns():
+ """Updates all Gradio dropdowns with current server settings"""
+ debug_func_entry()
+ global at_settings
+
+ if mode_manager.debug_tgwui:
+ print_func("Refreshing server settings", message_type="debug", debug_type="update_dropdowns")
+
+ at_settings = get_alltalk_settings()
+
+ if mode_manager.debug_tgwui:
+ print_func(f"Retrieved {len(at_settings.voices)} voices", message_type="debug", debug_type="update_dropdowns")
+ print_func(f"Retrieved {len(at_settings.rvcvoices)} RVC voices", message_type="debug", debug_type="update_dropdowns")
+ print_func(f"Current model: {at_settings.current_model_loaded}", message_type="debug", debug_type="update_dropdowns")
+
+ current_voices = at_settings.voices
+ rvccurrent_voices = at_settings.rvcvoices
+ rvccurrent_character_voice = mode_manager.config["tgwui"]["tgwui_rvc_char_voice"]
+ rvccurrent_character_pitch = mode_manager.config["tgwui"]["tgwui_rvc_char_pitch"]
+ rvccurrent_narrator_voice = mode_manager.config["tgwui"]["tgwui_rvc_char_voice"]
+ rvccurrent_narrator_pitch = mode_manager.config["tgwui"]["tgwui_rvc_char_pitch"]
+ current_models_available = sorted(at_settings.models_available)
+ current_model_loaded = at_settings.current_model_loaded
+ current_character_voice = mode_manager.config["tgwui"]["tgwui_character_voice"]
+ current_narrator_voice = mode_manager.config["tgwui"]["tgwui_narrator_voice"]
+
+ # Capability checks
+ current_lowvram_capable = at_settings.lowvram_capable
+ current_lowvram_enabled = at_settings.lowvram_enabled
+ current_temperature_capable = at_settings.temperature_capable
+ current_repetitionpenalty_capable = at_settings.repetitionpenalty_capable
+ current_generationspeed_capable = at_settings.generationspeed_capable
+ current_pitch_capable = at_settings.pitch_capable
+ current_deepspeed_capable = at_settings.deepspeed_capable
+ current_deepspeed_enabled = at_settings.deepspeed_enabled
+ current_non_quoted_text_is = mode_manager.config["tgwui"]["tgwui_non_quoted_text_is"]
+ current_languages_capable = at_settings.languages_capable
+
+ # Set appropriate labels based on capabilities
+ language_label = "Languages" if at_settings.languages_capable else "Model not multi language"
+
+ # Update voice selections if needed
+ if current_character_voice not in current_voices:
+ if mode_manager.debug_tgwui:
+ print_func(f"Character voice {current_character_voice} not found, resetting",
+ message_type="debug", debug_type="update_dropdowns")
+ current_character_voice = current_voices[0] if current_voices else ""
+ mode_manager.config["tgwui"]["tgwui_character_voice"] = current_character_voice
+
+ if current_narrator_voice not in current_voices:
+ if mode_manager.debug_tgwui:
+ print_func(f"Narrator voice {current_narrator_voice} not found, resetting",
+ message_type="debug", debug_type="update_dropdowns")
+ current_narrator_voice = current_voices[0] if current_voices else ""
+ mode_manager.config["tgwui"]["tgwui_narrator_voice"] = current_narrator_voice
+
+ # Update RVC voice selections if needed
+ if rvccurrent_character_voice not in rvccurrent_voices:
+ if mode_manager.debug_tgwui:
+ print_func(f"RVC character voice {rvccurrent_character_voice} not found, resetting",
+ message_type="debug", debug_type="update_dropdowns")
+ rvccurrent_character_voice = rvccurrent_voices[0] if rvccurrent_voices else ""
+ mode_manager.config["tgwui"]["tgwui_rvc_char_voice"] = rvccurrent_character_voice
+
+ if rvccurrent_narrator_voice not in rvccurrent_voices:
+ if mode_manager.debug_tgwui:
+ print_func(f"RVC narrator voice {rvccurrent_narrator_voice} not found, resetting",
+ message_type="debug", debug_type="update_dropdowns")
+ rvccurrent_narrator_voice = rvccurrent_voices[0] if rvccurrent_voices else ""
+ mode_manager.config["tgwui"]["tgwui_rvc_char_voice"] = rvccurrent_narrator_voice
+
+ rvccurrent_character_pitch = rvccurrent_character_pitch if rvccurrent_character_pitch else 0
+ rvccurrent_narrator_pitch = rvccurrent_narrator_pitch if rvccurrent_narrator_pitch else 0
+
+ # Prevent model reload during update
+ tgwui_handle_ttsmodel_dropdown_change.skip_reload = True
+
+ # Return updated Gradio components
+ return_values = [
+ gr.Checkbox(interactive=current_lowvram_capable, value=current_lowvram_enabled),
+ gr.Checkbox(interactive=current_deepspeed_capable, value=current_deepspeed_enabled),
+ gr.Dropdown(choices=current_voices, value=current_character_voice),
+ gr.Dropdown(choices=rvccurrent_voices, value=rvccurrent_character_voice, interactive=True),
+ gr.Slider(value=rvccurrent_character_pitch),
+ gr.Dropdown(choices=current_voices, value=current_narrator_voice),
+ gr.Dropdown(choices=rvccurrent_voices, value=rvccurrent_narrator_voice, interactive=True),
+ gr.Slider(value=rvccurrent_narrator_pitch),
+ gr.Dropdown(choices=current_models_available, value=current_model_loaded),
+ gr.Dropdown(interactive=current_temperature_capable),
+ gr.Dropdown(interactive=current_repetitionpenalty_capable),
+ gr.Dropdown(interactive=current_languages_capable, label=language_label),
+ gr.Dropdown(interactive=current_generationspeed_capable),
+ gr.Dropdown(interactive=current_pitch_capable),
+ gr.Dropdown(value=current_non_quoted_text_is),
+ ]
+
+ def reset_skip_reload():
+ tgwui_handle_ttsmodel_dropdown_change.skip_reload = False
+
+ threading.Timer(0.5, reset_skip_reload).start()
+
+ return return_values
+
+def ui():
+ """Creates the TGWUI Gradio interface"""
+ global alltalk_settings
+
+ if mode_manager.debug_tgwui:
+ print_func("Initializing TGWUI interface", message_type="debug", debug_type="ui")
+
+ alltalk_settings = get_alltalk_settings()
+
+ with gr.Accordion(mode_manager.branding + " TTS (Text-gen-webui Remote)"):
+ tgwui_available_voices_gr = alltalk_settings.voices
+ tgwui_rvc_available_voices_gr = alltalk_settings.rvcvoices
+
+ # Activate alltalk_tts, Enable autoplay, Show text
+ with gr.Row():
+ tgwui_activate_tts_gr = gr.Checkbox(
+ value=mode_manager.config["tgwui"]["tgwui_activate_tts"],
+ label="Enable TGWUI TTS"
+ )
+ tgwui_autoplay_gr = gr.Checkbox(
+ value=mode_manager.config["tgwui"]["tgwui_autoplay_tts"],
+ label="Autoplay TTS Generated"
+ )
+ tgwui_show_text_gr = gr.Checkbox(
+ value=mode_manager.config["tgwui"]["tgwui_show_text"],
+ label="Show Text in chat"
+ )
+
+ # Low vram enable, Deepspeed enable, Link
+ with gr.Row():
+ tgwui_lowvram_enabled_gr = gr.Checkbox(
+ value=alltalk_settings.lowvram_enabled if alltalk_settings.lowvram_capable else False,
+ label="Enable Low VRAM Mode",
+ interactive=alltalk_settings.lowvram_capable
+ )
+ tgwui_lowvram_enabled_play_gr = gr.HTML(visible=False)
+
+ tgwui_deepspeed_enabled_gr = gr.Checkbox(
+ value=mode_manager.config["tgwui"]["tgwui_deepspeed_enabled"],
+ label="Enable DeepSpeed",
+ interactive=alltalk_settings.deepspeed_capable
+ )
+ tgwui_deepspeed_enabled_play_gr = gr.HTML(visible=False)
+
+ tgwui_empty_space_gr = gr.HTML(
+ f"
- To manage AllTalk sustainably, I prioritize support requests based on their overall impact and the number of users affected. I encourage you to utilize the comprehensive documentation and engage with the AllTalk community discussion area. These resources often provide immediate answers and foster a supportive user network.
- Should your inquiry extend beyond the documentation, especially if it concerns a bug or feature request, I assure you I’ll offer my best support as my schedule permits. However, please be prepared for varying response times, reflective of the personal dedication I bring to AllTalk. Your understanding and patience in this regard are greatly appreciated.
- It's important to note that **I am not** the developer of any TTS models utilized by AllTalk, nor do I claim to be an expert on them, including understanding all their nuances, issues, and quirks. For specific TTS model concerns, I’ve provided links to the original developers in each TTS engines "Engine Information" section for direct assistance/research.
- Thank you for your continued support and understanding.
-
- ### 💖 Showing Your Support
- > If AllTalk has been helpful to you, consider showing your support through a donation on my **[Ko-fi page](https://ko-fi.com/erew123)**.
- Your support is greatly appreciated and helps ensure the continued development and improvement of AllTalk.
- """
- gr.Markdown(about)
\ No newline at end of file
diff --git a/system/gradio_pages/alltalk_documentation.py b/system/gradio_pages/alltalk_documentation.py
deleted file mode 100644
index 0bca0b1..0000000
--- a/system/gradio_pages/alltalk_documentation.py
+++ /dev/null
@@ -1,1212 +0,0 @@
-import gradio as gr
-
-def alltalk_documentation():
- low_vram = """
- The Low VRAM option is a crucial feature designed to enhance performance under constrained (VRAM) conditions, as TTS models **can** require 2GB-3GB of VRAM to run effectively. This feature strategically manages the relocation of the Text-to-Speech (TTS) model between your system's Random Access Memory (RAM) and VRAM, moving it between the two on the fly. Obviously, this is very useful for people who have smaller graphics cards or people who's LLM has filled their VRAM, but you want to keep up performance.
-
- When you don't have enough VRAM free after loading your LLM model into your VRAM (Normal Mode example below), you can see that with so little working space, your GPU will have to swap in and out bits of the TTS model, which causes horrible slowdown.
-
- Note An Nvidia Graphics card is required for the LowVRAM option to work, as you will just be using system RAM otherwise.
-
- ### How It Works
- The Low VRAM mode intelligently relocates of the entire TTS model and stores the TTS model in your system RAM when not being used. When the TTS engine requires VRAM for processing, the entire model seamlessly moves into VRAM, causing your LLM to unload/displace some layers, ensuring optimal performance of the TTS engine.
-
- Post-TTS processing, the model moves back to system RAM, freeing up VRAM space for your Language Model (LLM) to load back in the missing layers. This adds about 1-2 seconds to both text generation by the LLM and the TTS engine.
-
- By transferring the entire model between RAM and VRAM, the Low VRAM option avoids model fragmentation, ensuring the TTS model remains cohesive and has all the working space it needs in your GPU, without having to just work on small bits of the TTS model at a time (which causes terrible slow down).
-
- This creates a TTS generation performance Boost for Low VRAM Users and is particularly beneficial for users with less than 2GB of free VRAM after loading their LLM, delivering a substantial 5-10x improvement in TTS generation speed.
- """
-
- silly_tavern_support = """
- #### SillyTavern/AllTalk extension version
- If you intend to use AllTalk V2 with SillyTavern, you will need to update the SillyTavern Extension from the `alltalk_tts\system\SillyTavern Extension\` folder. Instructions are included within the folder.
-
- #### Important note for Text-generation-webui users
- You **HAVE** to disable **Enable TTS** within the Text-generation-webui AllTalk interface, otherwise Text-generation-webui will also generate TTS due to the way it sends out text. You can do this each time you start up Text-generation-webui or set it in the start-up settings at the top of this page.
-
- ### 🏰 Quick Tips
- > Only change DeepSpeed, Low VRAM or Model one at a time. Wait for it to say Ready before changing something else.
- > You can permanently change the AllTalk startup settings or DeepSpeed, Low VRAM and Model at the top of this page.
- > Different AI models use quotes and asterisks differently, so you may need to change "Text not inside" depending on model.
- > Add new voice samples to the voices folder. You can Finetune a model to make it sound even closer to the original sample.
- > DeepSpeed will improve processing time to TTS to be 2-3x faster.
- > Low VRAM can be very beneficial if you don't have much memory left after loading your LLM.
-
- #### 🏰 TTS Generation Methods in SilllyTavern
- You have 2 types of audio generation options, Streaming and Standard.
-
- The Streaming Audio Generation method is designed for speed and is best suited for situations where you just want quick audio playback. This method, however, is limited to using just one voice per TTS generation request. This means a limitation of the Streaming method is the inability to utilize the AllTalk narrator function, making it a straightforward but less nuanced option.
-
- On the other hand, the Standard Audio Generation method provides a richer auditory experience. It's slightly slower than the Streaming method but compensates for this with its ability to split text into multiple voices. This functionality is particularly useful in scenarios where differentiating between character dialogues and narration can enhance the storytelling and delivery. The inclusion of the AllTalk narrator functionality in the Standard method allows for a more layered and immersive experience, making it ideal for content where depth and variety in voice narration add significant value.
-
- In summary, the choice between Streaming and Standard methods in AllTalk TTS depends on what you want. Streaming is great for quick and simple audio generation, while Standard is preferable for a more dynamic and engaging audio experience.
-
- **AllTalk TTS Generation Method:**
- > Select between Standard and Streaming Audio Generation methods.
- > This setting impacts the AllTalk narrator functionality.
-
- **Language Selection:**
- > Select your preferred TTS generation language from the "Language" dropdown.
-
- **Model Switching:**
- > Switch between different TTS models like API TTS, API Local, XTTSv2 Local, and optionally XTTSv2 FT if you have a finetuned model available.
- > Fine-tuned model availability (XTTSv2 FT) will only show when a finetuned model is detected by AllTalk.
- > See TTS Models/Methods for more information (though most people will want to stick with XTTSv2 Local).
-
- **DeepSpeed and Low VRAM Options:**
- > Optimize performance with DeepSpeed and Low VRAM settings.
- > DeepSpeed can offer a 2-3x performance boost on TTS generation. (Requires installation)
- > See the relevant sections in this documentation for details.
-
- Changing model or DeepSpeed or Low VRAM **each** take about 15 seconds so you should only change one at a time and wait for `Ready` before changing the next setting. To set these options long term you can apply the settings at the top of this page.
-
- #### 🏰 AllTalk Narrator
- Only available on the Standard Audo Generation method.
-
- **Narrator Voice Selection:**
- > Allows users to choose different narrator voices.
- > Access via the "Narrator Voice" dropdown.
-
- **AllTalk Narrator:**
- > Toggle the AllTalk narrator feature.
- > Access via "AT Narrator" dropdown with Enabled/Disabled options.
-
- **Text Outside Asterisks Handling:**
- > Choose how text outside asterisks is interpreted (as Narrator or Character voice).
- > Managed via the "Text Not Inside * or "" dropdown.
- > Note, only available when the AllTalk Narrator is enabled.
-
- #### 🏰 Usage Notes:
- > On startup of SillyTavern, it will pull your current settings from AllTalk (Current model, DeepSpeed status, Low VRAM status and Finetuned model availability).
- > Enabling the narrator automatically unchecks certain checkboxes related to text handling.
- > Changes in model or settings might trigger multiple requests to the server; patience is advised.
-
- #### 🏰 Troubleshooting:
- > If experiencing issues, use the Reload button in SillyTavern's TTS extension to reinitialize the connection to AllTalk and check if AllTalk is started correctly.
- """
-
- updating_alltalk = """
- ### 🟪 Updating
-
- Maintaining the latest version of your setup ensures access to new features and improvements. Below are the steps to update your installation, whether you're using Text-Generation-webui or running as a Standalone Application.
-
-
- UPDATING - Text-Generation-webui
-
- The update process closely mirrors the installation steps. Follow these to ensure your setup remains current:
-
- **Open a Command Prompt/Terminal**
- > Navigate to your Text-Generation-webui folder with:
- > `cd text-generation-webui`
-
- **Start the Python Environment**
- > Activate the Python environment tailored for your operating system. Use the appropriate command from below based on your OS:
- > **Windows**: `cmd_windows.bat`
- > **Linux**: `./cmd_linux.sh`
- > **macOS**: `cmd_macos.sh`
- > **WSL (Windows Subsystem for Linux)**: `cmd_wsl.bat`
-
- > If you're unfamiliar with Python environments and wish to learn more, consider reviewing **Understanding Python Environments Simplified** in the Help section.
-
- **Navigate to the AllTalk TTS Folder**
- > Move into your extensions and then the alltalk_tts directory:
- > `cd extensions/alltalk_tts`
-
- **Update the Repository**
- > Fetch the latest updates from the repository with:
- > `git pull`
-
- **Install Updated Requirements**
- > Depending on your machine's OS, install the required dependencies using pip:
- > **Windows Machines**:
- > `pip install -r system\\requirements\\requirements_textgen.txt`
- > **Linux/Mac**:
- > `pip install -r system/requirements/requirements_textgen.txt`
-
- **DeepSpeed Requirements**
- > If Text-gen-webui is using a new version of PyTorch, you **may** need to uninstall and update your DeepSpeed version.
- > Use AllTalks diagnostics or start-up menu to identify your version of PyTorch.
-
-
-
-
- UPDATING - Standalone Application
-
-
- If you installed from a ZIP file, you cannot use a `git pull` to update, as noted in the Quick Setup instructions.
-
- For Standalone Application users, here's how to update your setup:
-
- 1. **Open a Command Prompt/Terminal**:
- > Navigate to your AllTalk folder with
- > `cd alltalk_tts`
-
- 2. **Access the Python Environment**:
- > In a command prompt or terminal window, navigate to your `alltalk_tts` directory and start the Python environment:
- > **Windows**
- > `start_environment.bat`
- > **Linux/macOS**
- > `./start_environment.sh`
-
- > If you're unfamiliar with Python environments and wish to learn more, consider reviewing **Understanding Python Environments Simplified** in the Help section.
-
- 2. **Pull the Latest Updates**:
- > Retrieve the latest changes from the repository with:
- > `git pull`
-
- 3. **Install Updated Requirements**:
- > Depending on your machine's OS, install the required dependencies using pip:
- > **Windows Machines**
- > `pip install -r system\\requirements\\requirements_standalone.txt`
- > **Linux/Mac**
- > `pip install -r system/requirements/requirements_standalone.txt`
-
-
-
- ### 🟪 Resolving Update Issues
-
- If you encounter problems during or after an update, following these steps can help resolve the issue by refreshing your installation while preserving your data:
-
-
- RESOLVING - Updates
-
- The process involves renaming your existing `alltalk_tts` directory, setting up a fresh instance, and then migrating your data:
-
- 1. **Rename Existing Directory**:
- > First, rename your current `alltalk_tts` folder to keep it safe e.g. `alltalk_tts.old`. This preserves any existing data.
-
- 2. **Follow the Quick Setup instructions**:
- > You will now follow the **Quick Setup** instructions, performing the `git clone https://github.com/erew123/alltalk_tts` to pull down a new copy of AllTalk and install the requirements.
-
- > If you're not familiar with Python environments, see **Understanding Python Environments Simplified** in the Help section for more info.
-
- 3. **Migrate Your Data**:
- > **Before** starting the AllTalk, transfer the `models`, `voices`, `outputs` folders and also `confignew.json` from `alltalk_tts.old` to the new `alltalk_tts` directory. This action preserves your voice history and prevents the need to re-download the model.
-
- 4) **Launch AllTalk**
- > You're now ready to launch AllTalk and check it works correctly.
-
- 6. **Final Step**:
- > Once you've verified that everything is working as expected and you're satisfied with the setup, feel free to delete the `alltalk_tts.old` directory to free up space.
-
-
- """
-
- narrator_guides = """
- Messages intended for the Narrator should be enclosed in asterisks `*` and those for the character inside quotation marks `"`. However, AI systems often deviate from these rules, resulting in text that is neither in quotes nor asterisks. Sometimes, text may appear with only a single asterisk, and AI models may vary their formatting mid-conversation. For example, they might use asterisks initially and then switch to unmarked text. A properly formatted line should look like this:
-
- `"`Hey! I'm so excited to finally meet you. I've heard so many great things about you and I'm eager to pick your brain about computers.`"` `*`She walked across the room and picked up her cup of coffee`*`
-
- Most narrator/character systems switch voices upon encountering an asterisk or quotation marks, which is somewhat effective. AllTalk has undergone several revisions in its sentence splitting and identification methods. While some irregularities and AI deviations in message formatting are inevitable, any line beginning or ending with an asterisk should now be recognized as Narrator dialogue. Lines enclosed in double quotes are identified as Character dialogue. For any other text, you can choose how AllTalk handles it: whether it should be interpreted as Character or Narrator dialogue (most AI systems tend to lean more towards one format when generating text not enclosed in quotes or asterisks).
-
- ### 🟦 Narrator Types of Text
-
- The TTS engine identifies and processes three types of text when the Narrator function is `Enabled` or `Enabled (Silent)`:
-
- > **Narrated Text**: Text enclosed in asterisks `*Narrated text*`.
- > **Character Text**: Text enclosed in double quotes `"Character text"`.
- > **Text-Not-Inside**: Any text not enclosed in asterisks or double quotes. The handling of this text is customizable.
-
- ### 🟦 Settings for Narrated Text
-
- You can control how narrated text is handled with the following settings:
-
- > **Enabled**: Narrated text will be spoken by the TTS engine using the narrator voice.
- > **Enabled (Silent)**: Narrated text is recognized but not spoken. This is useful for adding silent annotations or actions within the text.
- > **Disabled**: Narrated text will be spoken using the character voice instead of the narrator voice. The narrator engine is turned off, and the Text-Not-Inside setting does not apply.
-
- Please note, if you set `Enabled` or `Enabled (silent)` as the APi defaults, then all text will go into the narrator function unless `disabled` is sent as part of the TTS generation request.
-
- ### 🟦 Settings for Text-Not-Inside
-
- You can customize how text-not-inside is handled with these options:
-
- > **"Character"**: Text-not-inside is treated as character dialogue and will be spoken.
- > ***Narrator***: Text-not-inside is treated as narration and will be spoken.
- > **Silent**: Text-not-inside is recognized but not spoken.
-
- ### 🟦 Examples and Variations
-
- Here are some examples to illustrate how different settings affect the text processing:
-
- > **Example 1**:
- **Narrator Enabled, Text-Not-Inside as Character**
- > ***Narrated text*** will be spoken as narration.
- > **"Character text"** will be spoken as character dialogue.
- > **Any other** text will be spoken as character dialogue.
-
- > **Example 2**:
- **Narrator Disabled**
- > ***Narrated text*** will be spoken using the character voice.
- > **"Character text"** will be spoken as character dialogue.
- > **Any other** text will be spoken as character dialogue.
-
- > **Example 3**:
- **Narrator Enabled (Silent), Text-Not-Inside as Silent**
- > ***Narrated text*** will be recognized but not spoken.
- > **"Character text"** will be spoken as character dialogue.
- > **Any other** text will be recognized but not spoken.
-
- ### 🟦 Potential Confusions and Clarifications
-
- **Silent Options Impact**
-
- > **Narrator Enabled (Silent)**: This setting allows for silent annotations within your text. Users can add notes or actions in the narrative without them being spoken.
- > **Text-Not-Inside as Silent**: This setting enables users to include text that is recognized by the TTS engine but remains unspoken, useful for adding context or instructions within the script that are not meant to be vocalized.
-
- **Combining Settings**
-
- > **Full Silence**: If both Narrator is set to `Enabled (Silent)` and Text-Not-Inside is set to `Silent`, **only character text** in quotes will be spoken.
- > **Partial Silence**: You can configure the settings to have only certain parts of the text spoken while others are silent. For example, you might want all narration silent and only character dialogue spoken, or vice versa.
-
- ### 🟦 Short Story Example
-
- >*Once upon a time in a small village, there was a young girl named Lily.*
- Lily woke up early every morning.
- "I love exploring the forest near my home."
- * Lily said. One day, she found a mysterious key lying on the ground.*
- "This key looks old and rusty, but it seems to sparkle in the sunlight,"
- She thought. Lily decided she would keep the key safe until she could discover its purpose.
- *Curious and excited, she decided to find out what the key opened.*
-
- ### Short Story Explanation
-
- **Narrated Text** (Asterisks surrounding the text):
- > *Once upon a time in a small village, there was a young girl named Lily.*
- > * Lily said. One day, she found a mysterious key lying on the ground.*
- > *Curious and excited, she decided to find out what the key opened.*
- > These lines are enclosed in asterisks and represent the narration. If Narrator was set to `Enabled (Silent)` these lines would not be spoken.
-
- **Character Text** (Double Quotes surrounding the text):
- > "I love exploring the forest near my home."
- > "This key looks old and rusty, but it seems to sparkle in the sunlight."
- > These lines are enclosed in double quotes and represent the character’s spoken dialogue or thoughts.
-
- **Text-Not-Inside** (Neither Asterisks nor Quotes):
- > Lily woke up early every morning.
- > She thought. Lily decided she would keep the key safe until she could discover its purpose.
- > These lines are not enclosed in asterisks or quotes and will be handled according to the `Text-Not-Inside` settings. If set to `Text-not-inside` > `Silent` these lines would not be spoken.
-
- """
-
- rvc_guides = """
- RVC (Retrieval-based Voice Conversion) enhances TTS by replicating voice characteristics for characters or narrators, adding depth to synthesized speech. It functions as a TTS-to-TTS pipeline and can be used with any TTS engine/model. However, the closer the original TTS generation is to the voice you want to use with RVC, the better the result will be. Therefore, it is recommended to use a voice cloning TTS engine like Coqui XTTS with voice samples for optimal performance.
-
- When you first enable RVC on the `Global Settings > RVC Settings` tab and click the `Update RVC Settings` button, AllTalk will create the necessary folders and download any of the missing model files required for RVC to work.
-
- ### 🟩 Voice Model Files
-
- Voice models should be stored in the `/models/rvc_voices/{subfolder}` directory in their own subfolder. Typically, a voice model includes a PTH file and potentially an index file. If an index file is present, AllTalk will automatically select and use it. If multiple index files are found, none will be used, and a message will be output to the console. You can find places on the internet to download pre-generated RVC voice models e.g. https://voice-models.com/ and huggingface etc.
-
- ### 🟩 Purpose of the Index File
-
- The index file helps improve the quality of the generated audio by providing a reference during the conversion process. The FAISS index enables faster and more accurate retrieval of voice characteristics, leading to more natural and high-quality voice synthesis. The training data size setting determines how much of the index is used, impacting both quality and computation time. By indexing a larger number of data points, the conversion process can produce audio that closely matches the desired voice characteristics.
-
- ### 🟩 RVC Settings Page
- """
- rvc_guides2 = """
- ### Default Character Voice Model
- > This setting allows you to select the voice model used for character conversion. If "Disabled" is selected, RVC will not be applied to character voices. This option is used only if RVC is enabled and no other voice is specified in the API request. Selecting a specific model will apply RVC to all character dialogues, potentially making them sound more natural and lifelike.
-
- ### Default Narrator Voice Model
- > This setting allows you to select the voice model used for narrator conversion. If "Disabled" is selected, RVC will not be applied to the narrator voice. This option is used only if RVC is enabled and no other voice is specified in the API request. Applying RVC to the narrator can enhance the overall narrative experience by providing a consistent and engaging voice.
-
- ### Index Influence Ratio
- > Sets the influence exerted by the index file on the final output. A higher value increases the impact of the index, potentially enhancing detail but also increasing the risk of artifacts. Fine-tuning this setting helps achieve the desired balance between audio detail and artifact prevention.
-
- ### Pitch
- > This slider sets the pitch of the audio output. Increasing the value raises the pitch, while decreasing the value lowers it. Adjusting the pitch can help match the voice to specific character traits or emotional states, enhancing the overall storytelling experience.
-
- ### Volume Envelope
- > This setting substitutes or blends with the volume envelope of the output. A ratio closer to 1 means the output envelope is more heavily employed. This can help maintain the natural dynamics of the voice, making it sound more realistic and less robotic.
-
- ### Protect Voiceless Consonants/Breath Sounds
- > Prevents artifacts in voiceless consonants and breath sounds. Higher values (up to 0.5) provide stronger protection but might affect indexing. This setting helps maintain the clarity and intelligibility of speech, especially in parts of the audio where voiceless consonants and breath sounds are present.
-
- ### AutoTune
- > Enables or disables auto-tune for the generated audio. Recommended for singing conversions to ensure the output remains in tune. Auto-tune can correct pitch discrepancies and produce a more polished and harmonious sound.
- """
- rvc_guides3 = """
- ### Filter Radius
- > If the number is greater than or equal to three, employing median filtering on the collected tone results has the potential to decrease respiration. This setting helps reduce noise and unwanted sounds in the audio, leading to cleaner and more professional-sounding output.
-
- ### Training Data Size (AllTalk Specific)
- > Determines the number of training data points used to train the FAISS index. Increasing the size may improve the quality of the output but can also increase computation time. Different index files have different sizes. This setting limits the maximum amount of the index used. For example, if an index file has 70,000 points and you set the limit to 50,000, only 50,000 points will be used. A higher training data size generally results in better audio quality because more data points contribute to a more accurate voice conversion.
-
- ### Embedder Model
- > Select between different models for learning speaker embedding. "hubert" and "contentvec" are the available options.
- **hubert**: A model that focuses on capturing the phonetic and linguistic content of the voice, which is useful for a wide range of voice conversion tasks.
- **contentvec**: A model that captures more detailed voice characteristics and nuances, potentially offering higher fidelity in voice conversion but may require more computational resources.
-
- ### Split Audio
- > Splits the audio into chunks for inference to obtain better results in some cases. This can improve the quality of conversion, especially for longer audio inputs, by ensuring more consistent and accurate processing across the entire length of the audio.
-
- ### Pitch Extraction Algorithm
- > Choose the algorithm used for extracting the pitch (F0) during audio conversion. The available options include:
- **crepe**: Provides high accuracy in pitch detection and is robust against various types of noise.
- **crepe-tiny**: A smaller, faster version of crepe with slightly reduced accuracy.
- **dio**: A fast pitch extraction algorithm that is less accurate than crepe but suitable for real-time applications.
- **fcpe**: Focuses on precise pitch extraction, often used for high-fidelity voice conversion.
- **harvest**: Known for producing smooth and natural pitch contours, suitable for music and singing voice conversion.
- **hybrid[rmvpe+fcpe]**: Combines the strengths of rmvpe and fcpe to achieve a balance of accuracy and performance.
- **pm**: A robust algorithm that works well in various conditions, offering a balance of speed and accuracy.
- **rmvpe**: Recommended for most cases due to its balance of accuracy and performance, especially in TTS applications.
- """
-
-
-
- deepspeed_guides = """
- ### 🔵🟢 DeepSpeed Installation Options
- **DeepSpeed requires an Nvidia Graphics card**
-
- ---
- ## Documentation yet to be updated. In a Nutshell though....
- > AllTalk **Standalone** setups **(Windows & Linux)** will install DeepSpeed **automatically**. You can stop reading here and you dont have to do anything.
-
- > **Windows** users, please see instructions in `atsetup.bat`
-
- > **Linux users**, please look here https://github.com/erew123/alltalk_tts/releases/tag/DeepSpeed-14.2-Linux
- ---
- ### Legacy manual build of DeepSpeed Instructions
- #### 🔵 Linux Installation
- DeepSpeed requires access to the **Nvidia CUDA Development Toolkit** to compile on a Linux system. It's important to note that this toolkit is distinct and unrealted to your graphics card driver or the CUDA version the Python environment uses.
-
-
- Linux DeepSpeed - Text-generation-webui
-
- ### DeepSpeed Installation for Text generation webUI
-
- 1. **Nvidia CUDA Development Toolkit Installation**:
- - The toolkit is crucial for DeepSpeed to compile/build for your version of Linux and requires around 3GB's of disk space.
- - Install using your package manager **(Recommended)** e.g. **CUDA Toolkit 11.8** or download directly from [Nvidia CUDA Toolkit Archive](https://developer.nvidia.com/cuda-toolkit-archive) (choose 11.8 or 12.1 for Linux).
-
- 2. **Open a Terminal Console**:
- - After Nvidia CUDA Development Toolkit installation, access your terminal console.
-
- 3. **Install libaio-dev**:
- - Use your Linux distribution's package manager.
-
- - - `sudo apt install libaio-dev` for Debian-based systems
- - `sudo yum install libaio-devel` for RPM-based systems.
-
- 4. **Navigate to Text generation webUI Folder**:
- - Change directory to your Text generation webUI folder with `cd text-generation-webui`.
-
- 5. **Activate Text generation webUI Custom Conda Environment**:
- - Run `./cmd_linux.sh` to start the environment.
-
- > If you're unfamiliar with Python environments and wish to learn more, consider reviewing **Understanding Python Environments Simplified** in the Help section.
-
- 6. **Set `CUDA_HOME` Environment Variable**:
- - DeepSpeed locates the Nvidia toolkit using the `CUDA_HOME` environment variable.
- - You will only set this temporarily as Text generation webUI sets up its own CUDA_HOME environment each time you use `./cmd_linux.sh` or `./start_linux.sh`
-
- 7. **Temporarily Configuring `CUDA_HOME`**:
- - When the Text generation webUI Python environment is active **(step 5)**, set `CUDA_HOME`.
-
- - - `export CUDA_HOME=/usr/local/cuda`
- - `export PATH=${CUDA_HOME}/bin:${PATH}`
- - `export LD_LIBRARY_PATH=${CUDA_HOME}/lib64:$LD_LIBRARY_PATH`
-
- - You can confirm the path is set correctly and working by running the command `nvcc --version` should confirm `Cuda compilation tools, release 11.8.`.
- - Incorrect path settings may lead to errors. If you encounter path issues or receive errors like `[Errno 2] No such file or directory` when you run the next step, confirm the path correctness or adjust as necessary.
-
- 8. **DeepSpeed Installation**:
- - Install DeepSpeed using `pip install deepspeed`.
-
- 9. **Troubleshooting**:
- - Troubleshooting steps for DeepSpeed installation can be located down below.
- - **NOTE**: You **DO NOT** need to set Text-generation-webUI's **--deepspeed** setting for AllTalk to be able to use DeepSpeed. These are two completely separate things and incorrectly setting that on Text-generation-webUI may cause other complications.
-
-
- Linux DeepSpeed - Standalone Installation
-
- ### DeepSpeed Installation for Standalone AllTalk
-
- 1. **Nvidia CUDA Development Toolkit Installation**:
- - The toolkit is crucial for DeepSpeed to compile/build for your version of Linux and requires around 3GB's of disk space.
- - Install using your package manager **(Recommended)** e.g. **CUDA Toolkit 11.8** or download directly from [Nvidia CUDA Toolkit Archive](https://developer.nvidia.com/cuda-toolkit-archive) (choose 11.8 or 12.1 for Linux).
-
- 2. **Open a Terminal Console**:
- - After Nvidia CUDA Development Toolkit installation, access your terminal console.
-
- 3. **Install libaio-dev**:
- - Use your Linux distribution's package manager.
-
- - - `sudo apt install libaio-dev` for Debian-based systems
- - `sudo yum install libaio-devel` for RPM-based systems.
-
- 4. **Navigate to AllTalk TTS Folder**:
- - Change directory to your AllTalk TTS folder with `cd alltalk_tts`.
-
- 5. **Activate AllTalk Custom Conda Environment**:
- - Run `./start_environment.sh` to start the AllTalk Python environment.
- - This command will start the custom Python environment that was installed with `./atsetup.sh`.
-
- > If you're unfamiliar with Python environments and wish to learn more, consider reviewing **Understanding Python Environments Simplified** in the Help section.
-
- 6. **Set `CUDA_HOME` Environment Variable**:
- - The DeepSpeed installation routine locates the Nvidia toolkit using the `CUDA_HOME` environment variable. This can be set temporarily for a session or permanently, depending on other requirements you may have for other Python/System environments.
- - For temporary use, proceed to **step 8**. For a permanent solution, see [Conda's manual on setting environment variables](https://conda.io/projects/conda/en/latest/user-guide/tasks/manage-environments.html#set-env-vars).
-
- 7. **(Optional) Permanent `CUDA_HOME` Setup**:
- - If you choose to set `CUDA_HOME` permanently, follow the instructions in the provided Conda manual link above.
-
- 8. **Configuring `CUDA_HOME`**:
- - When your Python environment is active **(step 5)**, set `CUDA_HOME`.
-
- - - `export CUDA_HOME=/usr/local/cuda`
- - `export PATH=${CUDA_HOME}/bin:${PATH}`
- - `export LD_LIBRARY_PATH=${CUDA_HOME}/lib64:$LD_LIBRARY_PATH`
-
- - You can confirm the path is set correctly and working by running the command `nvcc --version` should confirm `Cuda compilation tools, release 11.8.`.
- - Incorrect path settings may lead to errors. If you encounter path issues or receive errors like `[Errno 2] No such file or directory` when you run the next step, confirm the path correctness or adjust as necessary.
-
- 9. **DeepSpeed Installation**:
- - Install DeepSpeed using `pip install deepspeed`.
-
- 10. **Starting AllTalk TTS WebUI**:
- - Launch the AllTalk TTS interface with `./start_alltalk.sh` and enable DeepSpeed.
-
- ### Troubleshooting
-
- - If setting `CUDA_HOME` results in path duplication errors (e.g., `.../bin/bin/nvcc`), you can correct this by unsetting `CUDA_HOME` with `unset CUDA_HOME` and then adding the correct path to your system's PATH variable.
- - Always verify paths and compatibility with other CUDA-dependent applications to avoid conflicts.
- - If you have multiple versions of the Nvidia CUDA Development Toolkit installed, you will have to specify the version number in step 8 for the CUDA_HOME path.
- - If it became necessary to uninstall DeepSpeed, you can do so by start the Python enviroment and then running `pip uninstall deepspeed`
-
-
-
- #### 🟢 Windows Installation
- You have 2x options for how to setup DeepSpeed on Windows. Pre-compiled wheel files for specific Python, CUDA and Pytorch builds, or manually compiling DeepSpeed.
-
-
- Windows DeepSpeed - Pre-Compiled Wheels (Quick and Easy)
-
- ### DeepSpeed Installation with Pre-compiled Wheels
-
- 1. **Introduction to Pre-compiled Wheels**:
- - The `atsetup.bat` utility simplifies the installation of DeepSpeed by automatically downloading and installing pre-compiled wheel files. These files are tailored for **specific** versions of Python, CUDA, and PyTorch, ensuring compatibility with both the **Standalone Installation** and a standard build of **Text-generation-webui**.
-
- 2. **Manual Installation of Pre-compiled Wheels**:
- - If needed, pre-compiled DeepSpeed wheel files that I have built are available on the [Releases Page](https://github.com/erew123/alltalk_tts/releases). You can manually install or uninstall these wheels using the following commands:
- - Installation: `pip install {deep-speed-wheel-file-name-here}`
- - Uninstallation: `pip uninstall deepspeed`
-
- 3. **Using `atsetup.bat` for Simplified Management**:
- - For those running the Standalone Installation or a standard build of Text-generation-webui, the `atsetup.bat` utility offers the simplest and most efficient way to manage DeepSpeed installations on Windows.
-
-
-
-
- Windows DeepSpeed - Manual Compilation
-
- ### Manual DeepSpeed Wheel Compilation
-
- 1. **Preparation for Manual Compilation**:
- - Manual compilation of DeepSpeed wheels is an advanced process that requires:
- - **1-2 hours** of your time for initial setup and compilation.
- - **6-10GB** of disk space on your computer.
- - A solid technical understanding of Windows environments and Python.
-
- 2. **Understanding Wheel Compatibility**:
- - A compiled DeepSpeed wheel is uniquely tied to the specific versions of Python, PyTorch, and CUDA used during its compilation. If any of these versions are changed, you will need to compile a new DeepSpeed wheel to ensure compatibility.
-
- 3. **Compiling DeepSpee Resources**:
- - Myself and [@S95Sedan](https://github.com/S95Sedan) have worked to simplify the compilation process. [@S95Sedan](https://github.com/S95Sedan) has notably improved the process for later versions of DeepSpeed, ensuring ease of build on Windows.
- - Because [@S95Sedan](https://github.com/S95Sedan) is now maintaining the instructions for compiling DeepSpeed on Windows, please visit [@S95Sedan](https://github.com/S95Sedan)'s [DeepSpeed GitHub page](https://github.com/S95Sedan/Deepspeed-Windows).
-
-
- """
-
- help_with_problems = """
- ### 🆘 Support Requests, Troubleshooting & Feature requests
- I'm thrilled to see the enthusiasm and engagement with AllTalk! Your feedback and questions are invaluable, helping to make this project even better. To ensure everyone gets the help they need efficiently, please consider the following before submitting a support request:
-
- **Consult the Documentation:** A comprehensive guide and FAQ sections (below) are available to help you navigate AllTalk. Many common questions and troubleshooting steps are covered here.
-
- **Search Past Discussions:** Your issue or question might already have been addressed in the discussions area or [closed issues](https://github.com/erew123/alltalk_tts/issues?q=is%3Aissue+is%3Aclosed). Please use the search function to see if there's an existing solution or advice that applies to your situation.
-
- **Bug Reports:** If you've encountered what you believe is a bug, please first check the [Updates & Bug Fixes List](https://github.com/erew123/alltalk_tts/issues/25) to see if it's a known issue or one that's already been resolved. If not, I encourage you to report it by raising a bug report in the [Issues section](https://github.com/erew123/alltalk_tts/issues), providing as much detail as possible to help identify and fix the issue.
-
- **Feature Requests:** The current Feature request list can be [found here](https://github.com/erew123/alltalk_tts/discussions/74). I love hearing your ideas for new features! While I can't promise to implement every suggestion, I do consider all feedback carefully. Please share your thoughts in the [Discussions area](https://github.com/erew123/alltalk_tts/discussions) or via a Feature Request in the [Issues section](https://github.com/erew123/alltalk_tts/issues).
-
-
- ### 🟨 Help with problems
-
- #### 🔄 **Minor updates/bug fixes list** can be found [here](https://github.com/erew123/alltalk_tts/issues/25)
-
-
- 🟨 How to make a diagnostics report file
-
- If you are on a Windows machine or a Linux machine, you should be able to use the `atsetup.bat` or `./atsetup.sh` utility to create a diagnositcs file. If you are unable to use the `atsetup` utility, please follow the instructions below.
-
-
- Manually making a diagnostics report file
-
- 1) Open a command prompt window and start the Python environment. Depending on your setup (Text-generation-webui or Standalone AllTalk), the steps to start the Python environment vary:
-
- - **For Text-generation-webui Users**:
- - Navigate to the Text-generation-webui directory:
- - `cd text-generation-webui`
- - Start the Python environment suitable for your OS:
- - Windows: `cmd_windows.bat`
- - Linux: `./cmd_linux.sh`
- - macOS: `cmd_macos.sh`
- - WSL (Windows Subsystem for Linux): `cmd_wsl.bat`
- - Move into the AllTalk directory:
- - `cd extensions/alltalk_tts`
-
- - **For Standalone AllTalk Users**:
- - Navigate to the `alltalk_tts` folder:
- - `cd alltalk_tts`
- - Start the Python environment:
- - Windows: `start_environment.bat`
- - Linux: `./start_environment.sh`
-
- > If you're unfamiliar with Python environments and wish to learn more, consider reviewing **Understanding Python Environments Simplified** in the Help section.
-
- 2) Run the diagnostics and select the requirements file name you installed AllTalk with:
- - `python diagnostics.py`
-
- 3) You will have an on screen output showing your environment setttings, file versions request vs whats installed and details of your graphics card (if Nvidia). This will also create a file called `diagnostics.log` in the `alltalk_tts` folder, that you can upload if you need to create a support ticket on here.
-
- ### Installation and Setup Issues
-
-
- 🟨 Understanding Python Environments Simplified
-
- Think of Python environments like different rooms in your house, each designed for a specific purpose. Just as you wouldn't cook in the bathroom or sleep in the kitchen, different Python applications need their own "spaces" or environments because they have unique requirements. Sometimes, these requirements can clash with those of other applications (imagine trying to cook a meal in a bathroom!). To avoid this, you can create separate Python environments.
-
- #### Why Separate Environments?
- Separate environments, like separate rooms, keep everything organized and prevent conflicts. For instance, one Python application might need a specific version of a library or dependency, while another requires a different version. Just as you wouldn't store kitchen utensils in the bathroom, you wouldn't want these conflicting requirements to interfere with each other. Each environment is tailored and customized for its application, ensuring it has everything it needs without disrupting others.
-
- #### How It Works in Practice:
-
- **Standalone AllTalk Installation:** When you install AllTalk standalone, it's akin to adding a new room to your house specifically designed for your AllTalk activities. The setup process, using the atsetup utility, constructs this custom "room" (Python environment `alltalk_environment`) with all the necessary tools and furnishings (libraries and dependencies) that AllTalk needs to function smoothly, without meddling with the rest of your "house" (computer system). The AllTalk environment is started each time you run `start_alltalk` or `start_environment` within the AllTalk folder.
-
- **Text-generation-webui Installation:** Similarly, installing Text-generation-webui is like setting up another specialized room. Upon installation, it automatically creates its own tailored environment, equipped with everything required for text generation, ensuring a seamless and conflict-free operation. The Text-generation-webui environment is started each time you run `start_*your-os-version*` or `cmd_*your-os-version*` within the Text-generation-webui folder.
-
- #### Managing Environments:
- Just as you might renovate a room or bring in new furniture, you can also update or modify Python environments as needed. Tools like Conda or venv make it easy to manage these environments, allowing you to create, duplicate, activate, or delete them much like how you might manage different rooms in your house for comfort and functionality.
-
- Once you're in the right environment, by activating it, installing or updating dependencies (the tools and furniture of your Python application) is straightforward. Using pip, a package installer for Python, you can easily add what you need. For example, to install all required dependencies listed in a requirements.txt file, you'd use:
-
- `pip install -r requirements.txt`
-
- This command tells pip to read the list of required packages and versions from the requirements.txt file and install them in the current environment, ensuring your application has everything it needs to operate. It's like having a shopping list for outfitting a room and ensuring you have all the right items delivered and set up.
-
- Remember, just as it's important to use the right tools for tasks in different rooms of your house, it's crucial to manage your Python environments and dependencies properly to ensure your applications run as intended.
-
- #### How do I know if I am in a Python environment?:
- When a Python environment starts up, it changes the command prompt to show the Python environment that it currently running within that terminal/console.
-
- 
-
-
-
- 🟨 Windows & Python requirements for compiling packages (ERROR: Could not build wheels for TTS)
-
- `ERROR: Microsoft Visual C++ 14.0 or greater is required` or `ERROR: Could not build wheels for TTS.` or `ModuleNotFoundError: No module named 'TTS`
-
- Python requires that you install C++ development tools on Windows. This is detailed on the [Python site here](https://wiki.python.org/moin/WindowsCompilers). You would need to install `MSVCv142 - VS 2019 C++ x64/x86 build tools` and `Windows 10/11 SDK` from the C++ Build tools section.
-
- You can get hold of the **Community** edition [here](https://visualstudio.microsoft.com/downloads/) the during installation, selecting `C++ Build tools` and then `MSVCv142 - VS 2019 C++ x64/x86 build tools` and `Windows 10/11 SDK`.
-
- 
-
-
-
- 🟨 Standalone Install - start_{youros}.xx opens and closes instantly and AllTalk doesnt start
-
- This is more than likely caused by having a space ` ` in your folder path e.g. `c:\program files\alltalk_tts`. In this circumstance you would be best moving the folder to a path without a space e.g. `c:\myfiles\alltalk_tts`. You would have to delete the `alltalk_environment` folder and `start_alltalk.bat` or `start_alltalk.sh` and then re-run `atsetup` to re-create the environment and startup files.
-
-
-
- 🟨 Text-geneneration-webui & Stable-Diffusion Plugin - Load Order & stripped text
-
- The Stable Diffusion plugin for Text-generation-webui **strips out** some of the text, which is passed to Stable Diffusion for image/scene generation. Because this text is stripped, its important to consider the load order of the plugins to get the desired result you want. Lets assume the AI has just generated the following message `*He walks into the room with a smile on his face and says* Hello how are you?`. Depending on the load order will change what text reaches AllTalk for generation e.g.
-
- **SD Plugin loaded before AllTalk** - Only `Hi how are you?` is sent to AllTalk, with the `*He walks into the room with a smile on his face and says*` being sent over to SD for image generation. Narration of the scene is not possible.
- **AllTalk loaded before SD Plugin** - `*He walks into the room with a smile on his face and says* Hello how are you?` is sent to AllTalk with the `*He walks into the room with a smile on his face and says*` being sent over to SD for image generation.
- The load order can be changed within Text-generation-webui's `settings.yaml` file or `cmd_flags.txt` (depending on how you are managing your extensions).
-
-
-
- 🟨 I think AllTalks requirements file has installed something another extension doesn't like
-
- Ive paid very close attention to **not** impact what Text-generation-webui is requesting on a factory install. This is one of the requirements of submitting an extension to Text-generation-webui. If you want to look at a comparison of a factory fresh text-generation-webui installed packages (with cuda 12.1, though AllTalk's requirements were set on cuda 11.8) you can find that comparison [here](https://github.com/erew123/alltalk_tts/issues/23). This comparison shows that AllTalk is requesting the same package version numbers as Text-generation-webui or even lower version numbers (meaning AllTalk will not update them to a later version). What other extensions do, I cant really account for that.
-
- I will note that the TTS engine downgrades Pandas data validator to 1.5.3 though its unlikely to cause any issues. You can upgrade it back to text-generation-webui default (december 2023) with `pip install pandas==2.1.4` when inside of the python environment. I have noticed no ill effects from it being a lower or higher version, as far as AllTalk goes. This is also the same behaviour as the Coqui_tts extension that comes with Text-generation-webui.
-
- Other people are reporting issues with extensions not starting with errors about Pydantic e.g. ```pydantic.errors.PydanticImportError: BaseSettings` has been moved to the pydantic-settings package. See https://docs.pydantic.dev/2.5/migration/#basesettings-has-moved-to-pydantic-settings for more details.```
-
- Im not sure if the Pydantic version has been recently updated by the Text-generation-webui installer, but this is nothing to do with AllTalk. The other extension you are having an issue with, need to be updated to work with Pydantic 2.5.x. AllTalk was updated in mid december to work with 2.5.x. I am not specifically condoning doing this, as it may have other knock on effects, but within the text-gen Python environment, you can use `pip install pydantic==2.5.0` or `pip install pydantic==1.10.13` to change the version of Pydantic installed.
-
-
- 🟨 I am having problems getting AllTalk to start after changing settings or making a custom setup/model setup.
-
- I would suggest following [Problems Updating](https://github.com/erew123/alltalk_tts#-problems-updating) and if you still have issues after that, you can raise an issue [here](https://github.com/erew123/alltalk_tts/issues)
-
-
- ### Networking and Access Issues
-
-
- 🟨 I cannot access AllTalk from another machine on my Network
-
- You will need to change the IP address within AllTalk's settings from being 127.0.0.1, which only allows access from the local machine its installed on. To do this, please see [Changing AllTalks IP address & Accessing AllTalk over your Network](https://github.com/erew123/alltalk_tts/tree/main?tab=readme-ov-file#-changing-alltalks-ip-address--accessing-alltalk-over-your-network) at the top of this page.
-
- You may also need to allow access through your firewall or Antivirus package to AllTalk.
-
-
-
- 🟨 I am running a Headless system and need to change the IP Address manually as I cannot reach the config page
-
- To do this you can edit the `confignew.json` file within the `alltalk_tts` folder. You would look for `"ip_address": "127.0.0.1",` and change the `127.0.0.1` to your chosen IP address,then save the file and start AllTalk.
-
- When doing this, be careful not to impact the formatting of the JSON file. Worst case, you can re-download a fresh copy of `confignew.json` from this website and that will put you back to a factory setting.
-
-
- ### Configuration and Usage Issues
-
- 🟨 I activated DeepSpeed in the settings page, but I didnt install DeepSpeed yet and now I have issues starting up
-
- You can either follow the [Problems Updating](https://github.com/erew123/alltalk_tts#-problems-updating) and fresh install your config. Or you can edit the `confignew.json` file within the `alltalk_tts` folder. You would look for '"deepspeed_activate": true,' and change the word true to false `"deepspeed_activate": false,' ,then save the file and try starting again.
-
- If you want to use DeepSpeed, you need an Nvidia Graphics card and to install DeepSpeed on your system. Instructions are [here](https://github.com/erew123/alltalk_tts#-deepspeed-installation-options)
-
-
-
- 🟨 I am having problems updating/some other issue where it wont start up/Im sure this is a bug
-
- Please see [Problems Updating](https://github.com/erew123/alltalk_tts#-problems-updating). If that doesnt help you can raise an ticket [here](https://github.com/erew123/alltalk_tts/issues). It would be handy to have any log files from the console where your error is being shown. I can only losely support custom built Python environments and give general pointers. Please create a `diagnostics.log` report file to submit with a support request.
-
- Also, is your text-generation-webui up to date? [instructions here](https://github.com/oobabooga/text-generation-webui?tab=readme-ov-file#how-to-install)
-
-
-
- 🟨 Error Details: HTTPConnectionPool(host='null', port=80): Max retries exceeded with url: /api/tts-generate............ etc
-
- If you get this error, the gradio window you have open has lost connection to the AllTalk backend. This is most likely caused by AllTalk being restarted in the background and you not opening the gradio window again. Simply refresh the gradio web page and it will be connected back in and working.
-
-
-
-
- 🟨 I see some red "asyncio" messages
-
- As far as I am aware, these are to do with the chrome browser the gradio text-generation-webui in some way. I raised an issue about this on the text-generation-webui [here](https://github.com/oobabooga/text-generation-webui/issues/4788) where you can see that AllTalk is not loaded and the messages persist. Either way, this is more a warning than an actual issue, so shouldnt affect any functionality of either AllTalk or text-generation-webui, they are more just an annoyance.
-
-
- ### Startup, Performance and Compatibility Issues
-
-
- 🟨 Understanding the AllTalk start-up screen
-
- The AllTalk start-up screen provides various bits of information about the detected Python environment and errors.
-
- 
-
- **Config file check**
- - Sometimes I need to add/remove something to your existing configuration file settings. Obviously, I don’t want to impact your existing settings, however any new features may need these settings to be created before AllTalk starts up. Ive added extra code that checks `alltalk_tts/system/config/at_configupdate.json` and `alltalk_tts/system/config/at_configdowngrade.json`, either adding or removing items to your configuration as necessary. If a change is made, you will be notified and a backup of the previous configuration file will be created in the `alltalk_tts` folder.
-
- **AllTalk startup Mode**
- - informational. This will state if AllTalk has detected it is running as part of Text-generation-webui or as a Standalone Application.
-
- **WAV file deletion**
- - If you have set deletion of old generated WAV files, this will state the time frame after which they are purged.
-
- **DeepSpeed version**
- - What version of DeepSpeed is installed/detected. This will **not** tell you if the version of DeepSpeed is compiled for your Python, PyTorch or CUDA version. Its important to remember that DeepSpeed has to be compiled for the exact version of Python, PyTorch and CUDA that you are using, so please ensure you have the correct DeepSpeed version installed if necessary.
-
- **Model is available**
- - AllTalk is checking if your model files exist. This is not a validity check of the actual model files, they can still be corrupted. If files are missing, AllTalk will attempt to download them from Huggingface, however, if Huggingface has an outage/issue or your internet connection has issues, its possible corrupted or incomplete files will be downloaded. Please read `RuntimeError: PytorchStreamReader failed reading zip archive: failed finding central directory` if you need to confirm your model files are ok.
-
- **Current Python Version**
- - Informational. Literally tells you the version of Python running in your Python environment.
-
- **Current PyTorch Version**
- - Informational. Tell tells you the version of **PyTorch** running in your Python environment, however if you have an Nvidia card, you should be running a CUDA based version of Pytorch. This is indicated with a `+cXXX` after the PyTorch version e.g. `2.2.2+cu121` would be PyTorch version 2.2.2 with CUDA 12.1 extensions. If you don’t have the PyTorch CUDA extensions installed, but you do have an Nvidia card, you may need to re-install PyTorch.
-
- **Current CUDA Version**
- - Informational. This is linked to the Current PyTorch Version, as detailed above.
-
- **Current TTS Version**
- - Informational. The current version of the TTS engine that is running.
-
- **AllTalk Github updated**
- - As long as you have an internet connection, this will tell you last time AllTalk was updated on Github. It is checking the [commit list](https://github.com/erew123/alltalk_tts/commits/main/) to see when the last commit was made. As such, this could be simply a documentation update, a bug fix or new features. Its simply there as a guide to let you know the last time something was changed on AllTalk's Github.
-
- **TTS Subprocess**
- - When AllTalk reaches this stage, the subprocess that loads in the AI model is starting. This is most likely where an error could occur with loading the TTS model, just after the documentation message.
-
- **AllTalk Settings & Documentation: http ://x.x.x.x**
- - The link where you can reach AllTalks built in settings and documentation page. The TTS model will be loading immediately after this is displayed.
-
-
-
-
- 🟨 AllTalk is only loading into CPU, but I have an Nvidia GPU so it should be loading into CUDA
-
- This is caused by Pytorch (Torch) not having the CUDA extensions installed (You can check by running the diagnostics). Typically this happens (on Standalone installations) because when the setup routine goes to install Pytorch with CUDA, it looks in the PIP cache and if a previous application has downloaded a version of Pytorch that **doesn't** have CUDA extensions, the PIP installer doesnt recognise this fact and just uses the cached version for installation. To resolve this:
-
- 1) On the `atsetup` utility, on the `Standalone menu` select to `Purge the PIP cache`. This will remove cached packages from the PIP cache, meaning it will have to download fresh copies.
- 2) As we need to force the upgrade to the Python environment, the easiest way to do this will be to use `atsetup` to `Delete AllTalk's custom Python environment`. This means it will have to rebuild the Python environment. **Note**, you may have to run this step twice, as it has to exit the current Python environment, then you have to re-load `atsetup` and select `Delete AllTalk's custom Python environment` again.
- 3) You can now use `atsetup` to `Install AllTalk as a Standalone Application` which will download fresh copies of everything and re-install the Python environment.
- 4) Once this is done you can check if CUDA is now working with the diagnostics or starting AllTalk and checking the model loads into CUDA.
-
-
-
-
-
- 🟨 RuntimeError: PytorchStreamReader failed reading zip archive: failed finding central directory
-
- This error message is caused by the model being corrupted or damaged in some way. This error can occur if Huggingface, where the model is downloaded from, have an error (when the model is downloaded) or potentailly internet issues occuring while the model is downloaded on first start-up.
-
- ```
- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
- RuntimeError: PytorchStreamReader failed reading zip archive: failed finding central directory
-
- ERROR: Application startup failed. Exiting.
- [AllTalk Startup] Warning TTS Subprocess has NOT started up yet, Will keep trying for 120 seconds maximum. Please wait.
- ```
-
- To resolve this, first look in your `alltalk_tts/models/xttsv2_2.0.2` (or whichever) model folder and confirm that the file sizes are correct.
-
- 
-
- You can delete one or more suspect files and a factory fresh copy of that file or files will be downloaded on next start-up of AllTalk.
-
-
-
-
- 🟨 RuntimeError: Found no NVIDIA driver on your system.
-
- This error message is caused by DeepSpeed being enabled when you do not have a Nvidia GPU. To resolve this, edit `confignew.json` and change `"deepspeed_activate": true,` to `"deepspeed_activate": false,` then restart AllTalk.
-
- ```
- File "C:\alltalk_tts\alltalk_environment\env\Lib\site-packages\torch\cuda\__init__.py", line 302, in _lazy_init
- torch._C._cuda_init()
- RuntimeError: Found no NVIDIA driver on your system. Please check that you have an NVIDIA GPU and installed a driver from http://www.nvidia.com/Download/index.aspx
-
- ERROR: Application startup failed. Exiting.
- [AllTalk Startup] Warning TTS Subprocess has NOT started up yet, Will keep trying for 120 seconds maximum. Please wait.
- ```
-
-
-
-
- 🟨 raise RuntimeError("PyTorch version mismatch! DeepSpeed ops were compiled and installed.
-
- This error message is caused by having DeepSpeed enabled, but you have a version of DeepSpeed installed that was compiled for a different version of Python, PyTorch or CUDA (or any mix of those). You will need to start your Python environment and run `pip uninstall deepspeed` to remove DeepSpeed from your Python environment and then install the correct version of DeepSpeed.
-
- ```
- raise RuntimeError("PyTorch version mismatch! DeepSpeed ops were compiled and installed
- RuntimeError: PyTorch version mismatch! DeepSpeed ops were compiled and installed with a different version than what is being used at runtime. Please re-install DeepSpeed or switch torch versions. Install torch version=2.1, Runtime torch version=2.2
- ```
-
-
-
-
- 🟨 Warning TTS Subprocess has NOT started up yet, Will keep trying for 120 seconds maximum. Please wait. It times out after 120 seconds.
- When the subprocess is starting 2x things are occurring:
-
- **A)** Its trying to load the voice model into your graphics card VRAM (assuming you have a Nvidia Graphics card, otherwise its your system RAM)
- **B)** Its trying to start up the mini-webserver and send the "ready" signal back to the main process.
-
- Before giving other possibilities a go, some people with **old machines** are finding their startup times are **very** slow 2-3 minutes. Ive extended the allowed time within the script from 1 minute to 2 minutes. **If you have an older machine** and wish to try extending this further, you can do so by editing `script.py` and changing `startup_wait_time = 120` (120 seconds, aka 2 minutes) at the top of the script.py file, to a larger value e.g `startup_wait_time = 240` (240 seconds aka 4 minutes).
-
- **Note:** If you need to create a support ticket, please create a `diagnostics.log` report file to submit with a support request. Details on doing this are above.
-
- Other possibilities for this issue are:
-
- 1) You are starting AllTalk in both your `CMD FLAG.txt` and `settings.yaml` file. The `CMD FLAG.txt` you would have manually edited and the `settings.yaml` is the one you change and save in the `session` tab of text-generation-webui and you can `Save UI defaults to settings.yaml`. Please only have one of those two starting up AllTalk.
-
- 2) You are not starting text-generation-webui with its normal Python environment. Please start it with start_{your OS version} as detailed [here](https://github.com/oobabooga/text-generation-webui#how-to-install) (`start_windows.bat`,`./start_linux.sh`, `start_macos.sh` or `start_wsl.bat`) OR (`cmd_windows.bat`, `./cmd_linux.sh`, `cmd_macos.sh` or `cmd_wsl.bat` and then `python server.py`).
-
- 3) You have installed the wrong version of DeepSpeed on your system, for the wrong version of Python/Text-generation-webui. You can go to your text-generation-webui folder in a terminal/command prompt and run the correct cmd version for your OS e.g. (`cmd_windows.bat`, `./cmd_linux.sh`, `cmd_macos.sh` or `cmd_wsl.bat`) and then you can type `pip uninstall deepspeed` then try loading it again. If that works, please see here for the correct instructions for installing DeepSpeed [here](https://github.com/erew123/alltalk_tts#-deepspeed-installation-options).
-
- 4) You have an old version of text-generation-webui (pre Dec 2023) I have not tested on older versions of text-generation-webui, so cannot confirm viability on older versions. For instructions on updating the text-generation-webui, please look [here](https://github.com/oobabooga/text-generation-webui#how-to-install) (`update_linux.sh`, `update_windows.bat`, `update_macos.sh`, or `update_wsl.bat`).
-
- 5) You already have something running on port 7851 on your computer, so the mini-webserver cant start on that port. You can change this port number by editing the `confignew.json` file and changing `"port_number": "7851"` to `"port_number": "7602"` or any port number you wish that isn’t reserved. Only change the number and save the file, do not change the formatting of the document. This will at least discount that you have something else clashing on the same port number.
-
- 6) You have antivirus/firewalling that is blocking that port from being accessed. If you had to do something to allow text-generation-webui through your antivirus/firewall, you will have to do that for this too.
-
- 7) You have quite old graphics drivers and may need to update them.
-
- 8) Something within text-generation-webui is not playing nicely for some reason. You can go to your text-generation-webui folder in a terminal/command prompt and run the correct cmd version for your OS e.g. (`cmd_windows.bat`, `./cmd_linux.sh`, `cmd_macos.sh` or `cmd_wsl.bat`) and then you can type `python extensions\alltalk_tts\script.py` and see if AllTalk starts up correctly. If it does then something else is interfering.
-
- 9) Something else is already loaded into your VRAM or there is a crashed python process. Either check your task manager for erroneous Python processes or restart your machine and try again.
-
- 10) You are running DeepSpeed on a Linux machine and although you are starting with `./start_linux.sh` AllTalk is failing there on starting. This is because text-generation-webui will overwrite some environment variables when it loads its python environment. To see if this is the problem, from a terminal go into your text-generation-webui folder and `./cmd_linux.sh` then set your environment variable again e.g. `export CUDA_HOME=/usr/local/cuda` (this may vary depending on your OS, but this is the standard one for Linux, and assuming you have installed the CUDA toolkit), then `python server.py` and see if it starts up. If you want to edit the environment permanently you can do so, I have not managed to write full instructions yet, but here is the conda guide [here](https://conda.io/projects/conda/en/latest/user-guide/tasks/manage-environments.html#set-env-vars).
-
- 11) You have built yourself a custom Python environment and something is funky with it. This is very hard to diagnose as its not a standard environment. You may want to updating text-generation-webui and re installing its requirements file (whichever one you use that comes down with text-generation-webui).
-
-
-
- 🟨 I have multiple GPU's and I have problems running Finetuning
-
- Finetuning pulls in various other scripts and some of those scripts can have issues with multiple Nvidia GPU's being present. Until the people that created those other scripts fix up their code, there is a workaround to temporarily tell your system to only use the 1x of your Nvidia GPU's. To do this:
-
- - **Windows** - You will start the script with `set CUDA_VISIBLE_DEVICES=0 && python finetune.py`
- After you have completed training, you can reset back with `set CUDA_VISIBLE_DEVICES=`
-
- - **Linux** - You will start the script with `CUDA_VISIBLE_DEVICES=0 python finetune.py`
- After you have completed training, you can reset back with `unset CUDA_VISIBLE_DEVICES`
-
- Rebooting your system will also unset this. The setting is only applied temporarily.
-
- Depending on which of your Nvidia GPU's is the more powerful one, you can change the `0` to `1` or whichever of your GPU's is the most powerful.
-
-
-
-
- 🟨 Firefox - Streaming Audio doesnt work on Firefox
-
- This is a long standing issue with Mozilla & Firefox and one I am unable to resolve as Mozilla have not resolved the issue with Firefox. The solution is to use another web browser if you want to use Streaming audio. For details of my prior invesitigation please look at this [ticket](https://github.com/erew123/alltalk_tts/issues/143)
-
-
- ### Application Specific Issues
-
-
- 🟨 SillyTavern - I changed my IP address and now SillyTavern wont connect with AllTalk
- SillyTavern checks the IP address when loading extensions, saving the IP to its configuration only if the check succeeds. For whatever reason, SillyTavern's checks dont always allow changing its IP address a second time.
-
- To manually change the IP address:
-
- 1) Navigate to the SillyTavern Public folder located at `/sillytavern/public/`.
- 2) Open the `settings.json` file.
- 3) Look for the AllTalk section and find the `provider_endpoint` entry.
- 3) Replace `localhost` with your desired IP address, for example, `192.168.1.64`.
-
- 
-
-
- ### TTS Generation Issues & Questions
-
-
- 🟨 XTTS - Does the XTTS AI Model Support Emotion Control or Singing?
-
- No, the XTTS AI model does not currently support direct control over emotions or singing capabilities. While XTTS infuses generated speech with a degree of emotional intonation based on the context of the text, users cannot explicitly control this aspect. It's worth noting that regenerating the same line of TTS may yield slightly different emotional inflections, but there is no way to directly control it with XTTS.
-
-
- 🟨 XTTS - Skips, repeats or pronunciation Issues
-
- Firstly, it's important to clarify that the development and maintenance of the XTTS AI models and core scripts are handled by [Coqui](https://docs.coqui.ai/en/latest/index.html), with additional scripts and libraries from entities like [huggingface](https://huggingface.co/docs/transformers/en/index) among many other Python scripts and libraries used by AllTalk.
-
- AllTalk is designed to be a straightforward interface that simplifies setup and interaction with AI TTS models like XTTS. Currently, AllTalk supports the XTTS model, with plans to include more models in the future. Please understand that the deep inner workings of XTTS, including reasons why it may skip, repeat, or mispronounce, along with 3rd party scripts and libraries utilized, are ultimately outside my control.
-
- Although I ensure the text processed through AllTalk is accurately relayed to the XTTS model speech generation process, and I have aimed to mitigate as many issues as much as possible; skips, repeats and bad pronounciation can still occur.
-
- Certain aspects I have not been able to investigate due to my own time limitations, are:
-
- - The impact of DeepSpeed on TTS quality. Is this more likely to cause skips or repetition?
- - Comparative performance between different XTTS model versions (e.g., 2.0.3 vs. 2.0.2) regarding audio quality and consistency.
-
- **From my experience and anecdotally gained knowledge:**
-
- - Lower quality voice samples tend to produce more anomalies in generated speech.
- - Model finetuning with high-quality voice samples significantly reduces such issues, enhancing overall speech quality.
- - Unused/Excessive punctuation causes issues e.g. asterisks `*`, hashes `#`, brackets `(` `)` etc. Many of these AllTalk will filter out.
-
- So for example, the `female_01.wav` file that is provided with AllTalk is a studio quality voice sample, which the XTTS model was trained on. Typically you will find it unlikely that anomolies occur with TTS generation when using this voice sample. Hence good quality samples and finetuning, generally improve results with XTTS.
-
- If you wish to try out the XTTS version 2.0.3 model and see if it works better, you can download it from [here](https://huggingface.co/coqui/XTTS-v2/tree/v2.0.3), replacing all the files within your `/alltalk_tts/models/xttsv2_2.0.2` folder. This is on my list to both test version 2.0.3 more, but also build a more flexible TTS models downloader, that will not only accomdating other XTTS models, but also other TTS engines. If you try the XTTS version 2.0.3 model and gleen any insights, please let me know.
-
- """
-
- finetuning_a_model = """
- ### ⚫ Finetuning a model
- If you have a voice that the model doesnt quite reproduce correctly, or indeed you just want to improve the reproduced voice, then finetuning is a way to train your "XTTSv2 local" model **(stored in `/alltalk_tts/models/xxxxx/`)** on a specific voice. For this you will need:
-
- - An Nvidia graphics card. (Please see the help section [note](https://github.com/erew123/alltalk_tts/edit/main/README.md#performance-and-compatibility-issues) if you have multiple Nvidia GPU's).
- - 18GB of disk space free (most of this is used temporarily)
- - At least 2 minutes of good quality speech from your chosen speaker in mp3, wav or flacc format, in one or more files (have tested as far as 20 minutes worth of audio).
- - As a side note, many people seem to think that the Whisper v2 model (used on Step 1) is giving better results at generating training datasets, so you may prefer to try that, as opposed to the Whisper 3 model.
-
- #### ⚫ How will this work/How complicated is it?
- Everything has been done to make this as simple as possible. At its simplest, you can literally just download a large chunk of audio from an interview, and tell the finetuning to strip through it, find spoken parts and build your dataset. You can literally click 4 buttons, then copy a few files and you are done. At it's more complicated end you will clean up the audio a little beforehand, but its still only 4x buttons and copying a few files.
-
- #### ⚫ The audio you will use
- I would suggest that if its in an interview format, you cut out the interviewer speaking in audacity or your chosen audio editing package. You dont have to worry about being perfect with your cuts, the finetuning Step 1 will go and find spoken audio and cut it out for you. Is there is music over the spoken parts, for best quality you would cut out those parts, though its not 100% necessary. As always, try to avoid bad quality audio with noises in it (humming sounds, hiss etc). You can try something like [Audioenhancer](https://audioenhancer.ai/) to try clean up noisier audio. There is no need to down-sample any of the audio, all of that is handled for you. Just give the finetuning some good quality audio to work with.
-
- #### ⚫ Can I Finetune a model more than once on more than one voice
- Yes you can. You would do these as multiple finetuning's, but its absolutely possible and fine to do. Finetuning the XTTS model does not restrict it to only being able to reproduce that 1x voice you trained it on. Finetuning is generally nudging the model in a direction to learn the ability to sound a bit more like a voice its not heard before.
-
- #### ⚫ A note about anonymous training Telemetry information & disabling it
- Portions of Coqui's TTS trainer scripts gather anonymous training information which you can disable. Their statement on this is listed [here](https://github.com/coqui-ai/Trainer?tab=readme-ov-file#anonymized-telemetry). If you start AllTalk Finetuning with `start_finetuning.bat` or `./start_finetuning.sh` telemetry will be disabled. If you manually want to disable it, please expand the below:
-
-
- Manually disable telemetry
-
- Before starting finetuning, run the following in your terminal/command prompt:
-
- - On Windows by typing `set TRAINER_TELEMETRY=0`
- - On Linux & Mac by typing `export TRAINER_TELEMETRY=0`
-
- Before you start `finetune.py`. You will now be able to finetune offline and no anonymous training data will be sent.
-
- """
-
- finetuning_setup = """
- #### ⚫ Prerequisites for Fine-tuning with Nvidia CUDA Development Toolkit 11.8
-
- All the requirements for Finetuning will be installed by using the atsetup utility and installing your correct requirements (Standalone or for Text-generation-webui). The legacy manual instructions are stored below, however these shouldnt be required.
-
-
- Legacy manual instructions for installing Nvidia CUDA Development Toolkit 11.8
- - To perform fine-tuning, a specific portion of the **Nvidia CUDA Development Toolkit v11.8** must be installed. This is crucial for step 1 of fine-tuning. The objective is to minimize the installation footprint by installing only the essential components.
- - The **Nvidia CUDA Development Toolkit v11.8** operates independently from your graphics card drivers and the CUDA version utilized by your Python environment.
- - This installation process aims to keep the download and install size as minimal as possible, however a full install of the tookit requires 3GB's of disk space.
- - When running Finetuning it will require upto 20GB's of temporary disk space, so please ensure you have this space available and preferably use a SSD or NVME drive.
-
- 1. **Download the Toolkit**:
- - Obtain the **network install** version of the Nvidia CUDA Development Toolkit 11.8 from [Nvidia's Archive](https://developer.nvidia.com/cuda-11-8-0-download-archive).
-
- 2. **Run the Installer**:
- - Choose **Custom (Advanced)** installation.
- - Deselect all options initially.
- - Select the following components:
- - `CUDA` > `Development` > `Compiler` > `nvcc`
- - `CUDA` > `Development` > `Libraries` > `CUBLAS` (**both** development and runtime)
-
- 3. **Configure Environment Search Path**:
- - It's essential that `nvcc` and CUDA 11.8 library files are discoverable in your environment's search path. Adjustments can be reverted post-fine-tuning if desired.
-
- **For Windows**:
- - Edit the `Path` environment variable to include `C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v11.8\bin`.
- - Add `CUDA_HOME` and set its path to `C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v11.8.`
-
- **For Linux**:
- - The path may vary by Linux distribution. Here's a generic setup:
- - `export CUDA_HOME=/usr/local/cuda`
- - `export PATH=${CUDA_HOME}/bin:${PATH}`
- - `export LD_LIBRARY_PATH=${CUDA_HOME}/lib64:$LD_LIBRARY_PATH`
-
- - Consider adding these to your `~/.bashrc` for permanence, or apply temporarily for the current session by running the above commands each time you start your Python environment.
-
- **Note**: If using Text-generation-webui, its best to set these temporarily.
-
- 4. **Verify Installation**:
- - Open a **new** terminal/command prompt to refresh the search paths.
- - In a terminal or command prompt, execute `nvcc --version`.
- - Success is indicated by a response of `Cuda compilation tools, release 11.8.` Specifically, ensure it is version 11.8.
-
- 5. **Troubleshooting**:
- - If the correct version isn't reported, recheck your environment path settings for accuracy and potential conflicts with other CUDA versions.
-
-
- #### Additional Note on Torch and Torchaudio:
- - Ensure Torch and Torchaudio are CUDA-enabled (any version), which is separate from the CUDA Toolkit installation. CUDA 11.8 corresponds to `cu118` and CUDA 12.1 to `cu121` in AllTalk diagnostics.
- - Failure to install CUDA for Torch and Torchaudio will result in Step 2 of fine-tuning failing. These requirements are distinct from the CUDA Toolkit installation, so avoid conflating the two.
-
-
- #### ⚫ Starting Fine-tuning
-
- **NOTE:** Ensure AllTalk has been launched at least once after any updates to download necessary files for fine-tuning.
-
- 1. **Close Resource-Intensive Applications**:
- - Terminate any applications that are using your GPU/VRAM to ensure enough resources for fine-tuning.
-
- 2. **Organize Voice Samples**:
- - Place your audio samples into the following directory:
- `/alltalk_tts/finetune/put-voice-samples-in-here/`
-
- Depending on your setup (Text-generation-webui or Standalone AllTalk), the steps to start the Python environment vary:
-
- ### **For Standalone AllTalk Users**:
- Navigate to the `alltalk_tts` folder:
- > `cd alltalk_tts`
-
- Start the Python environment:
- > **Windows**: `start_finetune.bat`
- **Linux**: `./start_finetune.sh`
-
- ### **For Text-generation-webui Users**:
- Navigate to the Text-generation-webui directory:
- >`cd text-generation-webui`
-
- Start the Python environment suitable for your OS:
- **Windows**: `cmd_windows.bat`
- **Linux**: `./cmd_linux.sh`
- **macOS**: `cmd_macos.sh`
- **WSL (Windows Subsystem for Linux)**: `cmd_wsl.bat`
-
- Move into the AllTalk directory:
- >`cd extensions/alltalk_tts`
-
- **Linux** users only need to run this command:
- `
- export LD_LIBRARY_PATH=`python3 -c 'import os; import nvidia.cublas.lib; import nvidia.cudnn.lib; print(os.path.dirname(nvidia.cublas.lib.__file__) + ":" + os.path.dirname(nvidia.cudnn.lib.__file__))'`
- `
- Start the fine-tuning process with the command:
- > `python finetune.py`
-
- > If you're unfamiliar with Python environments and wish to learn more, consider reviewing **Understanding Python Environments Simplified** in the Help section.
-
- ### Both Standalone & Text-generation-webui users
- 3. **Pre-Flight Checklist**:
- > Go through the pre-flight checklist to ensure readiness. Address any issues flagged as "Fail".
-
- 4. **Post Fine-tuning Actions**:
- > Upon completing fine-tuning, the final tab will guide you on managing your files and relocating your newly trained model to the appropriate directory.
- """
-
- finetuning_details = """
- #### ⚫ How many Epochs etc is the right amount?
-
- In finetuning the suggested/recommended amount of epochs, batch size, evaluation percent etc is already set. However, there is no absolutely correct answer to what the settings should be, it all depends on what you are doing.
-
- - If you just want to train a normal human voice that is in an existing language, for most people’s needs, the base settings would work fine. You may choose to increase the epochs up to maybe 20, or run a second round of training if needed.
- - If you were training an entirely new language, you would need a huge amount of training data and it requires around 1000 epochs (based on things I can find around the internet of people who tried this).
- - If you are training a cartoon style voice in an existing language, it may need well upwards of 40 epochs until it can reproduce that voice with some success.
-
- There are no absolute correct settings, as there are too many variables, ranging from the amount of samples you are using (5 minutes worth? 4 hours worth? etc), if they are similar samples to what the AI model already understands, so on and so forth. Coqui whom originally trained the model usually say something along the lines of, once you’ve trained it X amount, if it sounds good then you are done and if it doesn’t, train it more.
-
- #### ⚫ Evaluation Data Percentage
- In the process of finetuning, it's crucial to balance the data used for training the model against the data reserved for evaluating its performance. Typically, a portion of the dataset is set aside as an 'evaluation set' to assess the model's capabilities in dealing with unseen data. On Step 1 of finetuning you have the option to adjust this evaluation data percentage, offering more control over your model training process.
- **Why Adjust the Evaluation Percentage?**
- Adjusting the evaluation percentage **can** be beneficial in scenarios with limited voice samples. When dealing with a smaller dataset, allocating a slightly larger portion to training could enhance the model's ability to learn from these scarce samples. Conversely, with abundant data, a higher evaluation percentage might be more appropriate to rigorously test the model's performance. There are currently no absolutely optimal split percentages as it varies by dataset.
- - **Default Setting:** The default evaluation percentage is set at 15%, which is a balanced choice for most datasets.
- - **Adjustable Range:** Users can now adjust this percentage, but it’s generally recommend keeping it between 5% and 30%.
- - **Lower Bound:** A minimum of 5% ensures that there's enough data to evaluate model performance.
- - **Upper Bound:** Its suggested not exceeding 30% for evaluation to avoid limiting the amount of data available for training.
-
- - **Understanding the Impact:** Before adjusting this setting, it's important to understand its impact on model training and evaluation. Incorrect adjustments can lead to suboptimal model performance.
- - **Gradual Adjustments:** For those unfamiliar with the process, we recommend reading up on training data and training sets, then making small, incremental changes and observing their effects.
- - **Data Quality:** Regardless of the split, the quality of the audio data is paramount. Ensure that your datasets are built from good quality audio with enough data within them.
-
- #### ⚫ Using a Finetuned model in Text-generation-webui
-
- At the end of the finetune process, you will have an option to `Compact and move model to /trainedmodel/` this will compact the raw training file and move it to `/model/trainedmodel/`. When AllTalk starts up within Text-generation-webui, if it finds a model in this location a new loader will appear in the interface for `XTTSv2 FT` and you can use this to load your finetuned model.
**Be careful** not to train a new model from the base model, then overwrite your current `/model/trainedmodel/` **if** you want a seperately trained model. This is why there is an `OPTION B` to move your just trained model to `/models/lastfinetuned/`.
-
- #### ⚫ Training one model with multiple voices
-
- At the end of the finetune process, you will have an option to `Compact and move model to /trainedmodel/` this will compact the raw training file and move it to `/model/trainedmodel/`. This model will become available when you start up finetuning. You will have a choice to train the Base Model or the `Existing finetuned model` (which is the one in `/model/trainedmodel/`). So you can use this to keep further training this model with additional voices, then copying it back to `/model/trainedmodel/` at the end of training.
-
- #### ⚫ Do I need to keep the raw training data/model?
-
- If you've compacted and moved your model, its highly unlikely you would want to keep that data, however the choice is there to keep it if you wish. It will be between 5-10GB in size, so most people will want to delete it.
-
- #### ⚫ I have deeper questions about training the XTTS model, where can I find more information?
-
- If you have deeper questions about the XTTS model, its capabilites, the training process etc, anything thats not covered within the above text or the interface of `finetune.py`, please use the following links to research Coqui's documentation on the XTTS model.
-
- - https://docs.coqui.ai/en/latest/models/xtts.html
- - https://github.com/coqui-ai/TTS
- - https://github.com/coqui-ai/TTS/discussions
-
- """
-
- tts_generator = """
- ### ⬜ AllTalk TTS Generator
- AllTalk TTS Generator is the solution for converting large volumes of text into speech using the voice of your choice. Whether you're creating audio content or just want to hear text read aloud, the TTS Generator is equipped to handle it all efficiently. Please see here for a quick [demo](https://www.youtube.com/watch?v=hunvXn0mLzc)
The link to open the TTS generator can be found on the built-in Settings and Documentation page.
**DeepSpeed** is **highly** recommended to speed up generation. **Low VRAM** would be best turned off and your LLM model unloaded from your GPU VRAM (unload your model). **No Playback** will reduce memory overhead on very large generations (15,000 words or more). Splitting **Export to Wav** into smaller groups will also reduce memory overhead at the point of exporting your wav files (so good for low memory systems).
-
- #### ⬜ Estimated Throughput
- This will vary by system for a multitude of reasons, however, while generating a 58,000 word document to TTS, with DeepSpeed enabled, LowVram disabled, splitting size 2 and on an Nvidia RTX 4070, throughput was around 1,000 words per minute. Meaning, this took 1 hour to generate the TTS. Exporting to combined wavs took about 2-3 minutes total.
-
- #### ⬜ Quick Start
- > **Text Input:** Enter the text you wish to convert into speech in the 'Text Input' box.
- > **Generate TTS:** Hit this to start the text-to-speech conversion.
- > **Pause/Resume:** Used to pause and resume the playback of the initial generation of wavs or the stream.
- > **Stop Playback:** This will stop the current audio playing back. It does not stop the text from being generated however.
-
- Once you have sent text off to be generated, either as a stream or wav file generation, the TTS server will remain busy until this process has competed. As such, think carefully as to how much you want to send to the server.
- If you are generating wav files and populating the queue, you can generate one lot of text to speech, then input your next lot of text and it will continue adding to the list.
-
- #### ⬜ Customization and Preferences
- > **Character Voice:** Choose the voice that will read your text.
- > **Language:** Select the language of your text.
- > **Chunk Sizes:** Decide the size of text chunks for generation. Smaller sizes are recommended for better TTS quality.
-
- #### ⬜ Interface and Accessibility
- > **Dark/Light Mode:** Switch between themes for your visual comfort.
- > **Word Count and Generation Queue:** Keep track of the word count and the generation progress.
-
- #### ⬜ TTS Generation Modes
- > **Wav Chunks:** Perfect for creating audio books, or anything you want to keep long term. Breaks down your text into manageable wav files and queues them up. Generation begins automatically, and playback will start after a few chunks have been prepared ahead. You can set the volume to 0 if you don’t want to hear playback. With Wav chunks, you can edit and/or regenerate portions of the TTS as needed.
-
- > **Streaming:** For immediate playback without the ability to save. Ideal for on-the-fly speech generation and listening. This will not generate wav files and it will play back through your browser. You cannot stop the server generating the TTS once it has been sent.
-
- With wav chunks you can either playback “In Browser” which is the web page you are on, or “On Server” which is through the console/terminal where AllTalk is running from, or "No Playback". Only generation “In Browser” can play back smoothly and populate the Generated TTS List. Setting the Volume will affect the volume level played back both “In Browser” and “On Server”.
-
- For generating **large amounts of TTS**, it's recommended to select the **No Playback** option. This setting minimizes the memory usage in your web browser by avoiding the loading and playing of audio files directly within the browser, which is particularly beneficial for handling extensive audio generations. The definition of large will vary depending on your system RAM availability (will update when I have more information as to guidelines). Once the audio is generated, you can export your list to JSON (for safety) and use the **Play List** option to play back your audio.
-
- #### ⬜ Playback and List Management
- > **Playback Controls:** Utilize 'Play List' to start from the beginning or 'Stop Playback' to halt at any time.
- > **Custom Start:** Jump into your list at a specific ID to hear a particular section.
- > **Regeneration and Editing:** If a chunk isn't quite right, you can opt to regenerate it or edit the text directly. Click off the text to save changes and hit regenerate for the specific line.
- > **Export/Import List:** Save your TTS list as a JSON file or import one. Note: Existing wav files are needed for playback. Exporting is handy if you want to take your files away into another program and have a list of which wav is which, or if you keep your audio files, but want to come back at a later date, edit one or two lines, regenerate the speech and re-combine the wav’s into one new long wav.
-
- #### ⬜ Exporting Your Audio
- > **Export to WAV:** Combine all generated TTS from the list, into one single WAV file for easy download and distribution. Its always recommended to export your list to a JSON before exporting, so that you have a backup, should something go wrong. You can simply re-import the list and try exporting again.
-
- When exporting, there is a file size limit of 1GB and as such you have the option to choose how many files to include in each block of audio exported. 600 is just on the limit of 1GB, depending on the average file size, so 500 or less is a good amount to work with. You can combine the generated files after if you wish, in Audacity or similar.
-
- Additionally, lower export batches will lower the memory requirements, so if your system is low on memory (maybe 8 or 16GB system), you can use smaller export batches to keep the memory requirement down.
-
- #### ⬜ Exporting Subtitles (SRT file)
- > **Export SRT:** This will scan through all wav files in your list and generate a subtitles file that will match your exported wav file.
-
- #### ⬜ Analyzing generated TTS for errors
- > **Analyze TTS:** This will scan through all wav files comparing each ID's orignal text with the TTS generated for that ID and then flag up inconsistences. Its important to understand this is a **best effort** process and **not 100% perfect**, for example:
-
- > Your text may have the word `their` and the automated routine that listens to your generated TTS interprets the word as `there`, aka a spelling difference.
- > Your text may have `Examples are:` (note the colon) and the automated routine that listens to your generated TTS interprets the word as `Examples are` (note NO colon as you cannot sound out a colon in TTS), aka a punctuation difference.
- > Your text may have `There are 100 items` and the automated routine that listens to your generated TTS interprets the word as `There are one hundred items`, aka numbers vs the number written out in words.
-
- There will be other examples such as double quotes. As I say, please remember this is a **best effort** to help you identify issues.
-
- As such, there is a `% Accuracy` setting. This uses a couple of methods to try find things that are similar e.g. taking the `their` and `there` example from above, it would identify that they both sound the same, so even if the text says `their` and the AI listening to the generated TTS interprets the word as `there`, it will realise that both sound the same/are similar so there is no need to flag that as an error. However, there are limits to this and some things may slip through or get picked up when you would prefer them not to be flagged.
-
- The higher the accuracy you choose, the more things it will flag up, however you may get more unwanted detections. The lower the less detections. Based on my few tests, accuracy settings between 96 to 98 seem to generally give the best results. However, I would highly recommend you test out a small 10-20 line text and test out the **Analyze TTS** button to get a feel for how it responds to different settings, as well as things it flags up.
-
- You will be able to see the ID's and Text (orignal and as interpreted) by looking at the terminal/command prompt window.
-
- The Analyze TTS feature uses the Whisper Larger-v2 AI engine, which will download on first use if necessary. This will require about 2.5GB's of disk space and could take a few minutes to download, depending on your internet connection.
-
- You can use this feature on systems that do not have an Nvidia GPU, however, unless you have a very powerful CPU, expect it to be slow.
-
- #### ⬜ Tricks to get the model to say things correctly
- Sometimes the AI model won’t say something the way that you want it to. It could be because it’s a new word, an acronym or just something it’s not good at for whatever reason. There are some tricks you can use to improve the chances of it saying something correctly.
-
- **Adding pauses**
- You can use semi-colons ";" and colons ":" to create a pause, similar to a period "." which can be helpful with some splitting issues.
-
- **Acronyms**
- Not all acronyms are going to be pronounced correctly. Let’s work with the word `ChatGPT`. We know it is pronounced `"Chat G P T"` but when presented to the model, it doesn’t know how to break it down correctly. So, there are a few ways we could get it to break out "Chat" and the G P and T. e.g.
-
- > `Chat G P T.`
- > `Chat G,P,T.`
- > `Chat G.P.T.`
- > `Chat G-P-T.`
- > `Chat gee pee tea`
-
- All bar the last one are using ways within the English language to split out "Chat" into one word being pronounced and then split the G, P and T into individual letters. The final example, which is to use Phonetics will sound perfectly fine, but clearly would look wrong as far as human readable text goes. The phonetics method is very useful in edge cases where pronunciation difficult.
-
- #### ⬜ Notes on Usage
- > For seamless TTS generation, it's advised to keep text chunks under 250 characters, which you can control with the Chunk sizes.
- > Generated audio can be played back from the list, which also highlights the currently playing chunk.
- > The TTS Generator remembers your settings, so you can pick up where you left off even after refreshing the page.
- """
-
- with gr.Tab("Documentation and Help"):
- with gr.Tab("Narrator Function"): gr.Markdown(narrator_guides)
- with gr.Tab("RVC"):
- gr.Markdown(rvc_guides)
- with gr.Row():
- with gr.Column(): gr.Markdown(rvc_guides2)
- with gr.Column(): gr.Markdown(rvc_guides3)
- with gr.Tab("Low VRAM"): gr.Markdown(low_vram)
- with gr.Tab("SillyTavern"): gr.Markdown(silly_tavern_support)
- with gr.Tab("Updating"): gr.Markdown(updating_alltalk)
- with gr.Tab("DeepSpeed"): gr.Markdown(deepspeed_guides)
- with gr.Tab("Help with Problems"): gr.Markdown(help_with_problems)
- with gr.Tab("XTTS Finetuning"):
- with gr.Tab("XTTS Finetuning basics"): gr.Markdown(finetuning_a_model)
- with gr.Tab("XTTS Finetuning Setup"): gr.Markdown(finetuning_setup)
- with gr.Tab("XTTS Finetuning Detailed"): gr.Markdown(finetuning_details)
- with gr.Tab("TTS Generator"): gr.Markdown(tts_generator)
\ No newline at end of file
diff --git a/system/gradio_pages/alltalk_generation_help.py b/system/gradio_pages/alltalk_generation_help.py
deleted file mode 100644
index 68612e9..0000000
--- a/system/gradio_pages/alltalk_generation_help.py
+++ /dev/null
@@ -1,99 +0,0 @@
-import gradio as gr
-
-def alltalk_generation_help():
- generation_blurb = """
- The Generation Screen is the main interface where you can input text to generate speech, as well as swap **and set the default** Text-to-Speech (TTS) engine and model. Below is a detailed description of each setting and its functionality.
- """
- generation_help = """
- ### 🟨 TTS Engine and Model
- **Swap TTS Engine**
- > This button allows you to change and set the current TTS engine. Swapping the engine will also set the new engine as the default for future sessions. When you swap the engine, the settings on the page will refresh to match the capabilities of the selected engine. Some settings may become unavailable or newly available depending on the chosen engine.
-
- **Load Different Model**
- > This dropdown lists the available models for the selected TTS engine. These models are located in the models folder and are specific to each TTS engine. Changing the model will also set it as the default for future sessions and refresh the settings on the page.
-
- ### 🟨 RVC Pipeline
- **RVC Character Voice**
- > This setting is part of the RVC pipeline. If RVC is not enabled (which can be done in the RVC tab), "Disabled" will be the only option. Once RVC is enabled and the required models (approximately 700MB) are downloaded, any voices stored in the `/models/rvc_voices/{yourmodelhere}` folder will be available for selection. Selecting a voice will apply it during the TTS generation process. You can select "Disabled" at any time to bypass the RVC pipeline even when it is globally enabled.
-
- **RVC Narrator Voice**
- > Similar to the RVC Character Voice, this setting applies to the narrator voice in the RVC pipeline.
-
- ### 🟨 Generation Settings
- **Generation Mode**
- > Choose the mode of TTS generation, such as Standard or Streaming. Note that Gradio may have difficulty with streaming, so you might receive standard audio as a file even when streaming is selected. If the engine doesn't support Streaming, only Standard mode will be shown.
-
- **Language/Model not multi-language**
- > Select the language for TTS generation here. Some models are **not** multi-language capable and the language it generates in is built into the model itself. In such cases, this option will change to `Model not multi-language` and it will no longer be selectable when the model doesn't support multiple languages.
-
- **Narrator Enabled/Disabled**
- > Enable or disable the narrator feature. When enabled, the TTS engine processes three types of text (Refer to the "Narrator Function" help tab for more details):
- > **Narrated Text** : Text enclosed in asterisks *Narrated text*.
- > **Character Text** : Text enclosed in double quotes "Character text".
- > **Text-Not-Inside**: Any text not enclosed in asterisks or double quotes. The handling of this text is customizable.
-
- **Narrator Text-not-inside**
- > Configures how text that is not enclosed in asterisks or double quotes is handled. Detailed explanations are available in the "Narrator Function" help tab.
-
- **Auto-Stop Current Generation**
- > If the loaded model or generation method supports stopping the current TTS generation when sending a new request, this setting will attempt to stop the current process before starting a new one.
-
- ### 🟨 Voice Selection
- **Character (Main TTS) Voice**
- > Select the character voice from the available voices within the currently loaded TTS engine and model.
-
- **Narrator Voice**
- > Choose the narrator voice from the available voices within the currently loaded TTS engine and model.
-
- """
- generation_help2 = """
- ### 🟨 Additional Settings
- **Text Filtering**
- > **None**: No filtering. Raw text is sent to the TTS engine, which may result in odd sounds with some special characters.
- > **HTML**: For HTML content, using HTML entities like emojis or text characters.
- > **Standard**: Human-readable text with basic filtering to clean up special characters.
-
- **Play Locally or Remotely**
- > Choose to play the generated speech locally or on the terminal/console where the server is running.
-
- **Remote Play Volume**
- > Adjust the volume for remote playback.
-
- **Output File Name**
- > Specify the name for the output file.
-
- **Include Timestamp**
- > If enabled, each generated file will include a timestamp to ensure uniqueness. If disabled, the output filename will be used, and the file will be overwritten with each generation.
-
- **Speed**
- > Adjust the speed of the generated speech. This option is not available if the selected TTS engine doesn't support it.
-
- **Pitch**
- >Adjust the pitch of the generated speech. This option is not available if the selected TTS engine doesn't support it.
-
- **Temperature**
- > Control the randomness of the generation process. Higher values result in more varied output. This option is not available if the selected TTS engine doesn't support it.
-
- **Repetition Penalty**
- > Set the penalty for repetitive sequences in the generated speech. This option is not available if the selected TTS engine doesn't support it.
-
- ### 🟨 Output and Controls
- **TTS Result**
- > View the result of the TTS generation process here.
-
- **Playback Controls**
- > Use these controls to play, pause, and navigate through the generated speech.
-
- **Light/Dark Mode**
- > Toggle between light and dark mode for the interface.
-
- **Refresh Server Settings**
- >Refresh the server settings to apply any changes made. This is useful if you have reloaded the engine elsewhere or added new models/voices to your folders. If refreshing doesn't update your voices, you can use the "Swap TTS Engine" button to force a full refresh.
-
- **Interrupt TTS Generation**
- > Manually stop the current TTS generation process, similar to the "Auto-Stop Current Generation" setting but applied manually.
- """
- gr.Markdown(generation_blurb)
- with gr.Row():
- gr.Markdown(generation_help)
- gr.Markdown(generation_help2)
\ No newline at end of file
diff --git a/system/gradio_pages/alltalk_welcome.py b/system/gradio_pages/alltalk_welcome.py
deleted file mode 100644
index 2fa5db5..0000000
--- a/system/gradio_pages/alltalk_welcome.py
+++ /dev/null
@@ -1,62 +0,0 @@
-import os
-import json
-import pathlib as Path
-import gradio as gr
-
-def modify_config():
- config_file = os.path.join("confignew.json")
- with open(config_file, "r") as f:
- config = json.load(f)
- config["firstrun_splash"] = False
- with open(config_file, "w") as f:
- json.dump(config, f, indent=4)
- return f"Welcome screen is disabled."
-
-def alltalk_welcome(analytics_enabled=False):
- welcome = """
- ## 🟧 AllTalk v2 BETA
- For detailed information on using AllTalk, please look at the WIKI Github page https://github.com/erew123/alltalk_tts/wiki for the most up-to-date information, however some information is built in on the Documentation and Help tab section up above.
-
- Please use the **Light/Dark Mode** button at the top right of this page to make it easier to read. I would suggest reading the TLDR's on this page before you disable this splash screen.
- """
-
- welcome2 = """
- ### 🟧 TLDR what you need to do
- > **Generating TTS & Swapping TTS Engines** - You can do this on the Generate TTS tab, Swap Engines (Piper, Parler, VITS, XTTS etc) and also choose which model to load for the currently loaded TTS Engine. There is a Generate help tab on the Generate TTS page for more details.
-
- > **Download Voices/Models** - Go to the `TTS Engines Settings` > Choose the engine you want > `Model/Voices Download`. Additional instructions/information about each TTS engine, its specific settings, how to use it, are all stored here.
-
- > **RVC & RVC Voices** - To make RVC work, you have to enable RVC in `Gloabl Settings` > `RVC Settings`. RVC will download about 800MB of models that it needs to work. [RVC WIKI page link](https://github.com/erew123/alltalk_tts/wiki/RVC-(Retrieval%E2%80%90based-Voice-Conversion))
-
- ### 🟧 Error Message at the Console/Terminal
- > I am mantaining an up-to-date list of known error messages and fixes on the [Error Messages WIKI page](https://github.com/erew123/alltalk_tts/wiki/Error-Messages-List)
-
- ### 🟧 Showing Your Support
- > If AllTalk has been helpful to you, consider showing your support through a donation on my **[Ko-fi page](https://ko-fi.com/erew123)**. Your support is greatly appreciated and helps ensure the continued development and improvement of AllTalk.
- """
-
- welcome3 = """
- ### 🟧 SillyTavern users
- > There is an updated ST extension and instructions for installation are in the [SillyTavern Extension WIKI page](https://github.com/erew123/alltalk_tts/wiki/SillyTavern-Extension).
-
- ### 🟧 DeepSpeed on Linux
- > If you need DeepSpeed for Linux, please see the [DeepSpeed Releases page](https://github.com/erew123/alltalk_tts/releases/tag/DeepSpeed-14.2-Linux).
-
- ### 🟧 Text-generation-webui Remote Extension
- > AllTalk now ALSO has a TGWUI remote server extension, allowing you to install AllTalk outside of TGWUI's Python environment. Please see [TGWUI Remote Extension WIKI page](https://github.com/erew123/alltalk_tts/wiki/Text%E2%80%90generation%E2%80%90webui-Remote-Extension).
-
- ### 🟧 Future features/Feature Requests
- > The current list is available here [Feature Request List page](https://github.com/erew123/alltalk_tts/discussions/74)
-
- ### 🟧 Can I add other TTS engines?
- Yes if you have a bit of coding experience. All the Coqui supported engines should be pretty easy to set up now, I just havnt gotten around it yet. Other engines like Piper and Parler have been done of course, showing other engines can be imported. Rough instructions are in the `system/tts_engines/template` folder.
- """
- with gr.Tab("AllTalk v2 BETA"):
- gr.Markdown(welcome)
- with gr.Row():
- with gr.Column(): gr.Markdown(welcome2)
- with gr.Column(): gr.Markdown(welcome3)
- with gr.Row():
- output = gr.Textbox(label="Output")
- update_btn = gr.Button("Dont show me this screen again!")
- update_btn.click(fn=modify_config, inputs=None, outputs=output)
diff --git a/system/gradio_pages/api_documentation.py b/system/gradio_pages/api_documentation.py
deleted file mode 100644
index c2eec76..0000000
--- a/system/gradio_pages/api_documentation.py
+++ /dev/null
@@ -1,597 +0,0 @@
-import gradio as gr
-
-def api_documentation():
- best_practices = """
- ### 🟠 Best Practices for writing an extension
-
- You will need to store the IP address and Port (minimum) that your extension will be connecting to. Potentially also http/https if you wish to use AllTalk over tunnels. Additionally, you may also wish to store a connection timeout value.
-
- ```
- {
- "api_alltalk_protocol": "http://",
- "api_alltalk_ip_port": "127.0.0.1:7851",
- "api_connection_timeout": 5
- }
- ```
- When your extension starts up you will need to connect to the IP/Port and check the **/api/ready** end point for a 'Ready' status. You may wish to do this on a loop for the period of the connection timeout.
-
- If the timeout is reached without a response, you can set voices, models available, models loaded, basically whatever values you are going to store/present to the user as "Server Offline" or whatever terminology you wish.
-
- If the connection is available, then you would:
-
- > 1) Pull the current settings and populate those into a paramaters variabele. **/api/currentsettings**
- 2) Pull the current voices and populate those into a paramaters variabele. **/api/voices**
- 3) Pull the current rvcvoices and populate those into a paramaters variabele. **/api/rvcvoices**
-
- You can now present those to the user interface, choosing if you are just going to present the voices (narrator or not), RVC voices, model loaded, models available etc.
-
- If you are going to present a multitude of options, please abide by the **xxxxxx_capable** settings provided by **/api/currentsettings**, disabling or enabling options in your user interface as the loaded in TTS engine does or doesnt allow for e.g. if the current TTS engine doesnt support changing your generation audio speed, it will send **"generationspeed_capable": false** over when you use **/api/currentsettings**. As such, you should disable the option to change the generation speed within your interface, however, ultimately, even if not disabled, if the current TTS engine loaded isnt capable of speed generation, changing the generation speed will have no effect on the resulting TTS generation, but you may end up with users asking why!
-
- It is also advisable to have a refresh button to pull make AllTalk update its settings **/api/reload_config** and then you can pull current settings & voices again. This ensures the use always has a way to reconnect to the AllTalk server if its unavailable when your extension starts, or in the scenario that something else was changed in the Gradio interface and now the TTS Engine loaded in is different from when your extension started up.
-
- You can obviously choose if you will give the user access to change the model loaded, TTS engine etc. Please read the documentation on the API endpoints for more information.
-
- If you are presenting all the variables/options to the user, then when you send off a generation request to **/api/tts-generate** send all of the variable/options that are stored within your interface.
-
- If you are NOT presenting all the variables/options to the user, then you have 2x options when sending to **/api/tts-generate**:
-
- > 1) Send all the values/options but hard code some settings in your outbound generation request. This will ensure you always get the TTS generation result you want. Please read the **/api/tts-generate** section for details.
-
- OR
-
- >2) Dont send all the values/options in the generation request. In this scenario, the AllTalk server will populate missing settings from the global API Default Settings and the current TTS Engine's settings that are loaded in. e.g. if you dont provide a character voice, the default set character voice of whatever TTS engine is loaded in will be used. The same goes for all the other settings, except the actual text of the generation request.
-
- Finally, depending on how you are storing your settings, for your extension, you may wish to save the settings each time there is a successful generation request. This ensures that the settings are valid and the connected IP address/port are correct.
- """
-
- settings_endpoints = """
- ### 🟠 Ready Endpoint
- Check if the Text-to-Speech (TTS) engine has started and is ready to accept requests. This endpoint is useful when performing reload model/engine requests as it will return "Unloaded" while the engine is restarting. You can poll this endpoint every second or so to confirm a "Ready" status, indicating AllTalk is ready to process requests. Note that if there is an issue loading the selected model (e.g., missing from the file system), the endpoint will still return "Ready". This status only indicates that the engine has loaded.
-
- **URL**: `http://{ipaddress}:{port}/api/ready`
- **Method**: `GET`
-
- > `curl -X GET "http://127.0.0.1:7851/api/ready"`
-
- Response: `Ready` or `Unloaded`
-
- ### 🟠 Standard Voices List Endpoint
- Retrieve a list of available voices for generating speech that the currently loaded TTS engine and model supports. If the currently loaded TTS engine does not load a model directly (e.g., Piper), the models themselves will be displayed as voices.
-
- **URL**: `http://{ipaddress}:{port}/api/voices`
- **Method**: `GET`
-
- > `curl -X GET "http://127.0.0.1:7851/api/voices"`
-
- ```
- {
- "status": "success",
- "voices": ["voice1", "voice2", "voice3"]
- }
-
- ```
-
- ### 🟠 RVC Voices List Endpoint
- Retrieve a list of available RVC voices for further processing your TTS with the RVC pipeline. `Disabled` will always be included in the list. When `Disabled` is used, the RVC pipeline will not be utilized. If the RVC pipeline is globally disabled in AllTalk, `Disabled` will be the only item in the list. Index files matching their RVC voices will not be displayed; if an index file exists for an RVC voice, it will be automatically selected during generation. If multiple index files exist and it is impossible to automatically select one, a message will be output in the console.
-
- **URL**: `http://{ipaddress}:{port}/api/rvcvoices`
- **Method**: `GET`
-
- > `curl -X GET "http://127.0.0.1:7851/api/rvcvoices"`
-
- ```
- {
- "status": "success",
- "voices": ["Disabled", "folder1\\voice1.pth", "folder2\\voice2.pth", "folder3\\voice3.pth"]
- }
- ```
-
- ### 🟠 Current Settings Endpoint
- Retrieve the current settings of the TTS engine.
-
- **URL**: `http://{ipaddress}:{port}/api/currentsettings`
- **Method**: `GET`
-
- > `curl -X GET "http://127.0.0.1:7851/api/currentsettings"`
-
-
- ```
- {
- "engines_available": ["parler", "piper", "vits", "xtts"],
- "current_engine_loaded": "xtts",
- "models_available": [
- {"name": "xtts - xttsv2_2.0.2"},
- {"name": "apitts - xttsv2_2.0.2"},
- {"name": "xtts - xttsv2_2.0.3"},
- {"name": "apitts - xttsv2_2.0.3"}
- ],
- "current_model_loaded": "xtts - xttsv2_2.0.3",
- "manufacturer_name": "Coqui",
- "audio_format": "wav",
- "deepspeed_capable": true,
- "deepspeed_available": true,
- "deepspeed_enabled": true,
- "generationspeed_capable": true,
- "generationspeed_set": 1,
- "lowvram_capable": true,
- "lowvram_enabled": false,
- "pitch_capable": false,
- "pitch_set": 0,
- "repetitionpenalty_capable": true,
- "repetitionpenalty_set": 10,
- "streaming_capable": true,
- "temperature_capable": true,
- "temperature_set": 0.75,
- "ttsengines_installed": true,
- "languages_capable": true,
- "multivoice_capable": true,
- "multimodel_capable": true
- }
- ```
-
- #### **engines_available**
- > Lists the currently available TTS engines that can be loaded.
-
- #### **current_engine_loaded**
- > Shows the currently loaded TTS engine.
- These can be used together with the **/api/enginereload** endpoint to determine and change the currently loaded engine. Note that swapping the engine using this method can cause the main script process of AllTalk to lose sync with the subprocess. This results in a message indicating that script.py lost control of the subprocess when exiting AllTalk.
-
- #### **models_available**
- > Lists all the models available for the currently loaded TTS engine. Displayed as {engine-name - model-name} from models_available.
-
- #### **current_model_loaded**
- > Shows the currently loaded model.
- You can swap models using the **/api/reload** endpoint by specifying the full {enginename - modelname}.
-
- #### **audio_format**
- > The primary format in which the currently loaded engine produces audio output. This does not account for any global transcoding settings in the AllTalk Settings page but is used by the transcoding process to determine if transcoding is necessary.
-
- #### **deepspeed_capable**
- > Indicates if the current TTS engine supports DeepSpeed and can handle DeepSpeed enable/disable requests.
-
- #### **deepspeed_available**
- > If the current TTS engine is DeepSpeed capable, shows whether DeepSpeed was detected and loaded for use.
-
- #### **deepspeed_enabled**
- > Indicates whether DeepSpeed is currently enabled or disabled for the current TTS engine, assuming the above two are true.
-
- #### **generationspeed_capable**
- > Indicates if the current TTS engine supports generating TTS at different speeds.
-
- #### **generationspeed_set**
- > Shows the current default speed set in the TTS engine's configuration settings.
-
- #### **lowvram_capable**
- > Indicates if the current TTS engine allows moving the TTS model between VRAM and System RAM, which is useful in low VRAM situations, such as when using AllTalk with an LLM that fills up VRAM. This helps keep models contiguous within VRAM and speeds up processing.
-
- #### **lowvram_enabled**
- > If the model is lowvram_capable, setting this to true allows the model to move between VRAM and System RAM as necessary.
-
- #### **pitch_capable**
- > Indicates if the current TTS engine supports generating TTS at different pitches.
-
- #### **pitch_set**
- > Shows the current default audio pitch set in the TTS engine's configuration settings.
-
- #### **repetitionpenalty_capable**
- > Indicates if the current TTS engine supports different repetition penalties for its TTS models.
-
- #### **repetitionpenalty_set**
- > Shows the current default repetition penalty set in the TTS engine's configuration settings.
-
- #### **temperature_capable**
- > Indicates if the current TTS engine supports different temperature settings for its TTS models.
-
- #### **temperature_set**
- > Shows the current default temperature set in the TTS engine's configuration settings.
-
- #### **languages_capable**
- > Indicates if the models within the current TTS engine support multiple languages. This can be used to disable language selection in your interface if necessary.
-
- #### **multivoice_capable**
- > Indicates if the current model loaded into the TTS engine supports multiple voices or just one voice. If a model is not multi-voice capable, you can display 'Default' as the only available voice.
-
- #### **multimodel_capable**
- > Some TTS engines, such as Piper, use models as voices. In such cases, you don't need to load different models but present the models as voices. If a model is not multimodel_capable, it indicates this status in your interface.
-
- """
-
- openapi_endpoint = """
- ### 🟠 OpenAI Speech API Compatible Endpoint
- Validate and generate audio based on text input using an OpenAI Speech v1 API compatible endpoint, as per the documentation at [OpenAI Text-to-Speech Guide](https://platform.openai.com/docs/guides/text-to-speech) and [OpenAI API Reference](https://platform.openai.com/docs/api-reference/audio).
-
- Voices are mapped on a one-to-one basis within the AllTalk Gradio interface on a TTS engine by TTS engine basis. If RVC is globally enabled in AllTalk settings and a voice other than "Disabled" is selected for the character voice, the chosen RVC voice will be applied after the TTS is generated and before the audio is transcoded and sent back out.
-
- Note that there is no capability within the OpenAI API to specify a language, so the response will be in whatever language the currently loaded TTS engine and model support.
-
- **URL**: `http://{ipaddress}:{port}/v1/audio/speech`
- **Method**: `POST`
-
- `curl -X POST "http://127.0.0.1:7851/v1/audio/speech" -H "Content-Type: application/json" -d "{\"model\":\"any_model_name\",\"input\":\"Hello, this is a test.\",\"voice\":\"nova\",\"response_format\":\"wav\",\"speed\":1.0}"`
-
- The request body must be a JSON object with the following fields:
-
- **model** (string): The TTS model to use. Currently ignored, but is required in the request.
- **input** (string): The text to generate audio for. Maximum length is 4096 characters.
- **voice** (string): The voice to use when generating the audio. Supported voices are ["alloy", "echo", "fable", "nova", "onyx", "shimmer"].
- **response_format** (string, optional): The format of the audio. Audio will be transcoded to the requested format.
- **speed** (float, optional): The speed of the generated audio. Must be between 0.25 and 4.0. Default is 1.0. Id the TTS engine loaded doesnt support different speeds, then the audio generated will not abide by this speed request.
-
- ```
- {
- "model": "any_model_name",
- "input": "Hello, this is a test.",
- "voice": "nova",
- "response_format": "wav",
- "speed": 1.0
- }
- ```
-
- Remapping the voices through an API call:
-
- **URL**: `http://{ipaddress}:{port}/api/openai-voicemap`
- **Method**: `POST`
-
- `curl -X PUT "http://localhost:7851/api/openai-voicemap" -H "Content-Type: application/json" -d "{\"alloy\":\"female_01.wav\",\"echo\":\"female_01.wav\",\"fable\":\"female_01.wav\",\"nova\":\"female_01.wav\",\"onyx\":\"male_01.wav\",\"shimmer\":\"male_02.wav\"}"`
-
- You can re-map the 6x voices on the fly, to any voices supported/available for the currently loaded TTS engine by using the above style post. You can use the `curl -X GET "http://127.0.0.1:7851/api/voices"` endpoint to get a list of all currently available voices. Please note that the Gradio interface will not reflect these changes until AllTalk is reloaded as gradio caches the list.
-
- #### Python Example
- ```
- import requests
- import json
-
- # Define the endpoint URL
- url = "http://127.0.0.1:7851/v1/audio/speech"
-
- # Define the request payload
- payload = {
- "model": "any_model_name",
- "input": "Hello, this is a test.",
- "voice": "nova",
- "response_format": "wav",
- "speed": 1.0
- }
-
- # Set the headers
- headers = {
- "Content-Type": "application/json"
- }
-
- # Send the POST request
- response = requests.post(url, data=json.dumps(payload), headers=headers)
-
- # Check the response
- if response.status_code == 200:
- with open("output.wav", "wb") as f:
- f.write(response.content)
- print("Audio file saved as output.wav")
- else:
- print(f"Error: {response.status_code} - {response.text}")
- ```
- #### Javascript Example
- ```
- // Define the endpoint URL
- const url = "http://127.0.0.1:7851/v1/audio/speech";
-
- // Define the request payload
- const payload = {
- model: "any_model_name",
- input: "Hello, this is a test.",
- voice: "nova",
- response_format: "wav",
- speed: 1.0
- };
-
- // Set the headers
- const headers = {
- "Content-Type": "application/json"
- };
-
- // Send the POST request
- fetch(url, {
- method: "POST",
- headers: headers,
- body: JSON.stringify(payload)
- })
- .then(response => {
- if (response.ok) {
- return response.blob();
- } else {
- return response.text().then(text => { throw new Error(text); });
- }
- })
- .then(blob => {
- // Create a link element
- const url = window.URL.createObjectURL(blob);
- const a = document.createElement('a');
- a.style.display = 'none';
- a.href = url;
- a.download = 'output.wav';
-
- // Append the link to the body
- document.body.appendChild(a);
-
- // Programmatically click the link to trigger the download
- a.click();
-
- // Remove the link from the document
- window.URL.revokeObjectURL(url);
- document.body.removeChild(a);
-
- console.log("Audio file saved as output.wav");
- })
- .catch(error => {
- console.error("Error:", error);
- });
- ```
-
- """
-
- interaction_endpoints = """
- ### 🟠 Stop Generation Endpoint
- Interrupt the current TTS generation process. When this endpoint is called, it sets tts_stop_generation in the model_engine to True, which can be used to interrupt the current TTS generation. Stop requests will only be honored if the current TTS engine is capable of handling and can honor a stop request partway through generation.
-
- **URL**: `http://{ipaddress}:{port}/api/stop-generation`
- **Method**: `PUT`
-
- > `curl -X PUT "http://127.0.0.1:7851/api/stop-generation"`
-
- ```
- {
- "message": "Cancelling current TTS generation"
- }
- ```
-
- ### 🟠 Reload Configuration Endpoint
- Reload the TTS engine's configuration and scan for new voices and models. When this endpoint is called, it reloads all current settings from the configuration files and scans for new voices and models that are available. This ensures that subsequent calls to /api/currentsettings, /api/voices, and /api/rvcvoices return an up-to-date set of settings and information about the current state of the TTS engine.
-
- **URL**: `http://{ipaddress}:{port}/api/reload_config`
- **Method**: `GET`
-
- > `curl -X GET "http://127.0.0.1:7851/api/reload_config"`
-
- `Config file reloaded successfully`
-
- ### 🟠 Reload/Swap model
- Can be used to load in/swap to one of the models presented by /api/currentsettings in the `models_available` list.
-
- **URL**: `http://{ipaddress}:{port}/api/reload?tts_method=x`
- **Method**: `POST`
-
- > `curl -X POST "http://127.0.0.1:7851/api/reload?tts_method=xtts%20-%20xttsv2_2.0.2"`
-
- {"status": "model-success"} or {"status": "model-failure"}
-
- ### 🟠 Switch DeepSpeed Endpoint
-
- **URL**: `http://{ipaddress}:{port}/api/deepspeed`
- **Method**: `POST`
-
- > `curl -X POST "http://127.0.0.1:7851/api/deepspeed?new_deepspeed_value=True"`
-
- Replace True with False to disable DeepSpeed mode.
-
- ```
- {
- "status": "deepspeed-success"
- }
- ```
-
- ### 🟠 Switching Low VRAM Endpoint
-
- **URL**: `http://{ipaddress}:{port}/api/lowvramsetting`
- **Method: `POST`
-
- > `curl -X POST "http://127.0.0.1:7851/api/lowvramsetting?new_low_vram_value=True"`
-
- Replace True with False to disable Low VRAM mode.
-
- ```
- {
- "status": "lowvram-success"
- }
- ```
- """
-
- standardtts_endpoint = """
- ### 🟠 TTS Generation Endpoint (Standard Generation)
- Generate TTS audio based on text input. This endpoint supports both character and narrator speech generation. All global settings for the API endpoint can be configured within the AllTalk interface under Global Settings > AllTalk API Defaults. TTS engine-specific settings, such as voices to use or engine parameters, can be set on an engine-by-engine basis in TTS Engine Settings > TTS Engine of your choice.
-
- It's important to note that although you can send all these variables/settings, the loaded TTS engine will only support them if it is capable e.g. you can request a TTS generation in Russian, but if the TTS model that is loaded in only support English, it's only going to generate english sounding text to speech.
-
- **URL**: `http://{ipaddress}:{port}/api/tts-generate`
- **Method**: `POST`
- **Content-Type**: application/x-www-form-urlencoded
-
- ### 🟠 Example Command Lines (Standard Generation)
- Standard TTS generation supports narration and generates a WAV file/blob.
-
- > **Standard TTS Speech Example** - Generate a time-stamped file for standard text and play the audio at the command prompt/terminal:
- `curl -X POST "http://127.0.0.1:7851/api/tts-generate" -d "text_input=All of this is text spoken by the character. This is text not inside quotes, though that doesnt matter in the slightest" -d "text_filtering=standard" -d "character_voice_gen=female_01.wav" -d "narrator_enabled=false" -d "narrator_voice_gen=male_01.wav" -d "text_not_inside=character" -d "language=en" -d "output_file_name=myoutputfile" -d "output_file_timestamp=true" -d "autoplay=true" -d "autoplay_volume=0.8"`
-
- > **Narrator Example** - Generate a time-stamped file for text with narrator and character speech and play the audio at the command prompt/terminal:
- `curl -X POST "http://127.0.0.1:7851/api/tts-generate" -d "text_input=*This is text spoken by the narrator* \\\"This is text spoken by the character\\\;". This is text not inside quotes." -d "text_filtering=standard" -d "character_voice_gen=female_01.wav" -d "narrator_enabled=true" -d "narrator_voice_gen=male_01.wav" -d "text_not_inside=character" -d "language=en" -d "output_file_name=myoutputfile" -d "output_file_timestamp=true" -d "autoplay=true" -d "autoplay_volume=0.8"`
-
- > If your text contains double quotes, escape them with \" (see the narrator example).
- Voices sent in the request have to match the voices available within the TTS engine loaded. Generation requests where the voices dont match, will result nothing being generated and possibly an error message.
-
- Because of the way AllTalk works you can send a request with any mix of settings you wish e.g.
-
- > `curl -X POST "http://127.0.0.1:7851/api/tts-generate" -d "text_input=All of this is text spoken by the character. This is text not inside quotes, though that doesnt matter in the slightest"`
-
- Would generate the TTS for whatver engine is curretly loaded in. It will use the default API settings and default TTS engine settings `Global Settings > AllTalk API Defaults` to populate any missing fields, which gives you flexibility on how you want to send API requests to AllTalk.
-
- ### 🟠 Request Parameters
- #### **text_input**
- > The text you want the TTS engine to produce. Use escaped double quotes for character speech and asterisks for narrator speech if using the narrator function. Example:
- -d "text_input=*This is text spoken by the narrator* \"This is text spoken by the character\". This is text not inside quotes."
-
- #### **text_filtering**
- > Filter for text. Options:
- **none**: No filtering. Raw text is sent to the TTS engine.
- **standard**: Basic level of filtering to clean up special characters.
- **html**: For HTML content with entities like ".
- -d "text_filtering=none"
- -d "text_filtering=standard"
- -d "text_filtering=html"
-
- #### **character_voice_gen**
- > The name of the character's voice file (WAV format).
- -d "character_voice_gen=female_01.wav"
-
- #### **rvccharacter_voice_gen**
- > The name of the RVC voice file for the character. Should be in the format `folder\\file.pth` or the word `Disabled`. When Disabled is sent and RVC is globally enabled, the RVC pipeline will not be used for the character/main voice.
- -d "rvccharacter_voice_gen=folder\\voice1.pth"
-
- #### **rvccharacter_pitch**
- > The pitch for the RVC voice for the character. Should be in the range `-24` up to `24` with `0` being the central point and the global setting, set in the Gradio RVC page being used if the pitch is not specified in the TTS request.
- -d "rvccharacter_pitch=3"
-
- #### **narrator_enabled**
- > Enable or disable the narrator function. If true, minimum text filtering is set to standard.
- -d "narrator_enabled=true"
- -d "narrator_enabled=false"
-
- #### **narrator_voice_gen**
- > The name of the narrator's voice file (WAV format).
- -d "narrator_voice_gen=male_01.wav"
-
- #### **rvcnarrator_voice_gen**
- > The name of the RVC voice file for the narrator. Should be in the format folder\\file.pth or the word Disabled. When Disabled is sent and RVC is globally enabled, the RVC pipeline will not be used for the character/main voice.
- -d "rvcnarrator_voice_gen=folder\\voice2.pth"
-
- #### **rvcnarrator_pitch**
- > The pitch for the RVC voice for the narrator. Should be in the range `-24` up to `24` with `0` being the central point and the global setting, set in the Gradio RVC page being used if the pitch is not specified in the TTS request.
- -d "rvcnarrator_pitch=3"
-
- #### **text_not_inside**
- > Specify the handling of lines not inside double quotes or asterisks for the narrator feature. Options:
- **character**: Treat as character speech.
- **narrator**: Treat as narrator speech.
- **silent**: Ignore text not inside quotes or asterisks.
- -d "text_not_inside=character"
- -d "text_not_inside=narrator"
- -d "text_not_inside=silent"
-
- #### **language**
- > Choose the language for TTS. Options:
- **ar**: Arabic
- **zh-cn**: Chinese (Simplified)
- **cs**: Czech
- **nl**: Dutch
- **en**: English
- **fr**: French
- **de**: German
- **hi**: Hindi (limited support)
- **hu**: Hungarian
- **it**: Italian
- **ja**: Japanese
- **ko**: Korean
- **pl**: Polish
- **pt**: Portuguese
- **ru**: Russian
- **es**: Spanish
- **tr**: Turkish
- -d "language=en"
-
- #### **output_file_name***
- > The name of the output file (excluding the .wav extension).
- -d "output_file_name=myoutputfile"
-
- #### **output_file_timestamp**
- > Add a timestamp to the output file name. If true, each file will have a unique timestamp; otherwise, the same file name will be overwritten each time you generate TTS.
- -d "output_file_timestamp=true"
- -d "output_file_timestamp=false"
-
- #### **autoplay**
- > Enable or disable playing the generated TTS to your standard sound output device at the time of TTS generation.
- -d "autoplay=true"
- -d "autoplay=false"
-
- #### **autoplay_volume**
- > Set the autoplay volume. Should be between 0.1 and 1.0.
- -d "autoplay_volume=0.8"
-
- #### **speed**
- > Set the speed of the generated audio. Should be between 0.25 and 2.0.
- -d "speed=1.0"
-
- #### **pitch**
- > Set the pitch of the generated audio. Should be between -10 and 10.
- -d "pitch=0"
-
- #### **temperature**
- > Set the temperature for the TTS engine. Should be between 0.1 and 1.0.
- -d "temperature=0.75"
-
- #### **repetition_penalty**
- > Set the repetition penalty for the TTS engine. Should be between 1.0 and 20.0.
- -d "repetition_penalty=10"
-
- ### 🟠 TTS Generation Response
- > The API returns a JSON object with the following properties:
- **status**: Indicates whether the generation was successful (generate-success) or failed (generate-failure).
- **output_file_path**: The on-disk location of the generated WAV file.
- **output_file_url**: The HTTP location for accessing the generated WAV file for browser playback.
- **output_cache_url**: The HTTP location for accessing the generated WAV file as a pushed download.
-
- ```
- {
- "status": "generate-success",
- "output_file_path": "C:\\text-generation-webui\\extensions\\alltalk_tts\\outputs\\myoutputfile_1704141936.wav",
- "output_file_url": "/audio/myoutputfile_1704141936.wav",
- "output_cache_url": "/audiocache/myoutputfile_1704141936.wav"
- }
- ```
-
- Note: The response no longer includes the IP address and port number. You will need to add these in your own software/extension.
- """
-
- streamingtts_endpoint = """
- ### 🟠 TTS Generation Endpoint (Streaming Generation)
- Generate TTS audio as a stream. This endpoint does not support narration and will generate an audio stream, not a file. It also does not support the RVC pipeline. Generate and stream TTS audio directly for real-time playback.
-
- **URL**: `http://{ipaddress}:{port}/api/tts-generate-streaming`
- **Method**: POST
- **Content-Type**: application/x-www-form-urlencoded
-
- > `http://127.0.0.1:7851/api/tts-generate-streaming?text=Here%20is%20some%20text&voice=female_01.wav&language=en&output_file=stream_output.wav`
-
- Example JavaScript for Streaming Playback:
-
- ```
- const text = "Here is some text";
- const voice = "female_01.wav";
- const language = "en";
- const outputFile = "stream_output.wav";
- const encodedText = encodeURIComponent(text);
- const streamingUrl = `http://localhost:7851/api/tts-generate-streaming?text=${encodedText}&voice=${voice}&language=${language}&output_file=${outputFile}`;
- const audioElement = new Audio(streamingUrl);
- audioElement.play();
- ```
-
- Response: A StreamingResponse for the audio stream.
-
- Request Parameters:
-
- **text**: The text to convert to speech.
- **voice**: The voice type to use.
- **language**: The language for the TTS.
- **output_file**: The name of the output file.
- **Response**: A JSON object indicating the status of the request.
-
- ```
- {
- "output_file_path": "stream_output.wav"
- }
- ```
- """
-
- with gr.Tab("API Endpoints & Dev"):
- with gr.Tab("Best Development Practices"): gr.Markdown(best_practices)
- with gr.Tab("Standard TTS Generation"): gr.Markdown(standardtts_endpoint)
- with gr.Tab("Streaming TTS Generation"): gr.Markdown(streamingtts_endpoint)
- with gr.Tab("Settings API's"): gr.Markdown(settings_endpoints)
- with gr.Tab("Interaction API's"): gr.Markdown(interaction_endpoints)
- with gr.Tab("OpenAPI Compatible Endpoint"): gr.Markdown(openapi_endpoint)
\ No newline at end of file
diff --git a/system/gradio_pages/help_content.py b/system/gradio_pages/help_content.py
new file mode 100644
index 0000000..0ff339e
--- /dev/null
+++ b/system/gradio_pages/help_content.py
@@ -0,0 +1,2986 @@
+# 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;
+ }
+
+ .fixed-accordion {
+ max-height: 440px; /* or any desired height */
+ overflow: auto; /* Ensures content is scrollable if it overflows */
+ }
+
+ .fixed-plot {
+ height: 400px; /* Set a fixed height for the plot */
+ }
+
+ .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 */
+ }
+
+ .small-file-upload {
+ min-height: 200px;
+ max-height: 200px;
+ }
+
+ .small-file-upload2 {
+ min-height: 90px;
+ max-height: 90px;
+ }
+
+ """
+
+ WELCOME = """
+ # Welcome to AllTalk
+ Thanks for trying out AllTalk! Below you'll find information about getting help, reporting issues, and contributing to the project. If you're just getting started, you might want to check out the [Quick Start Guide](https://github.com/erew123/alltalk_tts/wiki/AllTalk-V2-QuickStart-Guide).
+ """
+
+ WELCOME1 = """
+ ## 📚 Documentation and Features
+ The Gradio interface includes expandable accordions throughout, providing help for each page and its settings/features. For more detailed information about features, settings, setup, and third-party add-ons, check out the [GitHub Wiki](https://github.com/erew123/alltalk_tts/wiki).
+
+ 💡 Tip: Use the Light/Dark Mode button in the top right corner for easier reading.
+
+ ## ⚠️ Errors and Issues
+ You can find solutions to all known errors and issues in the [Error Messages List](https://github.com/erew123/alltalk_tts/wiki/Error-Messages-List) on the GitHub Wiki. Also keep an eye on the terminal/console where AllTalk is running, as it provides detailed output for troubleshooting.
+
+ ## 💡Support and Help
+ If you need help, here's where to look:
+ - The help sections within the interface
+ - The [GitHub Wiki](https://github.com/erew123/alltalk_tts/wiki)
+ - The [Discussion forums](https://github.com/erew123/alltalk_tts/discussions)
+ - Past solutions in [GitHub Issues](https://github.com/erew123/alltalk_tts/issues)
+
+ For detailed troubleshooting guidance and debugging tools, check out the [Support Section](https://github.com/erew123/alltalk_tts/wiki#-support--help).
+ """
+
+ WELCOME2 = """
+ ## 🚀 Feature Requests
+ I track feature requests in a dedicated thread in the [GitHub Discussions area](https://github.com/erew123/alltalk_tts/discussions/74). While I'm open to suggestions, I can't guarantee I'll implement every request. If you have a specific feature in mind, please add it to the discussion.
+
+ ## 🤝 Contributing to AllTalk
+ I welcome contributions to AllTalk and am grateful to anyone willing to contribute. While I don't have specific guidelines, I'd ask that you familiarize yourself with the [GitHub Wiki](https://github.com/erew123/alltalk_tts/wiki) to understand how AllTalk works. If you'd like to contribute, please drop me a message in the discussions area describing what you'd like to work on - this helps avoid duplicate efforts and lets me share any relevant development plans.
+
+ ## 💖 Sponsor AllTalk
+ I've developed AllTalk in my free time, handling everything from coding and documentation to testing and community support. It's grown far beyond what I initially imagined, requiring significant time and effort to maintain and improve. If you'd like to support my work, you can make a one-time or ongoing contribution through my [Ko-Fi page](https://ko-fi.com/erew123). Your support means a lot to me!
+ """
+
+ DEBUG_HELP1 = """
+ ## 🔍 Function and Component-Specific Debug Options
+
+ - **debug_func**
+ - Tracks function entry points throughout the application
+ - Shows the execution flow and function call hierarchy
+ - Essential for understanding the program's execution path
+ - Example: `[AllTalk TTS] Debug debug_func Function entry: generate_audio`
+
+ **Note:** When debugging with `debug_func`, remember that functions often call other functions. The debug output shows the most recently entered function, but the actual issue might originate in a parent function. For effective debugging, review the sequence of function entries leading up to an error. For example:
+
+ ```
+ [AllTalk TTS] Debug debug_func Function entry: process_text
+ [AllTalk TTS] Debug debug_func Function entry: clean_text
+ [AllTalk TTS] Debug debug_func Function entry: generate_audio
+ [AllTalk TTS] Error in generate_audio
+ ```
+
+ Here, while the error appears in `generate_audio`, the root cause might be in how `process_text` or `clean_text` prepared the data.
+
+ - **debug_api**
+ - Logs API endpoint interactions and requests
+ - Shows incoming request data and response statuses
+ - Useful for troubleshooting API integrations & calls
+ - Example: `[AllTalk API] Debug debug_api Successfully retrieved 19 available voices`
+
+ - **debug_tts**
+ - Monitors core TTS generation processes
+ - Shows generation progress, timing, and status updates
+ - Helps diagnose basic TTS generation issues
+ - Use `debug_openai` for OpenAI TTS generation
+ - Example: `[AllTalk GEN] Debug debug_tts Starting TTS generation for file: output_12345.wav`
+
+ - **debug_tts_variables**
+ - Displays detailed TTS generation parameters
+ - Shows voice settings, language options, and generation parameters etc
+ - Useful for verifying correct parameter passing
+ - Example: `[AllTalk GEN] Debug debug_tts_variables temperature: 0.75, pitch: 1.0`
+
+ - **debug_narrator**
+ - Tracks narrator mode text processing
+ - Shows how text is split between narrator and character voices
+ - Helps debug multi-voice generation issues
+ - Example: `[AllTalk GEN] Debug debug_narrator Narrator voice: voice_A`
+ """
+
+ DEBUG_HELP2 = """
+ - **debug_fullttstext**
+ - Shows complete text being processed for TTS
+ - Without this, text is truncated to 90 characters in logs
+ - Useful when troubleshooting long text processing
+ - Example: `[AllTalk GEN] Debug debug_fullttstext Full text: [entire text content]`
+
+ - **debug_gradio_IP**
+ - Monitors URL/IP configuration used by Gradio and environment status
+ - Shows runtime environment (Docker/Colab) and active AllTalk API endpoint set
+ - Helps diagnose connection issues between Gradio UI and AllTalk's API server
+ - Example: `[AllTalk TTS] Debug debug_gradio_IP Base URL is set as : http://127.0.0.1:7851/api/voices`
+
+ ## 🔍 Audio Processing Debug Options
+
+ - **debug_transcode**
+ - Monitors audio format conversion processes
+ - Shows input/output formats and transcoding steps
+ - Useful for troubleshooting audio format issues
+ - Use `debug_openai` for OpenAI endpoint transcoding
+ - Example: `[AllTalk TTS] Debug debug_transcode Converting wav to mp3`
+
+ - **debug_concat**
+ - Tracks audio file concatenation operations within the Narrator
+ - Shows files being combined and resulting output
+ - Helps debug multi-part audio assembly
+ - Example: `[AllTalk TTS] Debug debug_concat Combining files: [file1.wav, file2.wav]`
+
+ - **debug_rvc**
+ - Monitors RVC (Retrieval-based Voice Conversion) processes
+ - Shows model loading, voice conversion steps
+ - Essential for troubleshooting voice conversion issues
+ - Example: `[AllTalk GEN] Debug debug_rvc Processing with model: character_voice.pth`
+
+ - **debug_openai**
+ - Tracks OpenAI API integration
+ - Shows OpenAI-specific request parameters and responses
+ - Useful for troubleshooting OpenAI voice generation
+ - Example: `[AllTalk TTS] Debug debug_openai Using voice: alloy`
+
+ ## 🔍 Usage Tips
+ - Enable multiple debug options together for comprehensive logging
+ - Use `debug_func` with other options to track execution flow
+ - Enable `debug_fullttstext` only when necessary to avoid log clutter
+ - Combine related options (e.g., `debug_tts` + `debug_tts_variables`) for detailed troubleshooting
+ """
+
+ ALLTALK_SETTINGS_PAGE1 = """
+ ## ⚙️ Global Settings Help
+
+ The **Global Settings** page allows you to configure essential system-wide parameters and enable various debugging options for detailed troubleshooting. This section provides an overview of each setting, its purpose, and guidelines for usage.
+
+ ## ⚙️ File Management
+
+ - **Del WAV's Older Than**: On start-up automatically deletes WAV files older than the specified duration to save disk space.
+ - **Default**: `Disabled`
+ - **Options**: Disabled or specify a duration (e.g., 7 days, 30 days).
+ - **Recommendation**: Set at least 30 days to keep your outputs folder from growing too large.
+
+ - **Output Folder Name (sub AllTalk)**: Defines the subfolder where all generated files will be stored.
+ - **Default**: `outputs`
+ - **Recommendation**: Preferably do not change this.
+ - **Tip**: Ensure the folder name is unique for clarity when managing multiple projects.
+
+ ## ⚙️ API and Gradio Interface Settings
+
+ - **API Port Number**: The port through which the AllTalk API is accessible.
+ - **Default**: `7851`
+ - **Note**: As standard AllTalk binds to 0.0.0.0 meaning all available IP addresses on your machine.
+ - **Note**: Ensure this port is not blocked by firewalls or used by other applications.
+
+ - **Gradio Port Number**: The port number for the Gradio interface used for the frontend.
+ - **Default**: `7852`
+ - **Note**: As standard AllTalk binds to 0.0.0.0 meaning all available IP addresses on your machine.
+ - **Note**: Ensure this port is not blocked by firewalls or used by other applications
+
+ - **Gradio Theme Selection**: Customize the appearance of the Gradio interface by choosing a theme.
+ - **Options**: `gradio/base` (default) or other themes supported by Gradio.
+ - **Note**: A restart is required for the change to take effect.
+
+ - **Gradio Interface**: Enables or disables the Gradio interface.
+ - **Default**: `Enabled`
+ - **Warning**: Disabling this option will disable Gradio from loading.
+ - **Tip**: You can re-enable the gradio interface by using the API web page (e.g., `http://127.0.0.1:7851/`).
+ """
+
+ ALLTALK_SETTINGS_PAGE2 = """
+ ## ⚙️ Transcoding
+
+ - **Audio Transcoding**
+ - **Default**: `Disabled`
+ - Enables or disables automatic audio format conversion for compatibility with the system.
+ - **Recommendation**: Enable only if your workflow requires consistent audio formats (e.g., converting WAV to MP3).
+ - **Note**: Ensure this port is not blocked by firewalls or used by other applications
+
+ ## 🔍 Debugging Options
+
+ #### Purpose
+ The debugging options provide granular insights into various processes, such as TTS generation, API interactions, and audio processing. Enabling these options can help identify and resolve issues during system usage.
+
+ - **Available Debug Options**
+ - `debug_func`, `debug_tts`, `debug_rvc`, etc. (See the Debugging Help Section for detailed descriptions).
+
+ #### Usage Tips**
+
+ - Enable relevant debugging categories based on the issue being investigated.
+ - Combine `debug_func` with other options for tracing execution paths.
+ - Avoid enabling all options simultaneously to prevent log clutter.
+
+ ## ⚙️ Interface Tab Management
+
+ #### Disable Gradio Interface Tabs
+ Use these checkboxes to hide specific tabs in the Gradio interface, simplifying the user interface for specific workflows.
+
+ - **Tabs Available**
+ - **Generate Help**: Displays help documentation.
+ - **Voice2RVC**: Access the Voice-to-RVC tools.
+ - **TTS Generator**: Manage TTS generation tasks.
+ - **TTS Engines Settings**: Configure and manage TTS engines.
+ - **AllTalk Documentation**: View detailed documentation and usage instructions.
+ - **API Documentation**: Reference API endpoints and integration details.
+ """
+
+ RVC_PAGE1 = """
+ The **RVC Settings** page provides configuration options for Real-Time Voice Cloning (RVC), a feature that enhances TTS by replicating voice characteristics for characters or narrators. This section provides detailed descriptions of the available settings and recommendations for their usage.
+
+ ## 🗣️ Setup and Model Management
+
+ - **Enable RVC**:
+ - **Purpose**: Toggles the Real-Time Voice Cloning feature & downloads the base RVC model required.
+ - **Default**: Disabled
+ - **Recommendation**: Enable this only if you plan to use RVC-enhanced TTS.
+ - **Tip**: Enable this to download and setup your base RVC models and folders.
+
+ - **Refresh Model Choices**:
+ - **Purpose**: Refreshes the list of available voice models from the `/models/rvc_voices` directory.
+ - **Tip**: Use this after adding new voice models to ensure they are recognized by the system.
+
+ ## 🗣️ Voice Model Configuration
+
+ - **Default Character Voice Model**
+ - **Purpose**: Selects the voice model used for character conversion if none other specifed in the API request.
+ - **Default**: Disabled
+ - **Recommendation**: Assign a model here if you regularly use RVC for character voices and no model is specified in API requests.
+
+ - **Default Narrator Voice Model**
+ - **Purpose**: Selects the voice model used for narrator conversion if none other specifed in the API request.
+ - **Default**: Disabled
+ - **Recommendation**: Assign a model here if you regularly use RVC for narrator voices and no model is specified in API requests.
+
+ ## 🗣️ Conversion Parameters
+
+ - **Pitch**
+ - **Purpose**: Adjusts the pitch of the audio output.
+ - **Default**: `0` (no pitch adjustment)
+ - **Range**: Any positive or negative value.
+ - **Recommendation**: Use small adjustments to fine-tune the output pitch. Larger values can distort the audio.
+
+ - **Volume Envelope**
+ - **Purpose**: Determines how the output volume envelope blends with the original.
+ - **Default**: `1` (fully blends with the output envelope)
+ - **Range**: `0.1` to `1`
+ - **Tip**: Use higher values for natural blending, especially for speech-focused TTS.
+
+ - **Protect Voiceless Consonants/Breath Sounds**
+ - **Purpose**: Reduces artifacts in voiceless consonants and breath sounds.
+ - **Default**: `0.5`
+ - **Range**: `0.1` to `0.5`
+ - **Recommendation**: Use the default value for most cases to preserve sound quality without overloading the index.
+
+ - **Filter Radius**
+ - **Purpose**: Applies median filtering to reduce respiration artifacts.
+ - **Default**: `3`
+ - **Range**: `1` to `5`
+ - **Recommendation**: Higher values are recommended for smoother audio but may slightly reduce naturalness.
+ """
+
+ RVC_PAGE2 = """
+ ## 🗣️ Advanced Settings
+
+ - **Index Influence Ratio**
+ - **Purpose**: Adjusts the weight of the index file in shaping the output.
+ - **Default**: `0.75`
+ - **Range**: `0.1` to `1`
+ - **Recommendation**: Increase for highly detailed voices, but keep lower for natural-sounding output.
+
+ - **Training Data Size**
+ - **Purpose**: Sets the number of data points used for training the FAISS index.
+ - **Default**: `45000`
+ - **Range**: Based on the index file size.
+ - **Tip**: Increase for complex voices, but monitor system performance.
+
+ ## 🗣️ Embedder and Audio Handling
+
+ - **Embedder Model**
+ - **Purpose**: Selects the model used for learning speaker embedding.
+ - **Options**:
+ - **hubert**: Focuses on phonetic accuracy.
+ - **contentvec**: Captures subtle vocal nuances.
+ - **Default**: `hubert`
+ - **Recommendation**: Use `contentvec` for more natural and expressive voice cloning.
+
+ - **Split Audio**
+ - **Purpose**: Splits audio into smaller chunks for better inference.
+ - **Default**: Enabled
+ - **Recommendation**: Leave enabled for longer audio inputs.
+
+ ## 🗣️ Pitch Extraction
+
+ - **Pitch Extraction Algorithm**
+ - **Purpose**: Determines the algorithm used for pitch extraction during audio conversion.
+ - **Options**:
+ - **crepe**: High accuracy, robust against noise.
+ - **crepe-tiny**: Faster but slightly less accurate.
+ - **dio**: Efficient, suitable for real-time usage.
+ - **fcpe**: Precise pitch extraction.
+ - **harvest**: Smooth and natural pitch contours.
+ - **hybrid[rmvpe+fcpe]**: Combines strengths of `rmvpe` and `fcpe`.
+ - **pm**: Balanced speed and accuracy.
+ - **rmvpe**: Recommended for most cases.
+ - **Default**: `rmvpe`
+ - **Recommendation**: Use `rmvpe` or `hybrid` for general cases; experiment with others for specific needs.
+
+ ## 🗣️ Usage Tips
+
+ 1. **Optimize Index Influence**: Adjust the ratio for better clarity and detail without introducing artifacts.
+ 2. **Refine Pitch and Volume**: Use small, incremental changes to maintain naturalness.
+ 3. **Choose the Right Embedder**: Experiment with `contentvec` for expressive voices or `hubert` for precise replication.
+ 4. **Test Pitch Algorithms**: The default (`rmvpe`) works well for most scenarios, but alternative options can enhance specific audio characteristics.
+ """
+
+ API_DEFAULTS1 = """
+ ## 🎯 Quick Start Guide
+ The **API Settings** page allows you to configure global defaults and behaviors for all AllTalk API calls. These settings serve as fallbacks when specific parameters aren't provided in individual API requests.
+
+ ### 🎯 Key Concepts:
+ 1. **Default Values**: These settings act as fallbacks when parameters aren't specified in API calls
+ 2. **Global Filters**: Control what text/characters can be processed system-wide
+ 3. **Version Compatibility**: Manage how API responses are formatted
+ 4. **Playback Behavior**: Configure how generated audio is handled
+
+ ### 🎯 Common Use Cases:
+ - Setting system-wide language preferences
+ - Configuring output file naming conventions
+ - Managing text filtering and character support
+ - Controlling audio playback behavior
+
+ ## 🎯 Understanding API Defaults
+
+ API defaults determine how AllTalk behaves when specific parameters are omitted from API requests. Think of them as your system's "automatic" settings.
+
+ ### How Defaults Work:
+ 1. When an API request includes a specific parameter, it overrides the default
+ 2. When a parameter is omitted, the system uses the value configured here
+ 3. Defaults apply to all API calls unless explicitly overridden
+
+ ## 🎯 Example Scenarios:
+
+ **Language Selection:**
+ ```plaintext
+ # With language in request:
+ POST /tts {"text": "Hello", "language": "fr"}
+ → Uses French, ignoring default
+
+ # Without language:
+ POST /tts {"text": "Hello"}
+ → Uses default language set in API Settings
+ ```
+
+ **Text Filtering:**
+ ```plaintext
+ # With filter specified:
+ POST /tts {"text": "", "filter": "html"}
+ → Uses HTML filtering
+
+ # Without filter:
+ POST /tts {"text": ""}
+ → Uses default filter setting
+ ```
+
+ ## 🎯 Important Notes:
+ - Default settings affect ALL API calls
+ - Changes require server restart to take effect
+ - Monitor logs when changing defaults to ensure desired behavior
+ - Test API calls after changing defaults to verify results
+ """
+
+ API_DEFAULTS2 = """
+ ## 🔄 API Version Selection
+ Choose which API response format your system should use:
+
+ - **AllTalk v2 API (Recommended):**
+ - Returns relative file paths only
+ - More flexible for different deployments
+ - Example response:
+ ```json
+ {
+ "output_file": "/outputs/tts_output.wav",
+ "status": "success"
+ }
+ ```
+ - Best for:
+ - New integrations
+ - Modern web applications
+ - Flexible deployment environments
+ - Container-based systems
+
+ - **AllTalk v1 API (Legacy):**
+ - Returns complete URLs with protocol and IP
+ - Maintains older integration support
+ - Example response:
+ ```json
+ {
+ "output_file": "http://127.0.0.1:7851/outputs/tts_output.wav",
+ "status": "success"
+ }
+ ```
+ - Best for:
+ - Existing integrations
+ - Systems requiring full URLs
+ - Direct file access needs
+ - Backward compatibility
+
+ ## 🔄 Legacy API Configuration
+ Settings specific to v1 API functionality:
+
+ - **IP Address Setting:**
+ - Only applies when using v1 API mode
+ - Default value: `127.0.0.1`
+ - Configuration options:
+ - Local testing: `127.0.0.1`
+ - Network access: Your server's IP
+ - Domain usage: Your domain name
+ - Important considerations:
+ - Must be accessible to client systems
+ - Affects all v1 API responses
+ - Impacts Gradio interface functionality
+ - Required for legacy client compatibility
+
+ ## 🔄 Integration Notes
+
+ - **API Version Choice:**
+ - New projects should use v2 API
+ - Only use v1 if specifically needed
+ - Can't mix versions in same client
+ - Version affects all API endpoints
+
+ - **URL Construction:**
+ - V2: Client builds full URLs
+ - V1: Server provides full URLs
+ - Consider security implications
+ - Check firewall/network access
+
+ - **Compatibility Checking:**
+ - Test API responses after changes
+ - Verify client handling
+ - Check file access methods
+ - Confirm URL resolution
+ """
+
+ API_DEFAULTS3 = """
+ ## 🎛️ Character Limits
+ Control text processing boundaries and system load:
+
+ - **Minimum Length Filter:**
+ - Strips sentences shorter than specified length
+ - Acts as first-pass text cleanup
+ - Examples:
+ ```
+ Setting: 3 characters
+ "Hi" → filtered out
+ "Hey" → processed
+ "Hello" → processed
+ ```
+ - Considerations:
+ - Lower values allow shorter utterances
+ - Higher values reduce processing overhead
+ - Recommended: 3 characters
+ - Affects all API requests
+
+ - **Maximum Request Size:**
+ - Controls total characters per API call
+ - Prevents system overload
+ - Default: 2000 characters
+ - Important factors:
+ - Server resources
+ - TTS engine limits
+ - Processing time
+ - Memory usage
+ - Example:
+ ```
+ 2000 chars ≈ 300-400 words
+ Longer texts should be split into multiple requests
+ ```
+
+ ## 🎛️ Output File Management
+ Configure how the system handles generated audio files:
+
+ - **Base Filename:**
+ - Sets default output name pattern
+ - Used when no filename provided
+ - Example settings:
+ ```
+ Basic: myoutputfile
+ With prefix: user1_output
+ With category: audiobook_chapter1
+ ```
+ - Tips:
+ - Use descriptive names
+ - Avoid spaces
+ - Consider adding prefixes
+ - Keep names consistent
+
+ - **Timestamp Options:**
+ - **Enabled (Recommended):**
+ - Adds unique timestamp to each file
+ - Format: `filename_YYYYMMDD_HHMMSS.wav`
+ - Prevents file overwrites
+ - Maintains history
+ - Example: `myoutputfile_20240320_143022.wav`
+
+ - **Disabled:**
+ - Uses exact filename specified
+ - Overwrites existing files
+ - Example: `myoutputfile.wav`
+ - Use with caution
+
+ ## 🎛️ Language Settings
+ Define default language behavior:
+
+ - **Default Language:**
+ - Used when no language specified in request
+ - Affects:
+ - Text processing
+ - Pronunciation
+ - Character handling
+ - Voice selection
+ - Available codes:
+ ```
+ en: English de: German fr: French
+ es: Spanish it: Italian ja: Japanese
+ zh: Chinese ru: Russian ko: Korean
+ ```
+ - Notes:
+ - Must match TTS engine capabilities
+ - Can be overridden per request
+ - Affects all unspecified requests
+ - Consider your primary use case
+
+ ## Request Processing Tips
+
+ - **Optimal Request Size:**
+ - Keep requests under 1500 characters for best performance
+ - Split long texts at natural breaks
+ - Consider paragraph or sentence boundaries
+ - Balance between size and processing speed
+
+ - **File Organization:**
+ - Use consistent naming conventions
+ - Consider adding date-based folders
+ - Implement regular cleanup
+ - Monitor disk usage
+ """
+
+ API_DEFAULTS4 = """
+ ## 📝 Text Processing & Filtering
+
+ ## 📝 Text Filtering Modes
+ Three levels of processing to handle different input types:
+
+ - **none:**
+ - Raw, unfiltered text processing
+ - No character filtering applied
+ - Maximum flexibility
+ - Use cases:
+ - Pre-filtered content
+ - Known clean input
+ - Testing purposes
+ - Warning: May expose TTS to problematic characters
+
+ - **standard (Recommended):**
+ - Balanced filtering approach
+ - Removes problematic characters
+ - Maintains readability
+ - Handles:
+ - Common punctuation
+ - Multiple languages
+ - Basic formatting
+ - Special characters
+ - Best for most use cases
+
+ - **html:**
+ - Specialized HTML content handling
+ - Strips HTML tags
+ - Preserves content
+ - Example transformations:
+ - `
+ - **∿ Low Frequency (Hz)** *(Range: 50 - 120 Hz)*
+ - **Default Value:** 85 Hz
+ - **Lower Values (e.g., 50–70 Hz):**
+ - Recommended for deeper male voices or audio with a broader vocal range.
+ - Useful when preserving low vocal harmonics in clean recordings.
+ - **Higher Values (e.g., 100–120 Hz):**
+ - Filters out low-frequency background hums (e.g., AC noise, mic handling noise).
+ - Ideal for recordings with rumble or low-frequency distortion.
+
+ - **∿ High Frequency (Hz)** *(Range: 3000 - 5000 Hz)*
+ - **Default Value:** 3800 Hz
+ - **Lower Values (e.g., 3000–3500 Hz):**
+ - Reduces high-pitched noise, hissing, or squeals in noisy environments.
+ - Useful for telephone-quality recordings or audio with limited frequency range.
+ - **Higher Values (e.g., 4000–5000 Hz):**
+ - Captures more speech detail and overtones, particularly for tonal languages or professionally recorded audio.
+ - Suitable for higher-quality recordings where clarity is paramount.
+
+ **Use Bandpass Filter in Noisy Environments:** Helps focus on human speech frequencies while ignoring irrelevant noise.
+
+ **Skip Bandpass for Pristine Audio:** For high-quality recordings, skipping this filter ensures no speech frequencies are lost.
+ """
+
+ WHISPER_HELP2 = """
+ ### Timing and Organization
+ - **Add Timestamps**:
+ * Adds time markers to transcription
+ * Useful for long recordings
+ * Format: `[HH:MM:SS] Text`
+
+ - **Enable Speaker Diarization**:
+ * Attempts to identify different speakers
+ * Basic implementation - best for clear speaker transitions
+ * Format: `Speaker 1: Text`
+
+ - **Silence Threshold** *(Range: 0.001 - 0.02)*
+ This setting determines the amplitude below which audio is treated as silence. Adjusting this is essential for balancing noise filtering and capturing soft speech.
+ - **Default Value:** 0.008
+ - **Use Higher Values (e.g., 0.01–0.02):**
+ - Reduces false triggers from faint background noise (e.g., air conditioners, hums).
+ - Suitable for noisier environments where background noise needs to be suppressed.
+ - **Use Lower Values (e.g., 0.001–0.007):**
+ - Helps capture quieter or softer speech.
+ - Ideal for high-quality recordings with minimal background noise.
+
+ **Tip:** Monitor the **Audio Levels graph** during preprocessing or testing to ensure speech is detected while avoiding unnecessary noise capture. Fine-tune based on the quietest speaker in your audio.
+
+ ### **Additional Notes for Optimization:**
+ - **Experiment with Combined Settings:**
+ - Pair a **higher Silence Threshold** (e.g., 0.01) with a **narrower Bandpass range** (e.g., 100–3500 Hz) in noisy environments.
+ - Use a **lower Silence Threshold** (e.g., 0.005) and a **wider Bandpass range** (e.g., 50–4000 Hz) for high-quality or wide-spectrum recordings.
+ - **Adapt Based on Speaker Profiles:**
+ - Adjust **Low Frequency** for male vs. female speakers.
+ - Raise **High Frequency** for tonal clarity in languages like Mandarin or musical recordings.
+
+ ## 📊 Real-Time Feedback
+
+ ### Audio Levels Graph
+ - Shows audio input strength in real-time
+ - **How to Read**:
+ * X-axis: Time
+ * Y-axis: Audio level
+ * **Good levels**: Consistent peaks without flatlines or clipping
+ * **Too low**: Flat or minimal movement
+ * **Too high**: Constant maximum peaks
+
+ ## 🔄 Workflow
+
+ 1. **Setup**:
+ - Select Whisper model based on your GPU's VRAM
+ - Choose output format and optional file name prefix
+ - Configure advanced settings if needed
+
+ 2. **Recording**:
+ - Click "Load Model" to initialize
+ - Press "Record" to start transcription
+ - Speak clearly at a consistent volume
+ - Watch Audio Levels for feedback
+ - Press "Stop" to pause/resume
+ - Use "Finish & Unload" when completely done
+
+ 3. **File Management**:
+ - Transcripts auto-save to `transcriptions/live_dictation/`
+ - Each session creates new timestamped files
+ - Multiple formats saved if selected
+
+ ## ⚠️ Important Notes
+
+ - **VRAM Usage**:
+ * Monitor GPU memory
+ * Close other GPU applications
+ * Restart application if performance degrades
+
+ - **Best Practices**:
+ * Speak clearly and at consistent volume
+ * Position microphone correctly
+ * Use Audio Levels graph for feedback
+
+ - **Performance Tips**:
+ * Use `large-v3-turbo` for best speed/quality balance
+ * Enable audio processing only when needed
+ * Refresh page between sessions if issues occur
+ """
\ No newline at end of file
diff --git a/system/requirements/requirements_colab.txt b/system/requirements/requirements_colab.txt
index 6012944..cecf0c0 100644
--- a/system/requirements/requirements_colab.txt
+++ b/system/requirements/requirements_colab.txt
@@ -1,14 +1,13 @@
coqui-tts>=0.24.3
-faster-whisper>=1.0.0
+openai-whisper
+filelock>=3.16.1
fuzzywuzzy
-gradio==4.32.2
+gradio==4.44.1
importlib_metadata>=7.1.0
inputimeout>=1.0.4
Jinja2>=3.1.0
librosa>=0.10.2.post1
local_attention>=1.9.0
-nvidia-cublas-cu11==11.11.3.6
-nvidia-cudnn-cu11==9.1.1.17
onnxruntime-gpu>=1.18.0
pydantic>=2.7.0
python-ffmpeg>=2.0.0
@@ -44,4 +43,5 @@ word2number
cutlet==0.4.0
fugashi==1.3.1
piper-tts; sys_platform == "linux"
-ctranslate2==4.4.0
+plotly==5.24.1
+scipy==1.14.1
\ No newline at end of file
diff --git a/system/requirements/requirements_standalone.txt b/system/requirements/requirements_standalone.txt
index e49cccc..1a75c40 100644
--- a/system/requirements/requirements_standalone.txt
+++ b/system/requirements/requirements_standalone.txt
@@ -1,6 +1,7 @@
coqui-tts>=0.24.3
+filelock>=3.16.1
fuzzywuzzy>=0.18.0
-gradio>=4.44.1
+gradio==4.44.1
importlib_metadata>=7.2.1
inputimeout>=1.0.4
Jinja2>=3.1.4
@@ -32,3 +33,5 @@ piper-tts; sys_platform == "linux"
piper-tts; sys_platform == "darwin"
piper-phonemize==1.1.0; sys_platform == "darwin"
fastapi==0.112.2
+plotly==5.24.1
+scipy==1.14.1
\ No newline at end of file
diff --git a/system/requirements/requirements_textgen.txt b/system/requirements/requirements_textgen.txt
index dd874c0..e7313a5 100644
--- a/system/requirements/requirements_textgen.txt
+++ b/system/requirements/requirements_textgen.txt
@@ -1,4 +1,5 @@
coqui-tts>=0.24.3
+filelock>=3.16.1
fuzzywuzzy>=0.18.0
importlib_metadata>=7.1.0
inputimeout>=1.0.4
@@ -29,3 +30,5 @@ fugashi==1.3.1
piper-tts; sys_platform == "linux"
piper-tts; sys_platform == "darwin"
piper-phonemize==1.1.0; sys_platform == "darwin"
+plotly==5.24.1
+scipy==1.14.1
diff --git a/system/requirements/requirements_unit_test.txt b/system/requirements/requirements_unit_test.txt
new file mode 100644
index 0000000..e7c14f1
--- /dev/null
+++ b/system/requirements/requirements_unit_test.txt
@@ -0,0 +1,4 @@
+filelock>=3.16.1
+pydantic>=2.7.0
+pytest>=8.3.3
+pytest-cov>=6.0.0
\ No newline at end of file
diff --git a/system/tts_diff/tts_diff.py b/system/tts_diff/tts_diff.py
index 962e6fd..6690f31 100644
--- a/system/tts_diff/tts_diff.py
+++ b/system/tts_diff/tts_diff.py
@@ -28,6 +28,8 @@ parser = argparse.ArgumentParser(description="Compare TTS output with the origin
parser.add_argument("--threshold", type=int, default=98, help="Similarity threshold for considering a match (default: 98)")
parser.add_argument("--ttslistpath", help="Path to the ttsList.json file")
parser.add_argument("--wavfilespath", help="Path to the wav outputs folder")
+parser.add_argument("--whisper-model", type=str, default="large-v3", help="Whisper model to use (default: large-v3)")
+
args = parser.parse_args()
if not args.ttslistpath:
@@ -38,6 +40,7 @@ if not args.wavfilespath:
json_file_path = Path(args.ttslistpath).resolve()
wav_file_path = Path(args.wavfilespath).resolve()
accuracy_threshold = args.threshold
+whisper_model_name = args.whisper_model # Capture the model name from the argument
def disclaimer_text():
print(f"\nDESCRIPTION: ")
@@ -196,12 +199,13 @@ def main():
print("[AllTalk TTSDiff] \033[94mJSON file :\033[92m", json_file_path, "\033[0m")
print("[AllTalk TTSDiff] \033[94mWAV files :\033[92m", wav_file_path, "\033[0m")
print("[AllTalk TTSDiff] \033[94mAccuracy :\033[92m", accuracy_threshold, "%", "\033[0m")
+ print("[AllTalk TTSDiff] \033[94mWhisper :\033[92m", whisper_model_name, "\033[0m")
device = "cuda" if torch.cuda.is_available() else "cpu"
print("[AllTalk TTSDiff] \033[94mDevice :\033[92m", device, "\033[0m")
- print("[AllTalk TTSDiff] Loading Whisper Large-v3 model...")
- model = whisper.load_model("large-v3", device=device)
-
+ print("[AllTalk TTSDiff] Loading Whisper model...")
+ model = whisper.load_model(whisper_model_name, device=device) # Use the specified or default model
+
flagged_ids = [] # Initialize the list to track IDs needing review
try:
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/f5tts/model_engine.py b/system/tts_engines/f5tts/model_engine.py
index bc2febd..c2110da 100644
--- a/system/tts_engines/f5tts/model_engine.py
+++ b/system/tts_engines/f5tts/model_engine.py
@@ -389,11 +389,13 @@ class tts_class:
models_dir = self.main_dir / "models" / "f5tts"
if not models_dir.exists():
- print(f"[{self.branding}ENG] Models directory not found: {models_dir}")
- self.available_models["No Models Found"] = "No Models Found"
+ print(f"[{self.branding}ENG] \033[91mWarning\033[0m: Models directory not found: {models_dir}")
+ print(f"[{self.branding}ENG] \033[91mWarning\033[0m: Please use the Gradio inteface to download/select a model.")
+ self.available_models["No Models Found"] = "f5tts"
return self.available_models
# Look for model directories that match the pattern f5tts_v*
+ found_valid_model = False
for model_dir in models_dir.glob("*tts_v*"):
if model_dir.is_dir():
# First try to find model_*.safetensors files
@@ -410,16 +412,20 @@ class tts_class:
# Check if we have at least one model file and all other required files
if model_files and all(f.exists() for f in [vocab_file, vocos_config, vocos_model]):
model_name = model_dir.name
- self.available_models[model_name] = model_name
+ self.available_models[f"f5tts - {model_name}"] = "f5tts"
+ found_valid_model = True
+ print(f"[{self.branding}ENG] Found valid model: {model_name}") if self.debug_tts else None
+ else:
+ print(f"[{self.branding}ENG] \033[91mWarning\033[0m: Model folder '{model_dir.name}' is missing required files")
# ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
# ↓↓↓ Keep everything below this line ↓↓↓
# ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
- if not self.available_models:
- self.available_models["No Models Found"] = "No Models Found" # Return a list with {'No Models Found'} if there are no models found.
- return self.available_models # Return a list of models in the format {'engine name': 'model 1', 'engine name': 'model 2", etc}
+ if not found_valid_model:
+ self.available_models["No Models Found"] = "f5tts"
+ print(f"[{self.branding}ENG] \033[91mWarning\033[0m: No valid F5-TTS models found")
+ return self.available_models
- #############################################################
#############################################################
# CHANGE ME # POPULATE FILES LIST FROM VOICES DIRECTORY ####
#############################################################
@@ -487,6 +493,12 @@ class tts_class:
# Split the engine name from model name and get just the model folder name
model_folder = model_name.split(" - ")[-1]
+ model_dir = self.main_dir / "models" / "f5tts" / model_folder
+ if not model_dir.exists():
+ print(f"[{self.branding}ENG] \033[91mError\033[0m: Model directory not found: {model_dir}")
+ print(f"[{self.branding}ENG] \033[93mPlease download an F5-TTS model file in the Gradio interface section for the F5-TTS engine.\033[0m")
+ raise HTTPException(status_code=404, detail=f"Model directory not found: {model_dir}")
+
try:
# Set up paths using the correct model folder name
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/model_engine.py b/system/tts_engines/parler/model_engine.py
index cae7c90..8338774 100644
--- a/system/tts_engines/parler/model_engine.py
+++ b/system/tts_engines/parler/model_engine.py
@@ -244,18 +244,25 @@ class tts_class:
# 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" / "parler" # Edit to match the name of your folder where voices are stored
+ models_folder = self.main_dir / "models" / "parler"
print("models_folder is:", models_folder) if self.debug_tts else None
self.available_models = {}
- # ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑
- # ↑↑↑ Keep everything above this line ↑↑↑
- # ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑
- # Check if model_path exists
+
+ # Add immediate check for models folder existence
+ if not models_folder.exists():
+ print(f"[{self.branding}ENG] \033[91mWarning\033[0m: Models folder does not exist: {models_folder}")
+ print(f"[{self.branding}ENG] \033[91mWarning\033[0m: Please use the Gradio inteface to download/select a model.")
+ self.available_models = {'No Models Available': 'parler'}
+ return self.available_models
+
+ # Check basic files needed for any model
common_required_files = [
- "config.json", "generation_config.json", "preprocessor_config.json",
- "special_tokens_map.json", "spiece.model", "tokenizer.json", "tokenizer_config.json"
+ "config.json",
+ "tokenizer_config.json",
+ "special_tokens_map.json"
]
+ found_valid_model = False
for subfolder in models_folder.iterdir():
if subfolder.is_dir():
model_name = subfolder.name
@@ -263,30 +270,28 @@ class tts_class:
# Check for common required files
if all(subfolder.joinpath(file).exists() for file in common_required_files):
- # Check for mini model (single safetensors file)
- if subfolder.joinpath("model.safetensors").exists():
- self.available_models[f"parler - {model_name}"] = "parler"
- print("self.available_models is:", self.available_models) if self.debug_tts else None
- # Check for large model (split safetensors files and index)
- elif (subfolder.joinpath("model.safetensors.index.json").exists() and
- subfolder.joinpath("model-00001-of-00002.safetensors").exists() and
- subfolder.joinpath("model-00002-of-00002.safetensors").exists()):
+ # Look for any safetensors files
+ safetensor_files = list(subfolder.glob("*.safetensors"))
+ if safetensor_files:
self.available_models[f"parler - {model_name}"] = "parler"
+ found_valid_model = True
print("self.available_models is:", self.available_models) if self.debug_tts else None
else:
- print(f"[{self.branding}ENG] \033[91mWarning\033[0m: Model folder '{model_name}' is missing required model files.")
+ print(f"[{self.branding}ENG] \033[91mWarning\033[0m: Model folder '{model_name}' has no .safetensors files.")
else:
print(f"[{self.branding}ENG] \033[91mWarning\033[0m: Model folder '{model_name}' is missing common required files.")
-
+
# ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
# ↓↓↓ Keep everything below this line ↓↓↓
# ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
- else:
- self.available_models = {'No Models Available': 'parler'} # 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.")
+
+ if not found_valid_model:
+ self.available_models = {'No Models Available': 'parler'}
+ print(f"[{self.branding}ENG] \033[91mWarning\033[0m: No valid Parler models found.")
+ print(f"[{self.branding}ENG] \033[91mWarning\033[0m: Please download and install Parler models.")
+
return self.available_models
-
+
#############################################################
#############################################################
# CHANGE ME # POPULATE FILES LIST FROM VOICES DIRECTORY ####
@@ -340,7 +345,12 @@ class tts_class:
model_path = self.main_dir / "models" / "parler" / model_name
print("model_path is:", model_path) if self.debug_tts_variables else None
-
+
+ if not model_path.exists():
+ print(f"[{self.branding}ENG] \033[91mError\033[0m: Model directory not found: {model_path}")
+ print(f"[{self.branding}ENG] \033[93mPlease download a Parler model file in the Gradio interface section for the Parler engine.\033[0m")
+ raise HTTPException(status_code=404, detail=f"Model directory not found: {model_path}")
+
# Get the appropriate dtype
dtype = self.get_dtype()
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/piper/help_content.py b/system/tts_engines/piper/help_content.py
new file mode 100644
index 0000000..4f32cb7
--- /dev/null
+++ b/system/tts_engines/piper/help_content.py
@@ -0,0 +1,360 @@
+# 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 = """
+ ## 🎯 Piper TTS Engine Help
+
+ Piper is a fast, local neural Text-to-Speech system designed for efficient performance with high-quality output.
+ """
+
+ HELP_PAGE1 = """
+ ## 📁 File Locations & Management
+
+ - **Model Storage**
+ - Models are stored in `/alltalk_tts/models/piper/`
+ - Each model consists of two required files:
+ - `.onnx` file (the voice model)
+ - `.onnx.json` file (model configuration)
+
+ - **Output Files**
+ - Generated audio is saved to `/alltalk_tts/outputs/`
+ - Automatic cleanup available via `Del WAV's older than` setting
+ - When enabled, old files are removed on startup
+ - Set to `Disabled` to preserve all output files
+
+ ## 🗣️ Voice System
+
+ - **Built-in Voices**
+ - Voices are integrated into the models themselves
+ - No separate voice files needed
+ - Each model represents a complete voice with its own characteristics
+ - Models are organized by language and quality levels (x_low, low, medium, high)
+
+ - **Language Support**
+ - Wide range of languages available
+ - Models named using language codes (e.g., en_US, de_DE)
+ - Multiple voice options per language
+ - Different quality levels for varying needs
+ """
+
+ HELP_PAGE2 = """
+ ## ⚡ Performance Features
+
+ - **Quality Levels**
+ - `x_low`: Fastest, lowest resource usage
+ - `low`: Good balance of speed and quality
+ - `medium`: Higher quality, moderate resources
+ - `high`: Best quality, higher resource usage
+
+ ## 📝 Important Notes
+
+ - **Model Names**
+ - Format: `[language_code]-[voice_name]-[quality]`
+ - Example: `en_US-lessac-medium`
+ - Indicates language, speaker, and quality level
+
+ - **Best Practices**
+ - Choose quality level based on your system capabilities
+ - Use appropriate language model for your text
+
+ ## 🔄 Model Downloads
+
+ - Models can be downloaded individually or as language packs
+ - All models are sourced from the official Piper repository
+ - Downloads include both required files automatically
+ - Models can be managed through the Models/Voices Download tab
+ """
\ No newline at end of file
diff --git a/system/tts_engines/piper/piper_settings_page.py b/system/tts_engines/piper/piper_settings_page.py
index b394f9e..fa8708d 100644
--- a/system/tts_engines/piper/piper_settings_page.py
+++ b/system/tts_engines/piper/piper_settings_page.py
@@ -1,320 +1,438 @@
-import os
-import json
-import requests
-import gradio as gr
-from tqdm import tqdm
-from pathlib import Path
-this_dir = Path(__file__).parent.resolve() # Sets up self.this_dir as a variable for the folder THIS script is running in.
-main_dir = Path(__file__).parent.parent.parent.parent.resolve() # Sets up self.main_dir as a variable for the folder AllTalk is running in
-
-##########################################################################
-# REQUIRED CHANGE #
-# Populate the voices list, using the method specific to your TTS engine #
-##########################################################################
-# This function is responsible for populating the list of available voices for your TTS engine.
-# You need to modify this function to use the appropriate method for your engine to retrieve the voice list.
-#
-# The current implementation lists all the WAV files in a "voices" directory, which may not be suitable for your engine.
-# You should replace the `xxxx_voices_file_list` function name to match your engine name. For example, if your engine
-# is named "mytts", the function should be named `mytts_voices_file_list`.
-#
-# You will also neef to update the code with your own implementation that retrieves the voice list according to your
-# engine's specific requirements. Typically this is the same code as will be in your model_engine.py file.
-#
-# For example, if your engine has a dedicated API or configuration file for managing voices, you should modify this
-# function to interact with that API or read from that configuration file.
-#
-# After making the necessary changes, this function should return a list of available voices that can be used
-# in your TTS engine's settings page.
-def piper_voices_file_list():
- voices = []
- models_dir = main_dir / "models" / "piper"
-
- for root, dirs, files in os.walk(models_dir):
- onnx_files = [file for file in files if file.endswith(".onnx")]
- for onnx_file in onnx_files:
- json_file = onnx_file + ".json"
- if json_file in files:
- relative_path = os.path.relpath(os.path.join(root, onnx_file), models_dir)
- voices.append(relative_path)
-
- 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 piper_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 piper_model_alltalk_settings(model_config_data):
- features_list = model_config_data['model_capabilties']
- voice_list = piper_voices_file_list()
- with gr.Blocks(title="piper 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(piper_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="piper", 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", "piper")
- 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", "piper", 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():
- gr.Markdown("""
- ### 🟧 Where are the Piper models stored?
- This extension will download the models to `/alltalk_tts/models/piper/` folder.
-
- ### 🟧 Where are the voices stored for piper models?
- Piper models themselves are the voices/have the voices built into them. The models are made up of 2x files, an `onnx` file and a `onnx.json` file.
-
- ### 🟧 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 piper_at_gradio_settings_page(model_config_data):
- app = piper_model_alltalk_settings(model_config_data)
- return app
-def piper_at_gradio_settings_page(model_config_data):
- app = piper_model_alltalk_settings(model_config_data)
- return app
+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 piper_voices_file_list():
+ voices = []
+ models_dir = main_dir / "models" / "piper"
+
+ for root, dirs, files in os.walk(models_dir):
+ onnx_files = [file for file in files if file.endswith(".onnx")]
+ for onnx_file in onnx_files:
+ json_file = onnx_file + ".json"
+ if json_file in files:
+ relative_path = os.path.relpath(os.path.join(root, onnx_file), models_dir)
+ voices.append(relative_path)
+
+ return voices
+
+# 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"]]
+
+#####################################################################
+# Piper Specific section for grouping/downloading the models/voices #
+#####################################################################
+
+def group_models_by_language():
+ """Group available models by language code (first 2 letters)"""
+ language_groups = {}
+
+ for model in available_models["models"]:
+ model_name = model["model_name"]
+ lang_code = model_name[:2] # Get first two letters (language code)
+
+ 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 language code to full name"""
+ language_names = {
+ "ar": "Arabic",
+ "ca": "Catalan",
+ "cs": "Czech",
+ "da": "Danish",
+ "de": "German",
+ "el": "Greek",
+ "en": "English",
+ "es": "Spanish",
+ "fa": "Persian",
+ "fi": "Finnish",
+ "fr": "French",
+ "hu": "Hungarian",
+ "is": "Icelandic",
+ "it": "Italian",
+ "ka": "Georgian",
+ "kk": "Kazakh",
+ "lb": "Luxembourgish",
+ "ne": "Nepali",
+ "nl": "Dutch",
+ "no": "Norwegian",
+ "pl": "Polish",
+ "pt": "Portuguese",
+ "ro": "Romanian",
+ "ru": "Russian",
+ "sk": "Slovak",
+ "sl": "Slovenian",
+ "sr": "Serbian",
+ "sv": "Swedish",
+ "sw": "Swahili",
+ "tr": "Turkish",
+ "uk": "Ukrainian",
+ "vi": "Vietnamese",
+ "zh": "Chinese"
+ }
+ return language_names.get(lang_code, f"Unknown ({lang_code})")
+
+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", "piper")
+ 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!"
+
+def download_language_pack(lang_code, progress=gr.Progress()):
+ """Download all models for a specific language"""
+ language_groups = group_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")
+
+ folder_path = os.path.join(main_dir, "models", "piper")
+ os.makedirs(folder_path, exist_ok=True)
+
+ downloaded_files = []
+ errors = []
+
+ for idx, model in enumerate(models, 1):
+ progress(idx/total_models, desc=f"Downloading model {idx}/{total_models}")
+
+ for url in model["files_to_download"]:
+ try:
+ file_name = os.path.basename(url.split('?')[0])
+ file_path = os.path.join(folder_path, file_name)
+
+ if not os.path.exists(file_path):
+ response = requests.get(url, stream=True)
+ total_size = int(response.headers.get("content-length", 0))
+
+ with open(file_path, "wb") as file:
+ for chunk in response.iter_content(chunk_size=1024):
+ if chunk:
+ file.write(chunk)
+
+ downloaded_files.append(file_name)
+
+ except Exception as e:
+ errors.append(f"Error downloading {file_name}: {str(e)}")
+
+ if errors:
+ return f"Downloaded {len(downloaded_files)} files with {len(errors)} errors:\n" + "\n".join(errors)
+ else:
+ return f"Successfully downloaded {len(downloaded_files)} files 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 piper_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 piper_model_alltalk_settings(model_config_data):
+ features_list = model_config_data['model_capabilties']
+ voice_list = piper_voices_file_list()
+ with gr.Blocks(title="piper 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(piper_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="piper", 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_models_by_language()
+ language_choices = [
+ f"{code} - {info['name']} ({len(info['models'])} voices)"
+ 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():
+ model_dropdown = gr.Dropdown(
+ choices=sorted(model_names),
+ label="Select Individual Voice",
+ value=model_names[0]
+ )
+ download_button = gr.Button("Download Selected Voice")
+
+ with gr.Row():
+ download_status = gr.Textbox(label=" Individual Voice 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", "piper", 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():
+ 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 piper_at_gradio_settings_page(model_config_data):
+ app = piper_model_alltalk_settings(model_config_data)
+ return app
+def piper_at_gradio_settings_page(model_config_data):
+ app = piper_model_alltalk_settings(model_config_data)
+ 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..cc03f8b
--- /dev/null
+++ b/system/tts_engines/xtts/help_content.py
@@ -0,0 +1,474 @@
+# 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
+ - JSON latent files are automatically created on first run of generation with a voice
+ """
+
+ 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
+
+ ### Automatic Latent Generation
+ - System automatically creates `.json` latent files alongside voice samples
+ - Latents are voice characteristics extracted from audio
+ - Generated on first use of a voice file
+ - Stored next to original audio (e.g., `broadcaster_male.wav` → `broadcaster_male.json`)
+ - Improves generation speed for subsequent uses
+ - No manual management needed
+
+ ### Manual Latent Storage
+ - Pre-computed latents can also be stored in `/voices/xtts_latents/`
+ - Useful for organizing frequently used voice profiles
+ - Allows sharing latents without original audio
+ - Same format as automatic latents
+ - Takes priority over local latent files
+ """
+
+ 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
################################
diff --git a/system/tts_generator/tts_generator.html b/system/tts_generator/tts_generator.html
index 6cecaa2..df8ff7b 100644
--- a/system/tts_generator/tts_generator.html
+++ b/system/tts_generator/tts_generator.html
@@ -725,6 +725,16 @@
+
+
Page 1 of X
@@ -902,7 +912,8 @@
// If the server confirms the data was saved successfully, trigger analysis
if (saveData.message === "Data saved successfully") {
const accuracy = parseInt(document.getElementById('AnalyzeAccuracy').value) || 98;
- const analyzeResponse = await fetch(`/api/trigger-analysis?threshold=${accuracy}`);
+ const whisperModel = document.getElementById('whisperModel').value;
+ const analyzeResponse = await fetch(`/api/trigger-analysis?threshold=${accuracy}&whisper_model=${whisperModel}`);
const analyzeData = await analyzeResponse.json();
if (analyzeData.summary.flagged_ids && analyzeData.summary.flagged_ids.length > 0) {
// Update the global problematicIds array with the new flagged IDs
diff --git a/test/test_config.py b/test/test_config.py
new file mode 100644
index 0000000..2845414
--- /dev/null
+++ b/test/test_config.py
@@ -0,0 +1,196 @@
+import os.path
+import tempfile
+import unittest
+from pathlib import Path
+
+from config import AlltalkConfig, AlltalkTTSEnginesConfig, AlltalkNewEnginesConfig
+
+
+class TestAlltalkConfig(unittest.TestCase):
+
+ def setUp(self):
+ self.config = AlltalkConfig.get_instance()
+ self.config.reload()
+
+ def test_default_config_path(self):
+ expected_config_path = Path(__file__).parent.parent.resolve() / "confignew.json"
+ self.assertEqual(self.config.get_config_path(), expected_config_path)
+ self.assertEqual(AlltalkConfig.default_config_path(), expected_config_path)
+
+ def test_branding(self):
+ self.assertEqual(self.config.branding, "AllTalk ")
+
+ def test_delete_output_wavs(self):
+ self.assertEqual(self.config.delete_output_wavs, "Disabled")
+
+ def test_gradio_interface(self):
+ self.assertTrue(self.config.gradio_interface)
+
+ def test_output_folder(self):
+ self.assertEqual(self.config.output_folder, "outputs")
+ # Testing the side effect of creating the output folder:
+ output_folder = Path(__file__).parent.parent.resolve() / self.config.output_folder
+ self.assertTrue(output_folder.exists())
+
+ def test_gradio_port_number(self):
+ self.assertEqual(self.config.gradio_port_number, 7852)
+
+ def test_firstrun_model(self):
+ self.assertTrue(self.config.firstrun_model)
+
+ def test_firstrun_splash(self):
+ self.assertTrue(self.config.firstrun_splash)
+
+ def test_launch_gradio(self):
+ self.assertTrue(self.config.launch_gradio)
+
+ def test_transcode_audio_format(self):
+ self.assertEqual(self.config.transcode_audio_format, "Disabled")
+
+ def test_theme(self):
+ self.assertEqual(self.config.theme.file, None)
+ self.assertEqual(self.config.theme.clazz, "gradio/base")
+
+ def test_rvc_settings(self):
+ self.assertFalse(self.config.rvc_settings.rvc_enabled)
+ self.assertEqual(self.config.rvc_settings.rvc_char_model_file, "Disabled")
+ self.assertEqual(self.config.rvc_settings.rvc_narr_model_file, "Disabled")
+ self.assertTrue(self.config.rvc_settings.split_audio)
+ self.assertEqual(self.config.rvc_settings.pitch, 0)
+ self.assertEqual(self.config.rvc_settings.filter_radius, 3)
+ self.assertEqual(self.config.rvc_settings.index_rate, 0.75)
+ self.assertEqual(self.config.rvc_settings.rms_mix_rate, 1)
+ self.assertEqual(self.config.rvc_settings.protect, 0.5)
+ self.assertEqual(self.config.rvc_settings.hop_length, 130)
+ self.assertEqual(self.config.rvc_settings.f0method, "fcpe")
+ self.assertEqual(self.config.rvc_settings.embedder_model, "hubert")
+ self.assertEqual(self.config.rvc_settings.training_data_size, 45000)
+
+ def test_tgwui(self):
+ self.assertTrue(self.config.tgwui.tgwui_activate_tts)
+ self.assertTrue(self.config.tgwui.tgwui_autoplay_tts)
+ self.assertEqual(self.config.tgwui.tgwui_narrator_enabled, "false")
+ self.assertEqual(self.config.tgwui.tgwui_non_quoted_text_is, "character")
+ self.assertFalse(self.config.tgwui.tgwui_deepspeed_enabled)
+ self.assertEqual(self.config.tgwui.tgwui_language, "English")
+ self.assertFalse(self.config.tgwui.tgwui_lowvram_enabled)
+ self.assertEqual(self.config.tgwui.tgwui_pitch_set, 0)
+ self.assertEqual(self.config.tgwui.tgwui_temperature_set, 0.75)
+ self.assertEqual(self.config.tgwui.tgwui_repetitionpenalty_set, 10)
+ self.assertEqual(self.config.tgwui.tgwui_generationspeed_set, 1)
+ self.assertEqual(self.config.tgwui.tgwui_narrator_voice, "female_01.wav")
+ self.assertTrue(self.config.tgwui.tgwui_show_text)
+ self.assertEqual(self.config.tgwui.tgwui_character_voice, "female_01.wav")
+ self.assertEqual(self.config.tgwui.tgwui_rvc_char_voice, "Disabled")
+ self.assertEqual(self.config.tgwui.tgwui_rvc_narr_voice, "Disabled")
+
+ def test_api_def(self):
+ self.assertEqual(self.config.api_def.api_port_number, 7851)
+ self.assertIn("a-zA-Z0-9", self.config.api_def.api_allowed_filter)
+ self.assertEqual(self.config.api_def.api_length_stripping, 3)
+ self.assertEqual(self.config.api_def.api_max_characters, 2000)
+ self.assertFalse(self.config.api_def.api_use_legacy_api)
+ self.assertEqual(self.config.api_def.api_legacy_ip_address, "127.0.0.1")
+ self.assertEqual(self.config.api_def.api_text_filtering, "standard")
+ self.assertEqual(self.config.api_def.api_narrator_enabled, "false")
+ self.assertEqual(self.config.api_def.api_text_not_inside, "character")
+ self.assertEqual(self.config.api_def.api_language, "en")
+ self.assertEqual(self.config.api_def.api_output_file_name, "myoutputfile")
+ self.assertTrue(self.config.api_def.api_output_file_timestamp)
+ self.assertFalse(self.config.api_def.api_autoplay)
+ self.assertEqual(self.config.api_def.api_autoplay_volume, 0.5)
+
+ def test_debugging(self):
+ self.assertFalse(self.config.debugging.debug_transcode)
+ self.assertFalse(self.config.debugging.debug_tts)
+ self.assertFalse(self.config.debugging.debug_openai)
+ self.assertFalse(self.config.debugging.debug_concat)
+ self.assertFalse(self.config.debugging.debug_tts_variables)
+ self.assertFalse(self.config.debugging.debug_rvc)
+
+ def test_gradio_pages(self):
+ self.assertTrue(self.config.gradio_pages.Generate_Help_page)
+ self.assertTrue(self.config.gradio_pages.Voice2RVC_page)
+ self.assertTrue(self.config.gradio_pages.TTS_Generator_page)
+ self.assertTrue(self.config.gradio_pages.TTS_Engines_Settings_page)
+ self.assertTrue(self.config.gradio_pages.alltalk_documentation_page)
+ self.assertTrue(self.config.gradio_pages.api_documentation_page)
+
+ def test_save_config(self):
+ with tempfile.NamedTemporaryFile(suffix=".json") as tmp:
+ self.config.branding = "foo"
+ self.config.save(tmp.name)
+ new_config = AlltalkConfig(tmp.name)
+ self.assertEqual(new_config.branding, "foo")
+
+ def test_no_private_fields(self):
+ for attr in self.config.to_dict().keys():
+ self.assertTrue(not attr.startswith("_"))
+
+class TestAlltalkTTSEnginesConfig(unittest.TestCase):
+
+ def setUp(self):
+ self.tts_engines_config = AlltalkTTSEnginesConfig.get_instance()
+ self.tts_engines_config.reload()
+
+ def test_tts_engines(self):
+ self.assertEqual(self.tts_engines_config.engine_loaded, "piper")
+ self.assertEqual(self.tts_engines_config.selected_model, "piper")
+ self.assertListEqual(self.tts_engines_config.get_engine_names_available(), ["parler", "piper", "vits", "xtts", "f5tts"])
+
+ def test_is_valid_engine(self):
+ for engine in ["parler", "piper", "vits", "xtts", "f5tts"]:
+ self.assertTrue(self.tts_engines_config.is_valid_engine(engine))
+
+ self.assertFalse(self.tts_engines_config.is_valid_engine("foo"))
+
+ def test_change_tts_engine(self):
+ self.tts_engines_config.change_engine("vits")
+ self.assertEqual(self.tts_engines_config.engine_loaded, "vits")
+ self.assertEqual(self.tts_engines_config.selected_model, "vits - tts_models--en--vctk--vits")
+
+ def test_tts_engines_default_config_path(self):
+ expected_config_path = os.path.join(Path(__file__).parent.parent.resolve(), "system", "tts_engines", "tts_engines.json")
+ self.assertEqual(self.tts_engines_config.get_config_path(), Path(expected_config_path))
+
+ def test_tts_engines_save(self):
+ with tempfile.NamedTemporaryFile(suffix=".json") as tmp:
+ self.tts_engines_config.engine_loaded = "foo"
+ self.tts_engines_config.save(tmp.name)
+ new_config = AlltalkTTSEnginesConfig(tmp.name)
+ self.assertEqual(new_config.engine_loaded, "foo")
+ self.assertListEqual(new_config.get_engine_names_available(), ["parler", "piper", "vits", "xtts", "f5tts"])
+ self.assertEqual(len(new_config.engines_available), len(new_config.get_engine_names_available()))
+
+ def test_merge_with_new_engines(self):
+ with tempfile.NamedTemporaryFile(suffix=".json") as tmp:
+ self.tts_engines_config.engines_available = []
+ self.assertListEqual(self.tts_engines_config.get_engine_names_available(), [])
+
+ self.tts_engines_config.save(tmp.name)
+
+ new_config = AlltalkTTSEnginesConfig(tmp.name)
+ self.assertEqual(new_config.get_engine_names_available(), ["parler", "xtts", "f5tts"])
+ self.assertEqual(len(new_config.engines_available), len(new_config.get_engine_names_available()))
+
+ def test_no_private_fields(self):
+ for attr in self.tts_engines_config.to_dict().keys():
+ self.assertTrue(not attr.startswith("_"))
+
+
+class TestAlltalkNewEnginesConfig(unittest.TestCase):
+
+ def setUp(self):
+ self.new_engines_config = AlltalkNewEnginesConfig.get_instance()
+ self.new_engines_config.reload()
+
+ def test_engine_names(self):
+ self.assertListEqual(self.new_engines_config.get_engine_names_available(), ["parler", "xtts", "f5tts"])
+
+ def test_get_engine_names_matching(self):
+ result = self.new_engines_config.get_engines_matching(lambda x: "tts" in x.name)
+ self.assertEqual(len(result), 2)
+
+ def test_no_private_fields(self):
+ for attr in self.new_engines_config.to_dict().keys():
+ self.assertTrue(not attr.startswith("_"))
diff --git a/test_server.py b/test_server.py
new file mode 100644
index 0000000..e3d2d19
--- /dev/null
+++ b/test_server.py
@@ -0,0 +1,1184 @@
+import asyncio
+import logging
+import platform
+import subprocess
+import sys
+import time
+from datetime import datetime
+from typing import Dict, List, Optional, Tuple
+import aiohttp
+
+# pylint: disable=all
+
+class AllTalkTester:
+ def __init__(self):
+ self.banner = """
+ _ _ _ _____ _ _ _____ _____ ____
+ / \ | | |_ _|_ _| | | __ |_ _|_ _/ ___|
+ / _ \ | | | | |/ _` | | |/ / | | | | \___ \
+ / ___ \| | | | | (_| | | < | | | | ___) |
+ /_/ \_\_|_| |_|\__,_|_|_|\_\ |_| |_| |____/
+
+ ╔════════════════════════════════════╗
+ ║ Server Testing Suite v1.1 ║
+ ╚════════════════════════════════════╝
+ """
+ self.config = {
+ "host": "127.0.0.1",
+ "port": 7851,
+ "log_file": "alltalk_test_results.log",
+ "timeout": 30, # Default timeout for tests
+ "retry_count": 3, # Number of retries for failed tests
+ "retry_delay": 5, # Delay between retries in seconds
+ }
+ self.narrator_test_cases = {
+ "narrator_simple": {
+ "text": "*The old wizard gazed at the stars.* \"What mysteries do they hold?\" *he wondered aloud.*",
+ "expect_files": 3
+ },
+ "mixed_complex": {
+ "text": "*The wind howled through the trees.* \"I don't like this place,\" *Sarah whispered.* \"Neither do I,\" *John replied, his voice trembling.*",
+ "expect_files": 5
+ },
+ "silent_test": {
+ "text": "*Silent narrator text.* \"Speaking character.\" Plain text here.",
+ "narrator_enabled": "silent",
+ "text_not_inside": "silent",
+ "expect_files": 1
+ }
+ }
+
+ self.filtering_test_cases = {
+ "text": "Test with special chars: @#$% and special é character and HTML tags bold",
+ "filters": ["none", "standard", "html"]
+ }
+
+ self.volume_test_cases = [
+ {"volume": 0.5, "should_succeed": True},
+ {"volume": 1.5, "should_succeed": False}
+ ]
+
+ self.error_test_cases = [
+ {"case": "invalid_voice", "voice": "nonexistent.wav", "expect_error": 500},
+ {"case": "bad_language", "language": "xx", "expect_error": 400},
+ {"case": "missing_text", "text": "", "expect_error": 422}
+ ]
+ self.test_results = []
+ self.setup_logging()
+
+ def setup_logging(self):
+ """Configure logging to both file and console"""
+ self.log_file = datetime.now().strftime('alltalk_test_%Y%m%d_%H%M%S.log')
+
+ # Configure logging with UTF-8 encoding
+ logging.basicConfig(
+ level=logging.INFO,
+ format='%(asctime)s - %(levelname)s - %(message)s',
+ handlers=[
+ logging.FileHandler(self.log_file, encoding='utf-8'),
+ logging.StreamHandler(sys.stdout)
+ ]
+ )
+ self.logger = logging.getLogger(__name__)
+ self.logger.info("AllTalk Testing Suite Started")
+
+ async def setup(self):
+ """Initial setup and configuration"""
+ print(self.banner)
+ self.logger.info("Starting test suite setup")
+
+ try:
+ self.host = input(f"Enter server IP/URL [{self.config['host']}]: ") or self.config['host']
+ self.port = input(f"Enter port [{self.config['port']}]: ") or self.config['port']
+ self.base_url = f"http://{self.host}:{self.port}"
+
+ self.logger.info(f"Configuration set - Host: {self.host}, Port: {self.port}")
+ return True
+ except Exception as e:
+ self.logger.error(f"Setup failed: {str(e)}")
+ return False
+
+ async def menu(self):
+ """Display and handle test selection menu"""
+ while True:
+ print("\nTest Options:")
+ print("1. Run All Tests (takes 30 seconds)")
+ print("2. Network Connectivity Tests")
+ print("3. Core API Tests")
+ print("4. TTS Generation Tests")
+ print("5. Error Case Tests")
+ print("6. Individual Test Selection")
+ print("7. Exit")
+
+ try:
+ choice = input("\nSelect option: ")
+
+ if choice == "1":
+ await self.run_all_tests()
+ elif choice == "2":
+ await self.run_network_tests()
+ elif choice == "3":
+ await self.run_core_api_tests()
+ elif choice == "4":
+ await self.run_tts_tests()
+ elif choice == "5":
+ await self._test_error_cases()
+ elif choice == "6":
+ await self.individual_test_menu()
+ elif choice == "7":
+ self.logger.info("Test suite shutting down")
+ break
+ else:
+ print("Invalid option. Please try again.")
+
+ except Exception as e:
+ self.logger.error(f"Menu operation failed: {str(e)}")
+
+ async def individual_test_menu(self):
+ """Sub-menu for selecting individual tests"""
+ while True:
+ print("\nSelect Individual Test:")
+ print("1. Text Filtering Tests")
+ print("2. Narrator Mode Tests")
+ print("3. Autoplay Tests")
+ print("4. Additional Language Test")
+ print("5. OpenAI Compatibility Tests")
+ print("6. Return to Main Menu")
+
+ try:
+ choice = input("\nSelect option: ")
+
+ if choice == "1":
+ await self._test_text_filtering()
+ elif choice == "2":
+ await self._test_narrator_modes()
+ elif choice == "3":
+ await self._test_autoplay()
+ elif choice == "4":
+ await self._test_additional_language()
+ elif choice == "5":
+ await self._test_openai_tts(self._generate_test_texts()["openai"])
+ elif choice == "6":
+ break
+ else:
+ print("Invalid option. Please try again.")
+
+ except Exception as e:
+ self.logger.error(f"Individual test selection failed: {str(e)}")
+
+ def log_result(self, category: str, test_name: str, status: str, details: str = ""):
+ """Log a test result to both the results list and logger"""
+ result = {
+ "timestamp": datetime.now().isoformat(),
+ "category": category,
+ "test": test_name,
+ "status": status,
+ "details": details
+ }
+ self.test_results.append(result)
+
+ if status == "PASS":
+ self.logger.info(f"{category} - {test_name}: {status}")
+ elif status == "WARNING":
+ self.logger.warning(f"{category} - {test_name}: {status} - {details}")
+ else:
+ self.logger.error(f"{category} - {test_name}: {status} - {details}")
+
+ async def run_network_tests(self):
+ """Perform network connectivity tests"""
+ self.logger.info("Starting network connectivity tests")
+
+ # Ping test
+ await self._run_ping_test()
+
+ # Basic connectivity test
+ await self._test_server_connection()
+
+ async def _run_ping_test(self):
+ """Perform ping test to server"""
+ try:
+ param = '-n' if platform.system().lower() == 'windows' else '-c'
+ command = ['ping', param, '4', self.host]
+
+ self.logger.info(f"Running ping test to {self.host}")
+ output = subprocess.check_output(command, stderr=subprocess.STDOUT).decode()
+
+ if "unreachable" in output.lower() or "timed out" in output.lower():
+ self.log_result("Network", "Ping Test", "WARNING",
+ "Host reachable but some packets lost")
+ else:
+ self.log_result("Network", "Ping Test", "PASS",
+ "Host responding to ping")
+
+ except subprocess.CalledProcessError as e:
+ self.log_result("Network", "Ping Test", "WARNING",
+ f"Ping failed but continuing with tests: {str(e)}")
+ except Exception as e:
+ self.log_result("Network", "Ping Test", "ERROR",
+ f"Unexpected error during ping test: {str(e)}")
+
+ async def _test_server_connection(self):
+ """Test basic server connectivity using /api/ready endpoint"""
+ self.logger.info("Testing server API connectivity")
+
+ async with aiohttp.ClientSession() as session:
+ try:
+ start_time = time.time()
+ retries = 0
+
+ while retries < self.config['retry_count']:
+ try:
+ async with session.get(
+ f"{self.base_url}/api/ready",
+ timeout=aiohttp.ClientTimeout(total=5)
+ ) as response:
+
+ if response.status == 200:
+ content = await response.text()
+ if content == "Ready":
+ elapsed = time.time() - start_time
+ self.log_result(
+ "Network",
+ "Server Connection",
+ "PASS",
+ f"Server ready. Response time: {elapsed:.2f}s"
+ )
+ return True
+ else:
+ self.log_result(
+ "Network",
+ "Server Connection",
+ "WARNING",
+ f"Server responded but not ready: {content}"
+ )
+ else:
+ self.log_result(
+ "Network",
+ "Server Connection",
+ "WARNING",
+ f"Unexpected status code: {response.status}"
+ )
+
+ except aiohttp.ClientError as e:
+ self.logger.warning(f"Connection attempt {retries + 1} failed: {str(e)}")
+
+ retries += 1
+ if retries < self.config['retry_count']:
+ await asyncio.sleep(self.config['retry_delay'])
+
+ self.log_result(
+ "Network",
+ "Server Connection",
+ "ERROR",
+ f"Server not responding after {retries} attempts"
+ )
+ return False
+
+ except Exception as e:
+ self.log_result(
+ "Network",
+ "Server Connection",
+ "ERROR",
+ f"Unexpected error testing server connection: {str(e)}"
+ )
+ return False
+
+ async def run_core_api_tests(self):
+ """Run tests for core API functionality"""
+ self.logger.info("Starting core API tests")
+
+ # Test core endpoints
+ settings = await self._test_current_settings()
+ if not settings:
+ self.logger.error("Failed to get current settings, skipping dependent tests")
+ return
+
+ await self._test_voices()
+ await self._test_rvc_voices()
+ await self._test_root_endpoint()
+
+ async def _test_current_settings(self) -> Optional[dict]:
+ """Test /api/currentsettings endpoint"""
+ self.logger.info("Testing current settings endpoint")
+
+ async with aiohttp.ClientSession() as session:
+ try:
+ async with session.get(
+ f"{self.base_url}/api/currentsettings",
+ timeout=aiohttp.ClientTimeout(total=self.config['timeout'])
+ ) as response:
+ if response.status == 200:
+ settings = await response.json()
+
+ # Validate essential settings exist
+ required_keys = [
+ 'current_engine_loaded',
+ 'current_model_loaded',
+ 'streaming_capable',
+ 'multivoice_capable'
+ ]
+
+ missing_keys = [key for key in required_keys if key not in settings]
+
+ if missing_keys:
+ self.log_result(
+ "Core API",
+ "Current Settings",
+ "WARNING",
+ f"Missing required settings: {', '.join(missing_keys)}"
+ )
+ else:
+ self.log_result(
+ "Core API",
+ "Current Settings",
+ "PASS",
+ f"Engine: {settings['current_engine_loaded']}, Model: {settings['current_model_loaded']}"
+ )
+
+ return settings
+ else:
+ self.log_result(
+ "Core API",
+ "Current Settings",
+ "ERROR",
+ f"Unexpected status code: {response.status}"
+ )
+ return None
+
+ except Exception as e:
+ self.log_result(
+ "Core API",
+ "Current Settings",
+ "ERROR",
+ f"Failed to get current settings: {str(e)}"
+ )
+ return None
+
+ async def _test_voices(self):
+ """Test /api/voices endpoint"""
+ self.logger.info("Testing voices endpoint")
+
+ async with aiohttp.ClientSession() as session:
+ try:
+ async with session.get(
+ f"{self.base_url}/api/voices",
+ timeout=aiohttp.ClientTimeout(total=self.config['timeout'])
+ ) as response:
+ if response.status == 200:
+ data = await response.json()
+ voices = data.get('voices', [])
+
+ if voices:
+ self.log_result(
+ "Core API",
+ "Voices",
+ "PASS",
+ f"Found {len(voices)} voices"
+ )
+ # Store voices for later use in TTS tests
+ self.available_voices = voices
+ else:
+ self.log_result(
+ "Core API",
+ "Voices",
+ "WARNING",
+ "No voices found"
+ )
+ else:
+ self.log_result(
+ "Core API",
+ "Voices",
+ "ERROR",
+ f"Unexpected status code: {response.status}"
+ )
+
+ except Exception as e:
+ self.log_result(
+ "Core API",
+ "Voices",
+ "ERROR",
+ f"Failed to get voices: {str(e)}"
+ )
+
+ async def _test_rvc_voices(self):
+ """Test /api/rvcvoices endpoint"""
+ self.logger.info("Testing RVC voices endpoint")
+
+ async with aiohttp.ClientSession() as session:
+ try:
+ async with session.get(
+ f"{self.base_url}/api/rvcvoices",
+ timeout=aiohttp.ClientTimeout(total=self.config['timeout'])
+ ) as response:
+ if response.status == 200:
+ data = await response.json()
+ rvc_voices = data.get('rvcvoices', [])
+
+ if "Disabled" in rvc_voices and len(rvc_voices) == 1:
+ self.log_result(
+ "Core API",
+ "RVC Voices",
+ "PASS",
+ "RVC is disabled (expected behavior)"
+ )
+ elif rvc_voices:
+ self.log_result(
+ "Core API",
+ "RVC Voices",
+ "PASS",
+ f"Found {len(rvc_voices)} RVC voices"
+ )
+ # Store RVC voices for later use
+ self.available_rvc_voices = rvc_voices
+ else:
+ self.log_result(
+ "Core API",
+ "RVC Voices",
+ "WARNING",
+ "No RVC voices found"
+ )
+ else:
+ self.log_result(
+ "Core API",
+ "RVC Voices",
+ "ERROR",
+ f"Unexpected status code: {response.status}"
+ )
+
+ except Exception as e:
+ self.log_result(
+ "Core API",
+ "RVC Voices",
+ "ERROR",
+ f"Failed to get RVC voices: {str(e)}"
+ )
+
+ async def _test_root_endpoint(self):
+ """Test root endpoint /"""
+ self.logger.info("Testing root endpoint")
+
+ async with aiohttp.ClientSession() as session:
+ try:
+ async with session.get(
+ self.base_url,
+ timeout=aiohttp.ClientTimeout(total=self.config['timeout'])
+ ) as response:
+ if response.status == 200:
+ content = await response.text()
+ if content:
+ self.log_result(
+ "Core API",
+ "Root Endpoint",
+ "PASS",
+ "Root endpoint returning content"
+ )
+ else:
+ self.log_result(
+ "Core API",
+ "Root Endpoint",
+ "WARNING",
+ "Root endpoint returned empty content"
+ )
+ else:
+ self.log_result(
+ "Core API",
+ "Root Endpoint",
+ "ERROR",
+ f"Unexpected status code: {response.status}"
+ )
+
+ except Exception as e:
+ self.log_result(
+ "Core API",
+ "Root Endpoint",
+ "ERROR",
+ f"Failed to access root endpoint: {str(e)}"
+ )
+
+ def _generate_test_texts(self) -> dict:
+ """Generate various test texts for different TTS scenarios"""
+ return {
+ "simple": "This is a simple test of the TTS system.",
+ "narrator": """*The narrator speaks first.* "Then a character speaks," *and the narrator returns.*""",
+ "mixed": """*It was a dark and stormy night.* "What's that noise?" *asked Sarah, her voice trembling.* "Just the wind," *replied John confidently.*""",
+ "openai": "Testing the OpenAI compatible endpoint with this text.",
+ "long": "This is a longer text that will be used to test the streaming capabilities of the TTS system. It includes multiple sentences to ensure we have enough content to properly test the streaming functionality."
+ }
+
+ async def _select_test_voice(self) -> Optional[str]:
+ """Select a random voice from available voices"""
+ if not hasattr(self, 'available_voices') or not self.available_voices:
+ self.log_result(
+ "TTS",
+ "Voice Selection",
+ "ERROR",
+ "No voices available for testing"
+ )
+ return None
+ return self.available_voices[0] # Use first available voice for consistency
+
+ async def run_tts_tests(self):
+ """Run comprehensive TTS generation tests"""
+ self.logger.info("Starting TTS generation tests")
+
+ test_voice = await self._select_test_voice()
+ if not test_voice:
+ return
+
+ # Standard TTS test with text filtering variations
+ await self._test_text_filtering()
+
+ # Narrator mode tests - now checks for available voices
+ voices = await self._select_two_voices()
+ if voices:
+ await self._test_narrator_modes()
+
+ # Autoplay tests
+ await self._test_autoplay()
+
+ # Additional language test
+ await self._test_additional_language()
+
+ # Error case testing
+ await self._test_error_cases()
+
+ # OpenAI compatibility tests
+ await self._test_openai_tts(self._generate_test_texts()["openai"])
+
+ async def _test_standard_tts(self, text: str, voice: str):
+ """Test standard TTS generation"""
+ self.logger.info("Testing standard TTS generation")
+
+ async with aiohttp.ClientSession() as session:
+ try:
+ data = {
+ "text_input": text,
+ "character_voice_gen": voice,
+ "language": "en",
+ "output_file_name": f"test_standard_{int(time.time())}",
+ "text_filtering": "standard"
+ }
+
+ async with session.post(
+ f"{self.base_url}/api/tts-generate",
+ data=data,
+ timeout=aiohttp.ClientTimeout(total=self.config['timeout'])
+ ) as response:
+ if response.status == 200:
+ result = await response.json()
+ if result.get("status") == "generate-success":
+ self.log_result(
+ "TTS",
+ "Standard Generation",
+ "PASS",
+ f"Output file: {result.get('output_file_path')}"
+ )
+ # Store successful generation for later tests
+ self.last_generated_file = result.get('output_file_path')
+ else:
+ self.log_result(
+ "TTS",
+ "Standard Generation",
+ "ERROR",
+ f"Generation failed: {result}"
+ )
+ else:
+ self.log_result(
+ "TTS",
+ "Standard Generation",
+ "ERROR",
+ f"Unexpected status code: {response.status}"
+ )
+
+ except Exception as e:
+ self.log_result(
+ "TTS",
+ "Standard Generation",
+ "ERROR",
+ f"Failed to generate TTS: {str(e)}"
+ )
+
+ async def _test_narrator_tts(self, text: str, voice: str):
+ """Test narrator mode TTS generation"""
+ self.logger.info("Testing narrator mode TTS generation")
+
+ async with aiohttp.ClientSession() as session:
+ try:
+ data = {
+ "text_input": text,
+ "character_voice_gen": voice,
+ "narrator_voice_gen": voice, # Using same voice for simplicity
+ "narrator_enabled": "true",
+ "language": "en",
+ "output_file_name": f"test_narrator_{int(time.time())}",
+ "text_filtering": "standard",
+ "text_not_inside": "narrator"
+ }
+
+ async with session.post(
+ f"{self.base_url}/api/tts-generate",
+ data=data,
+ timeout=aiohttp.ClientTimeout(total=self.config['timeout'])
+ ) as response:
+ if response.status == 200:
+ result = await response.json()
+ if result.get("status") == "generate-success":
+ self.log_result(
+ "TTS",
+ "Narrator Mode",
+ "PASS",
+ f"Output file: {result.get('output_file_path')}"
+ )
+ # Store successful generation for later tests
+ self.last_narrator_file = result.get('output_file_path')
+ else:
+ self.log_result(
+ "TTS",
+ "Narrator Mode",
+ "ERROR",
+ f"Generation failed: {result}"
+ )
+ else:
+ self.log_result(
+ "TTS",
+ "Narrator Mode",
+ "ERROR",
+ f"Unexpected status code: {response.status}"
+ )
+
+ except Exception as e:
+ self.log_result(
+ "TTS",
+ "Narrator Mode",
+ "ERROR",
+ f"Failed to generate narrator TTS: {str(e)}"
+ )
+
+ async def _test_streaming_tts(self, text: str, voice: str):
+ """Test streaming TTS generation"""
+ self.logger.info("Testing streaming TTS generation")
+
+ async with aiohttp.ClientSession() as session:
+ try:
+ params = {
+ "text": text,
+ "voice": voice,
+ "language": "en",
+ "output_file": f"test_streaming_{int(time.time())}"
+ }
+
+ async with session.get(
+ f"{self.base_url}/api/tts-generate-streaming",
+ params=params,
+ timeout=aiohttp.ClientTimeout(total=self.config['timeout'])
+ ) as response:
+ if response.status == 200:
+ # Test streaming by reading chunks
+ chunks_received = 0
+ async for chunk in response.content.iter_any():
+ chunks_received += 1
+
+ if chunks_received > 0:
+ self.log_result(
+ "TTS",
+ "Streaming Generation",
+ "PASS",
+ f"Received {chunks_received} chunks"
+ )
+ else:
+ self.log_result(
+ "TTS",
+ "Streaming Generation",
+ "WARNING",
+ "Stream succeeded but no chunks received"
+ )
+ elif response.status == 400:
+ error_data = await response.json()
+ if "streaming not supported" in error_data.get("error", "").lower():
+ self.log_result(
+ "TTS",
+ "Streaming Generation",
+ "PASS",
+ "Correctly reported streaming not supported"
+ )
+ else:
+ self.log_result(
+ "TTS",
+ "Streaming Generation",
+ "ERROR",
+ f"Bad request: {error_data}"
+ )
+ else:
+ self.log_result(
+ "TTS",
+ "Streaming Generation",
+ "ERROR",
+ f"Unexpected status code: {response.status}"
+ )
+
+ except Exception as e:
+ self.log_result(
+ "TTS",
+ "Streaming Generation",
+ "ERROR",
+ f"Failed to test streaming: {str(e)}"
+ )
+
+ async def _test_openai_tts(self, text: str):
+ """Test OpenAI compatible endpoint"""
+ self.logger.info("Testing OpenAI compatible endpoint")
+ self.logger.info(f"Testing OpenAI generation with text: {text}")
+
+ test_voices = ["alloy", "echo", "fable", "nova", "onyx", "shimmer"]
+ test_formats = ["wav", "mp3", "opus", "aac"]
+
+ for voice in test_voices[:2]: # Test first two voices only
+ for format in test_formats[:2]: # Test first two formats only
+ await self._test_openai_single_case(text, voice, format)
+
+ async def _test_openai_single_case(self, text: str, voice: str, format: str):
+ """Test single case for OpenAI endpoint"""
+ self.logger.info(f"Testing OpenAI voice: {voice}, format: {format}")
+ self.logger.info(f"Testing OpenAI generation with text: {text}")
+
+ payload = {
+ "model": "tts-1", # Model name doesn't matter
+ "input": text,
+ "voice": voice,
+ "response_format": format,
+ "speed": 1.0
+ }
+
+ async with aiohttp.ClientSession() as session:
+ try:
+ async with session.post(
+ f"{self.base_url}/v1/audio/speech",
+ json=payload,
+ headers={"Content-Type": "application/json"},
+ timeout=aiohttp.ClientTimeout(total=self.config['timeout'])
+ ) as response:
+ if response.status == 200:
+ # Check if we received audio data
+ content_type = response.headers.get('Content-Type', '')
+ if content_type.startswith('audio/'):
+ data = await response.read()
+ if len(data) > 0:
+ self.log_result(
+ "TTS",
+ f"OpenAI Generation ({voice}, {format})",
+ "PASS",
+ f"Generated audio size: {len(data)} bytes"
+ )
+ else:
+ self.log_result(
+ "TTS",
+ f"OpenAI Generation ({voice}, {format})",
+ "WARNING",
+ "Response successful but no audio data"
+ )
+ else:
+ self.log_result(
+ "TTS",
+ f"OpenAI Generation ({voice}, {format})",
+ "ERROR",
+ f"Unexpected content type: {content_type}"
+ )
+ else:
+ error_data = await response.json()
+ self.log_result(
+ "TTS",
+ f"OpenAI Generation ({voice}, {format})",
+ "ERROR",
+ f"Failed with status {response.status}: {error_data}"
+ )
+
+ except Exception as e:
+ self.log_result(
+ "TTS",
+ f"OpenAI Generation ({voice}, {format})",
+ "ERROR",
+ f"Failed to test OpenAI generation: {str(e)}"
+ )
+
+ async def _print_test_summary(self):
+ """Print summary of all test results"""
+ total = len(self.test_results)
+ passed = sum(1 for r in self.test_results if r['status'] == 'PASS')
+ warnings = sum(1 for r in self.test_results if r['status'] == 'WARNING')
+ errors = sum(1 for r in self.test_results if r['status'] == 'ERROR')
+
+ summary = f"""
+ Test Summary
+ ============
+ Total Tests: {total}
+ Passed: {passed}
+ Warnings: {warnings}
+ Errors: {errors}
+ """
+
+ print(summary)
+ self.logger.info(summary)
+
+ async def _select_two_voices(self) -> Optional[Tuple[str, str]]:
+ """Select two different voices for narrator testing."""
+ if not hasattr(self, 'available_voices') or len(self.available_voices) < 2:
+ self.log_result(
+ "TTS",
+ "Voice Selection",
+ "SKIP",
+ "Need at least 2 voices for narrator testing"
+ )
+ return None
+ return self.available_voices[0], self.available_voices[1]
+
+ async def _test_narrator_modes(self):
+ """Test various narrator mode combinations"""
+ self.logger.info("Testing narrator mode combinations")
+
+ voices = await self._select_two_voices()
+ if not voices:
+ return
+
+ character_voice, narrator_voice = voices
+ self.logger.info(f"Using character voice: {character_voice}, narrator voice: {narrator_voice}")
+
+ for test_name, case in self.narrator_test_cases.items():
+ self.logger.info(f"Running narrator test case: {test_name}")
+
+ data = {
+ "text_input": case["text"],
+ "character_voice_gen": character_voice,
+ "narrator_voice_gen": narrator_voice,
+ "narrator_enabled": case.get("narrator_enabled", "true"),
+ "text_not_inside": case.get("text_not_inside", "narrator"),
+ "language": "en",
+ "output_file_name": f"test_narrator_{test_name}_{int(time.time())}",
+ "output_file_timestamp": True
+ }
+
+ async with aiohttp.ClientSession() as session:
+ try:
+ async with session.post(
+ f"{self.base_url}/api/tts-generate",
+ data=data,
+ timeout=aiohttp.ClientTimeout(total=self.config['timeout'])
+ ) as response:
+ if response.status == 200:
+ result = await response.json()
+ if result.get("status") == "generate-success":
+ self.log_result(
+ "TTS",
+ f"Narrator Test {test_name}",
+ "PASS",
+ f"Output file: {result.get('output_file_path')}"
+ )
+ else:
+ self.log_result(
+ "TTS",
+ f"Narrator Test {test_name}",
+ "ERROR",
+ f"Generation failed: {result}"
+ )
+ except Exception as e:
+ self.log_result(
+ "TTS",
+ f"Narrator Test {test_name}",
+ "ERROR",
+ f"Test failed: {str(e)}"
+ )
+
+ async def _test_text_filtering(self):
+ """Test different text filtering options"""
+ self.logger.info("Testing text filtering options")
+
+ test_voice = await self._select_test_voice()
+ if not test_voice:
+ return
+
+ test_text = self.filtering_test_cases["text"]
+
+ for filter_type in self.filtering_test_cases["filters"]:
+ self.logger.info(f"Testing filter type: {filter_type}")
+ self.logger.info(f"Testing with text: {test_text}")
+
+ data = {
+ "text_input": test_text,
+ "character_voice_gen": test_voice,
+ "text_filtering": filter_type,
+ "language": "en",
+ "output_file_name": f"test_filter_{filter_type}_{int(time.time())}",
+ "output_file_timestamp": True
+ }
+
+ async with aiohttp.ClientSession() as session:
+ try:
+ async with session.post(
+ f"{self.base_url}/api/tts-generate",
+ data=data,
+ timeout=aiohttp.ClientTimeout(total=self.config['timeout'])
+ ) as response:
+ if response.status == 200:
+ result = await response.json()
+ if result.get("status") == "generate-success":
+ self.log_result(
+ "TTS",
+ f"Text Filter {filter_type}",
+ "PASS",
+ f"Generated with {filter_type} filtering"
+ )
+ else:
+ self.log_result(
+ "TTS",
+ f"Text Filter {filter_type}",
+ "ERROR",
+ f"Generation failed: {result}"
+ )
+ except Exception as e:
+ self.log_result(
+ "TTS",
+ f"Text Filter {filter_type}",
+ "ERROR",
+ f"Test failed: {str(e)}"
+ )
+
+ async def _test_autoplay(self):
+ """Test autoplay functionality with different volumes"""
+ self.logger.info("Testing autoplay functionality")
+
+ try:
+ test_voice = await self._select_test_voice()
+ if not test_voice:
+ return
+
+ for test_case in self.volume_test_cases:
+ try:
+ volume = test_case["volume"]
+ self.logger.info(f"Testing autoplay with volume: {volume}")
+
+ data = {
+ "text_input": "Testing autoplay functionality with different volume levels.",
+ "character_voice_gen": test_voice,
+ "language": "en",
+ "output_file_name": f"test_autoplay_{int(time.time())}",
+ "autoplay": True,
+ "autoplay_volume": volume
+ }
+
+ async with aiohttp.ClientSession() as session:
+ try:
+ async with session.post(
+ f"{self.base_url}/api/tts-generate",
+ data=data,
+ timeout=aiohttp.ClientTimeout(total=self.config['timeout'])
+ ) as response:
+ if response.status == 200:
+ result = await response.json()
+ expected_success = test_case["should_succeed"]
+
+ if result.get("status") == "generate-success" and expected_success:
+ self.log_result(
+ "TTS",
+ f"Autoplay Volume {volume}",
+ "PASS",
+ "Audio should be playing"
+ )
+ elif result.get("status") != "generate-success" and not expected_success:
+ self.log_result(
+ "TTS",
+ f"Autoplay Volume {volume}",
+ "PASS",
+ "Correctly rejected invalid volume"
+ )
+ else:
+ self.log_result(
+ "TTS",
+ f"Autoplay Volume {volume}",
+ "ERROR",
+ f"Unexpected result: {result}"
+ )
+ except Exception as e:
+ self.log_result(
+ "TTS",
+ f"Autoplay Volume {volume}",
+ "ERROR",
+ f"Test failed: {str(e)}"
+ )
+
+ except asyncio.CancelledError:
+ self.logger.warning("Autoplay test cancelled - cleaning up")
+ raise
+ except Exception as e:
+ self.log_result(
+ "TTS",
+ f"Autoplay Volume {volume}",
+ "ERROR",
+ f"Test failed: {str(e)}"
+ )
+
+ except asyncio.CancelledError:
+ self.logger.warning("Autoplay tests cancelled")
+ raise
+ except Exception as e:
+ self.logger.error(f"Autoplay testing failed: {str(e)}")
+
+ async def _test_error_cases(self):
+ """Test various error cases to ensure proper error handling"""
+ self.logger.info("Testing error cases")
+
+ test_voice = await self._select_test_voice()
+ if not test_voice:
+ return
+
+ for case in self.error_test_cases:
+ self.logger.info(f"Testing error case: {case['case']}")
+
+ # Build base data with known good values
+ data = {
+ "text_input": "Test text",
+ "character_voice_gen": test_voice,
+ "language": "en",
+ "output_file_name": f"test_error_{int(time.time())}"
+ }
+
+ # Modify data based on error case
+ if case['case'] == "invalid_voice":
+ data["character_voice_gen"] = case["voice"]
+ elif case['case'] == "bad_language":
+ data["language"] = case["language"]
+ elif case['case'] == "missing_text":
+ data["text_input"] = case["text"]
+
+ async with aiohttp.ClientSession() as session:
+ try:
+ async with session.post(
+ f"{self.base_url}/api/tts-generate",
+ data=data,
+ timeout=aiohttp.ClientTimeout(total=self.config['timeout'])
+ ) as response:
+ response_data = await response.json()
+
+ if response.status == case["expect_error"]:
+ # Check if error message is user-friendly
+ error_msg = response_data.get("error", "")
+ if error_msg and len(error_msg) < 200 and not any(x in error_msg.lower() for x in ["traceback", "exception", "stack"]):
+ self.log_result(
+ "Error Cases",
+ f"Error Test {case['case']}",
+ "PASS",
+ f"Received expected error: {error_msg}"
+ )
+ else:
+ self.log_result(
+ "Error Cases",
+ f"Error Test {case['case']}",
+ "WARNING",
+ "Error received but message might not be user-friendly"
+ )
+ else:
+ self.log_result(
+ "Error Cases",
+ f"Error Test {case['case']}",
+ "ERROR",
+ f"Expected status {case['expect_error']}, got {response.status}"
+ )
+ except Exception as e:
+ self.log_result(
+ "Error Cases",
+ f"Error Test {case['case']}",
+ "ERROR",
+ f"Test failed: {str(e)}"
+ )
+
+ async def _test_additional_language(self):
+ """Test TTS generation with an additional language if supported"""
+ self.logger.info("Testing additional language support")
+
+ # Check if multiple languages are supported
+ if not hasattr(self, 'current_settings') or not self.current_settings.get('languages_capable'):
+ self.log_result(
+ "TTS",
+ "Additional Language",
+ "SKIP",
+ "Multiple languages not supported by current engine"
+ )
+ return
+
+ test_voice = await self._select_test_voice()
+ if not test_voice:
+ return
+
+ test_language = "es" # Spanish as test language
+ test_text = "Prueba de texto en español"
+
+ data = {
+ "text_input": test_text,
+ "character_voice_gen": test_voice,
+ "language": test_language,
+ "output_file_name": f"test_lang_{int(time.time())}",
+ "output_file_timestamp": True
+ }
+
+ async with aiohttp.ClientSession() as session:
+ try:
+ async with session.post(
+ f"{self.base_url}/api/tts-generate",
+ data=data,
+ timeout=aiohttp.ClientTimeout(total=self.config['timeout'])
+ ) as response:
+ if response.status == 200:
+ result = await response.json()
+ if result.get("status") == "generate-success":
+ self.log_result(
+ "TTS",
+ "Additional Language",
+ "PASS",
+ f"Generated {test_language} audio"
+ )
+ else:
+ self.log_result(
+ "TTS",
+ "Additional Language",
+ "ERROR",
+ f"Generation failed: {result}"
+ )
+ else:
+ self.log_result(
+ "TTS",
+ "Additional Language",
+ "ERROR",
+ f"Unexpected status code: {response.status}"
+ )
+ except Exception as e:
+ self.log_result(
+ "TTS",
+ "Additional Language",
+ "ERROR",
+ f"Test failed: {str(e)}"
+ )
+
+
+ async def run_all_tests(self):
+ """Run all available tests in sequence"""
+ self.logger.info("Starting complete test suite")
+
+ # Network tests
+ await self.run_network_tests()
+
+ # Core API tests
+ await self.run_core_api_tests()
+
+ # TTS tests
+ await self.run_tts_tests()
+
+ # Print summary
+ await self._print_test_summary()
+
+async def main():
+ tester = AllTalkTester()
+ if await tester.setup():
+ await tester.menu()
+ else:
+ print("Setup failed. Exiting.")
+
+if __name__ == "__main__":
+ asyncio.run(main())
\ No newline at end of file
diff --git a/trainer_alltalk/finetune_content.py b/trainer_alltalk/finetune_content.py
index 1631ce4..8c9e4b7 100644
--- a/trainer_alltalk/finetune_content.py
+++ b/trainer_alltalk/finetune_content.py
@@ -4,67 +4,150 @@
class FinetuneContent:
"""CSS and help content for finetune.py"""
custom_css = """
- /* Status indicators and labels */
- .status-label {
- font-size: 1.2em;
- font-weight: bold;
- padding: 10px;
- border-radius: 5px;
- margin: 10px 0;
+ /* Add this to your existing CSS */
+ .gradio-container .prose {
+ max-width: none !important;
+ padding: 0.5rem !important; /* Reduced padding */
+ margin: 0 !important;
}
- .status-indicator {
- padding: 5px 10px;
- border-radius: 3px;
- font-weight: bold;
+ .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 */
}
- /* Layout components */
- .status-box {
- display: flex;
- align-items: center;
- padding: 0.5rem;
- margin-bottom: 0.5rem;
- border-radius: 4px;
- background: var(--background-fill-secondary);
+ /* 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 */
}
- .status-group {
- margin-bottom: 1rem;
- padding: 1rem;
- border-radius: 8px;
- background: var(--background-fill-primary);
+ /* 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;
}
- /* Help section styling */
- .help-section {
- margin-top: 2rem;
- border-top: 1px solid var(--border-color-primary);
- padding-top: 1rem;
+ /* 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;
}
- .help-accordion {
- margin-bottom: 0.5rem;
+ /* 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 */
}
- /* Ensure consistent font size in accordions */
- .gradio-container .prose,
- .gradio-container .prose p,
- .gradio-container .prose ul,
- .gradio-container .prose ol,
- .gradio-container .prose ul li,
- .gradio-container .prose ol li {
- font-size: 1.02em !important; /* Adjust to the preferred size, e.g., 1em or 14px */
- line-height: 1.3; /* Improves readability */
+ /* 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 */
}
- /* Grid layout */
- .check-grid {
- display: grid;
- grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
- gap: 1rem;
- margin-bottom: 1rem;
+ /* 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 */
+ }
+
+ .small-file-upload {
+ min-height: 200px;
+ max-height: 200px;
+ }
+
+ .small-file-upload2 {
+ min-height: 90px;
+ max-height: 90px;
+ }
+
"""
STEP1_QUICKSTART = """
diff --git a/tts_server.py b/tts_server.py
index 2ffcc4e..8d4bfad 100644
--- a/tts_server.py
+++ b/tts_server.py
@@ -1,39 +1,48 @@
-import os
-import sys
-import json
-import time
-import shutil
-import argparse
-import librosa
-import logging
-import importlib
-import subprocess
-from pathlib import Path
-logging.disable(logging.WARNING)
-#####################
-# Webserver Imports #
-#####################
+"""
+AllTalk TTS server implementation for managing and serving text-to-speech generation.
+
+This script provides endpoints for generating TTS, handling voice configurations,
+and managing audio processing, including RVC-based voice conversion and OpenAI-compatible APIs.
+It supports features like streaming, file format transcoding, and real-time configuration updates.
+
+Github: https://github.com/erew123/
+"""
+import warnings
from contextlib import asynccontextmanager
-from fastapi.staticfiles import StaticFiles
-from fastapi.templating import Jinja2Templates
-from fastapi.middleware.cors import CORSMiddleware
-from fastapi import FastAPI, Form, Request, Response, Depends, HTTPException, Query
-from fastapi.responses import JSONResponse, HTMLResponse, RedirectResponse, FileResponse, StreamingResponse
-##########################
-# Generation API Imports #
-##########################
+import argparse
+import asyncio
+from asyncio import Lock
+import importlib
+import inspect
+import json
+import logging
+import os
import re
import html
import uuid
import hashlib
+import sys
+import time
+import subprocess
+from pathlib import Path
+from typing import Union, List, Optional, Tuple
+import aiofiles
+import uvicorn
+from pydantic import BaseModel, ValidationError, Field, field_validator
+from fastapi.staticfiles import StaticFiles
+from fastapi.templating import Jinja2Templates
+from fastapi.middleware.cors import CORSMiddleware
+from fastapi import FastAPI, Form, Request, Response, Depends, HTTPException, Query
+from fastapi.responses import JSONResponse, HTMLResponse, FileResponse, StreamingResponse
import numpy as np
import soundfile as sf
-from typing import Union, Dict, List, Optional
-from pydantic import BaseModel, ValidationError, Field, field_validator
+import librosa
+from config import AlltalkConfig, AlltalkTTSEnginesConfig
+logging.disable(logging.WARNING)
+
########################################################################################
# START-UP # Silence RVC warning about torch.nn.utils.weight_norm even though not used #
########################################################################################
-import warnings
warnings.filterwarnings("ignore", category=UserWarning, module="torch.nn.utils.weight_norm")
warnings.filterwarnings("ignore", category=UserWarning, module="torch.nn.functional", lineno=5476)
# Filter ComplexHalf support warning
@@ -45,114 +54,123 @@ warnings.filterwarnings("ignore", message="1Torch was not compiled with flash at
####################
this_dir = Path(__file__).parent.resolve() # Set this_dir as the current alltalk_tts folder
-####################################################
-# Load params and api_defailts from confignew.json #
-####################################################
-def load_config():
- global branding, output_directory, debug_transcode, debug_tts, debug_tts_variables, engines_available, engine_loaded, debug_rvc, debug_concat, debug_openai
- # Define the path to the confignew.json file & load in api_defaults & params
- configfile_path = this_dir / "confignew.json"
- with open(configfile_path, "r") as configfile:
- configfile_data = json.load(configfile)
- api_defaults = {
- "api_text_filtering": configfile_data["api_def"]["api_text_filtering"],
- "api_narrator_enabled": configfile_data["api_def"]["api_narrator_enabled"],
- "api_text_not_inside": configfile_data["api_def"]["api_text_not_inside"],
- "api_language": configfile_data["api_def"]["api_language"],
- "api_output_file_name": configfile_data["api_def"]["api_output_file_name"],
- "api_output_file_timestamp": configfile_data["api_def"]["api_output_file_timestamp"],
- "api_autoplay": configfile_data["api_def"]["api_autoplay"],
- "api_autoplay_volume": configfile_data["api_def"]["api_autoplay_volume"],
- "api_port_number": configfile_data["api_def"]["api_port_number"],
- "api_allowed_filter": configfile_data["api_def"]["api_allowed_filter"],
- "api_length_stripping": configfile_data["api_def"]["api_length_stripping"],
- "api_max_characters": configfile_data["api_def"]["api_max_characters"],
- "api_use_legacy_api": configfile_data["api_def"]["api_use_legacy_api"],
- "api_legacy_ip_address": configfile_data["api_def"]["api_legacy_ip_address"],
- }
- rvc_settings = {
- "rvc_enabled": configfile_data["rvc_settings"]["rvc_enabled"],
- "rvc_char_model_file": configfile_data["rvc_settings"]["rvc_char_model_file"],
- "rvc_narr_model_file": configfile_data["rvc_settings"]["rvc_narr_model_file"],
- "split_audio": configfile_data["rvc_settings"]["split_audio"],
- "autotune": configfile_data["rvc_settings"]["autotune"],
- "pitch": configfile_data["rvc_settings"]["pitch"],
- "filter_radius": configfile_data["rvc_settings"]["filter_radius"],
- "index_rate": configfile_data["rvc_settings"]["index_rate"],
- "rms_mix_rate": configfile_data["rvc_settings"]["rms_mix_rate"],
- "protect": configfile_data["rvc_settings"]["protect"],
- "hop_length": configfile_data["rvc_settings"]["hop_length"],
- "f0method": configfile_data["rvc_settings"]["f0method"],
- "embedder_model": configfile_data["rvc_settings"]["embedder_model"],
- "training_data_size": configfile_data["rvc_settings"]["training_data_size"],
- }
- branding = configfile_data.get("branding", "")
- debug_transcode = configfile_data.get("debugging").get("debug_transcode")
- debug_tts = configfile_data.get("debugging").get("debug_tts")
- debug_tts_variables = configfile_data.get("debugging").get("debug_tts_variables")
- debug_rvc = configfile_data.get("debugging").get("debug_rvc")
- debug_concat = configfile_data.get("debugging").get("debug_concat")
- debug_openai = configfile_data.get("debugging").get("debug_openai")
- output_directory = this_dir / configfile_data.get("output_folder", "")
- output_directory.mkdir(parents=True, exist_ok=True)
- tts_engines_file = os.path.join(this_dir, "system", "tts_engines", "tts_engines.json")
- with open(tts_engines_file, "r") as f:
- tts_engines_data = json.load(f)
- engines_available = [engine["name"] for engine in tts_engines_data["engines_available"]] # List of the available TTS engines from tts_engines.json
- engine_loaded = tts_engines_data["engine_loaded"] # The currently set TTS engine from tts_engines.json
- selected_model = tts_engines_data["selected_model"]
- # Conditionally import infer_pipeline if rvc_enabled is true
- if rvc_settings["rvc_enabled"]:
- from system.tts_engines.rvc.infer.infer import infer_pipeline
+infer_pipeline = None
+config: AlltalkConfig | None = None
+tts_engines_config: AlltalkTTSEnginesConfig | None = None
+
+def load_config(force_reload = False):
+ """Initialize all configuration instances"""
+ global config, tts_engines_config # pylint: disable=global-statement
+ config = AlltalkConfig.get_instance(force_reload)
+ tts_engines_config = AlltalkTTSEnginesConfig.get_instance(force_reload)
+ after_config_load()
+
+def after_config_load():
+ """Initialize the infer_pipeline based on RVC settings."""
+ global infer_pipeline # pylint: disable=global-statement
+ if config.rvc_settings.rvc_enabled:
+ from system.tts_engines.rvc.infer.infer import infer_pipeline as rvc_pipeline
+ infer_pipeline = rvc_pipeline
else:
infer_pipeline = None
- return configfile_data, api_defaults, rvc_settings, infer_pipeline
-# Call to load in the params and api_defaults
-params, api_defaults, rvc_settings, infer_pipeline = load_config()
+load_config()
+
+##########################
+# Central print function #
+##########################
+def print_message(message, message_type="standard", component="TTS"):
+ """Centralized print function for AllTalk messages
+ 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.)
+ """
+ # 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} tts_server.py")
+ 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():
+ """Print debug message for function entry if debug_func is enabled"""
+ if config.debugging.debug_func:
+ current_func = inspect.currentframe().f_back.f_code.co_name
+ print_message(f"Function entry: {current_func}", "debug_func")
####################
# Check for FFMPEG #
####################
-def check_ffmpeg(this_dir):
+def check_ffmpeg(ffmpeg_dir):
+ """Verify FFmpeg availability in the system."""
+ debug_func_entry()
+ message = ""
+
if sys.platform == "win32":
- # Check for ffmpeg.exe in the specified directory on Windows
- ffmpeg_path = os.path.join(this_dir, "system", "win_ffmpeg", "ffmpeg.exe")
+ ffmpeg_path = os.path.join(ffmpeg_dir, "system", "win_ffmpeg", "ffmpeg.exe")
if os.path.exists(ffmpeg_path):
- return True
+ return True, "FFmpeg found in Windows directory"
+ message = "FFmpeg not found in Windows directory"
else:
- # Check for FFmpeg on Linux and macOS using the command line
try:
subprocess.run(['ffmpeg', '-version'], stdout=subprocess.PIPE, stderr=subprocess.PIPE, check=True)
- return True
+ return True, "FFmpeg found in system PATH"
except (subprocess.CalledProcessError, FileNotFoundError):
- pass
- return False
+ message = "FFmpeg not found in system PATH"
+
+ print_message(message, "warning")
+ return False, message
# Check if FFmpeg is installed
ffmpeg_installed = check_ffmpeg(this_dir)
if not ffmpeg_installed:
- print(f"[{branding}ENG] \033[92mTranscoding :\033[94m ffmpeg not found\033[0m")
- print(f"[{branding}ENG] FFmpeg is not installed. Transcoding will be disabled.")
- print(f"[{branding}ENG] Please install FFmpeg on your system.")
-
- if sys.platform == "win32":
- print(f"[{branding}ENG] \033[92mTranscoding :\033[94m ffmpeg not found\033[0m")
- print(f"[{branding}ENG] Installation instructions for Windows:")
- print(f"[{branding}ENG] Copy the 'ffmpeg.exe' file to '{os.path.join(this_dir, 'system', 'win_ffmpeg')}'")
- else:
- print(f"[{branding}ENG] \033[92mTranscoding :\033[94m ffmpeg not found\033[0m")
- print(f"[{branding}ENG] Installation instructions:")
- print(f"[{branding}ENG] Linux (Debian-based systems): Run 'sudo apt-get install ffmpeg' in the terminal.")
- print(f"[{branding}ENG] macOS: Run 'brew install ffmpeg' in the terminal (requires Homebrew).")
- # You can choose to exit the script or continue without FFmpeg
- # exit(1)
+ print_message("\033[92mTranscoding :\033[91m ffmpeg not found\033[0m", component="ENG")
+ print_message("FFmpeg is not installed. Transcoding will be disabled.", "warning", "ENG")
+ print_message("Please install FFmpeg on your system.", component="ENG")
+ if sys.platform == "win32":
+ print_message("\033[92mTranscoding :\033[91m ffmpeg not found\033[0m", component="ENG")
+ print_message("Installation instructions for Windows:", component="ENG")
+ print_message(f"Copy the 'ffmpeg.exe' file to '{os.path.join(this_dir, 'system', 'win_ffmpeg')}'", component="ENG")
+ else:
+ print_message("\033[92mTranscoding :\033[91m ffmpeg not found\033[0m", component="ENG")
+ print_message("Installation instructions:", component="ENG")
+ print_message("Linux (Debian-based systems): Run 'sudo apt-get install ffmpeg' in the terminal.", component="ENG")
+ print_message("macOS: Run 'brew install ffmpeg' in the terminal (requires Homebrew).", component="ENG")
+
+FFmpeg = None
if ffmpeg_installed:
from ffmpeg.asyncio import FFmpeg
- print(f"[{branding}ENG] \033[92mTranscoding :\033[93m ffmpeg found\033[0m")
-
+ print_message("\033[92mTranscoding :\033[93m ffmpeg found\033[0m", component="ENG")
+
################################
# Check for portaudio on Linux #
################################
@@ -160,36 +178,38 @@ try:
import sounddevice as sd
sounddevice_installed=True
except OSError:
- print(f"[{branding}Startup] \033[91mInfo\033[0m PortAudio library not found. If you wish to play TTS in standalone mode through the API suite")
- print(f"[{branding}Startup] \033[91mInfo\033[0m please install PortAudio. This will not affect any other features or use of Alltalk.")
- print(f"[{branding}Startup] \033[91mInfo\033[0m If you don't know what the API suite is, then this message is nothing to worry about.")
+ print_message("The PortAudio library is not installed. To enable audio playback for TTS in the terminal or console,", "warning")
+ print_message("please install PortAudio. This will not impact other features of Alltalk.", "warning")
+ print_message("You can still play audio through web browsers without PortAudio.", "warning")
+ print_message("Installing PortAudio is optional and not strictly required.")
sounddevice_installed=False
if sys.platform.startswith('linux'):
- print(f"[{branding}Startup] \033[91mInfo\033[0m On Linux, you can use the following command to install PortAudio:")
- print(f"[{branding}Startup] \033[91mInfo\033[0m sudo apt-get install portaudio19-dev")
+ print_message("On Linux, you can use the following command to install PortAudio:", "warning")
+ print_message("sudo apt-get install portaudio19-dev", "warning")
#######################################################################
# Attempt to import the ModelLoader class for the selected TTS engine #
#######################################################################
-if engine_loaded in engines_available:
- loader_module = importlib.import_module(f"system.tts_engines.{engine_loaded}.model_engine")
+if tts_engines_config.is_valid_engine(tts_engines_config.engine_loaded):
+ loader_module = importlib.import_module(f"system.tts_engines.{tts_engines_config.engine_loaded}.model_engine")
tts_class = getattr(loader_module, "tts_class")
# Setup model_engine as the way to call the functions within the Class.
model_engine = tts_class()
else:
- raise ValueError(f"Invalid TTS engine: {engine_loaded}")
+ raise ValueError(f"Invalid TTS engine: {tts_engines_config.engine_loaded}")
##########################################
# Run setup function in the model_engine #
##########################################
@asynccontextmanager
-async def startup_shutdown(no_actual_value_it_demanded_something_be_here):
+async def startup_shutdown(no_actual_value_it_demanded_something_be_here): # pylint: disable=unused-argument
+ """Initialize model engine and handle graceful shutdown. This is a context manager."""
+ debug_func_entry()
try:
await model_engine.setup()
except FileNotFoundError as e:
- print(f"Error during setup: {e}. Continuing without the TTS model.")
+ print_message(f"Error during setup: {e}. Continuing without the TTS model.", "error")
yield
- # Shutdown logic
###############################
# Setup FastAPI with Lifespan #
@@ -204,126 +224,84 @@ app.add_middleware(
allow_headers=["*"],
)
+# Global lock
+model_change_lock = Lock()
##############################
# API Endpoint - /api/reload #
##############################
-model_currently_changing = False
@app.route("/api/reload", methods=["POST"])
async def apifunction_reload(request: Request):
- global model_currently_changing
- if model_currently_changing == True:
+ """Handle API request to change TTS model."""
+ debug_func_entry()
+
+ if model_change_lock.locked(): # Check if the lock is already held
+ print_message("Model change already in progress", "debug_api", "API")
return Response(content=json.dumps({"status": "model-is currently changing"}), media_type="application/json")
- model_currently_changing == True
- requested_model = request.query_params.get("tts_method")
- if requested_model not in model_engine.available_models:
- model_currently_changing == False
- return {"status": "error", "message": "Invalid TTS method specified"}
- # Call the handle_tts_method_change method from the imported class
- success = await model_engine.handle_tts_method_change(requested_model)
- if success:
- model_engine.current_model_loaded = requested_model
- model_currently_changing == False
- return Response(content=json.dumps({"status": "model-success"}), media_type="application/json")
- else:
- model_currently_changing == False
+
+ async with model_change_lock: # Acquire the lock for this operation
+ requested_model = request.query_params.get("tts_method")
+ if requested_model not in model_engine.available_models:
+ print_message(f"Invalid TTS method requested: {requested_model}", "error", "API")
+ return {"status": "error", "message": "Invalid TTS method specified"}
+
+ print_message(f"Attempting to change model to: {requested_model}", "debug_api", "API")
+ success = await model_engine.handle_tts_method_change(requested_model)
+
+ if success:
+ model_engine.current_model_loaded = requested_model
+ print_message(f"Model successfully changed to: {requested_model}", "debug_api", "API")
+ return Response(content=json.dumps({"status": "model-success"}), media_type="application/json")
+
+ print_message(f"Failed to change model to: {requested_model}", "error", "API")
return Response(content=json.dumps({"status": "model-failure"}), media_type="application/json")
-
+
####################################
# API Endpoint - /api/enginereload #
####################################
-import asyncio
-uvicorn_server = None
+uvicorn_server = None # Global variable to track the server
def restart_self():
- """Restart the current script."""
- #print(f"[{branding}ENG] Restarting subprocess...")
+ """Restart the current Python process."""
+ debug_func_entry()
+ print_message("Restarting subprocess...", component="ENG")
os.execv(sys.executable, ['python'] + sys.argv)
async def handle_restart():
- global uvicorn_server
+ """Handle graceful shutdown of uvicorn server before restart."""
+ debug_func_entry()
+ global uvicorn_server # pylint: disable=global-statement,disable=global-variable-not-assigned
+
if uvicorn_server:
- print(f"[{branding}ENG] Stopping uvicorn server...")
+ print_message("Stopping uvicorn server...", component="ENG")
uvicorn_server.should_exit = True
uvicorn_server.force_exit = True
- # Check if the server has stopped
+
while not uvicorn_server.is_stopped:
- await asyncio.sleep(0.1) # Check every 100ms if server is stopped
+ await asyncio.sleep(0.1)
+
restart_self()
@app.post("/api/enginereload")
-async def apifunction_reload(request: Request):
+async def apifunction_enginereload(request: Request):
+ """Handle API request to change TTS engine. Validates engine, updates config, and triggers restart."""
+ debug_func_entry()
+
requested_engine = request.query_params.get("engine")
- tts_engines_file = this_dir / "system" / "tts_engines" / "tts_engines.json"
- tts_engines_file_backup_path = tts_engines_file.with_suffix(".backup") # Backup file path
-
- if requested_engine not in engines_available:
+ if not tts_engines_config.is_valid_engine(requested_engine):
+ print_message(f"Invalid engine requested: {requested_engine}", "error", "API")
return {"status": "error", "message": "Invalid TTS engine specified"}
-
- print(f"[{branding}ENG]")
- print(f"[{branding}ENG] \033[94mChanging model loaded. Please wait.\033[00m")
- print(f"[{branding}ENG]")
- # Function to safely load JSON with error handling and backup restoration
- def safe_load_json(file_path, backup_path=None):
- try:
- # Attempt to open and load the JSON file
- with open(file_path, 'r') as f:
- return json.load(f)
- except FileNotFoundError:
- # Handle missing file case, try restoring from backup or creating default
- print(f"File not found: {file_path}")
- if backup_path and os.path.exists(backup_path):
- print(f"Restoring from backup: {backup_path}")
- with open(backup_path, 'r') as f:
- data = json.load(f)
- # Restore the backup to the original file
- with open(file_path, 'w') as f:
- json.dump(data, f, indent=4)
- print(f"Restored {file_path} from backup.")
- return data
- else:
- raise Exception(f"File {file_path} is missing, and no backup is available.")
- except json.JSONDecodeError as e:
- # Handle JSON corruption case
- print(f"JSON decoding error in file {file_path}: {e}")
- if backup_path and os.path.exists(backup_path):
- print(f"Restoring from backup due to corrupted file: {backup_path}")
- with open(backup_path, 'r') as f:
- data = json.load(f)
- # Restore the backup to the original file
- with open(file_path, 'w') as f:
- json.dump(data, f, indent=4)
- print(f"Restored {file_path} from backup.")
- return data
- else:
- raise Exception(f"File {file_path} is corrupted, and no backup is available.")
+ print_message("", component="ENG")
+ print_message("\033[94mChanging model loaded. Please wait.\033[00m", component="ENG")
+ print_message("", component="ENG")
- # Load the tts_engines.json with resilience
- tts_engines_data = safe_load_json(tts_engines_file, tts_engines_file_backup_path)
-
- # Update the tts_engines_data with the requested engine
- for engine in tts_engines_data["engines_available"]:
- if engine["name"] == requested_engine:
- tts_engines_data["engine_loaded"] = requested_engine
- tts_engines_data["selected_model"] = engine["selected_model"]
- break
-
- # Write the updated JSON data safely and create a backup
try:
- # First, create a backup of the current file before writing
- shutil.copy(tts_engines_file, tts_engines_file_backup_path)
+ tts_engines_config.change_engine(requested_engine).save()
+ print_message(f"Engine configuration updated to: {requested_engine}", "debug_api", "API")
+ finally:
+ tts_engines_config.reload()
+ print_message("Configuration reloaded", "debug_api", "API")
- with open(tts_engines_file, "w") as f:
- json.dump(tts_engines_data, f, indent=4)
- # print(f"Updated and saved {tts_engines_file}. Backup created at {tts_engines_file_backup_path}.")
- except Exception as e:
- print(f"Error writing to {tts_engines_file}: {e}")
- # If writing failed, restore the backup
- shutil.copy(tts_engines_file_backup_path, tts_engines_file)
- raise Exception(f"Failed to save {tts_engines_file}, restored from backup.")
-
- # Start the restart process in the background
asyncio.create_task(handle_restart())
-
return Response(content=json.dumps({"status": "engine-success"}), media_type="application/json")
@@ -333,31 +311,41 @@ async def apifunction_reload(request: Request):
# When this endpoint it called it will set tts_stop_generation in the model_engine to True, which can be used to interrupt the current TTS generation.
@app.put("/api/stop-generation")
async def apifunction_stop_generation():
+ """Handle request to stop ongoing TTS generation."""
+ debug_func_entry()
if model_engine.tts_generating_lock and not model_engine.tts_stop_generation:
model_engine.tts_stop_generation = True
+ print_message("Stopping TTS generation", "debug_api", "API")
return {"message": "Cancelling current TTS generation"}
#############################
# API Endpoint - /api/audio #
#############################
-# Gives web access to the output files as downloads
@app.get("/audio/{filename}")
async def apifunction_get_audio(filename: str):
- audio_path = this_dir / output_directory / filename
+ """Serve audio file from output directory."""
+ debug_func_entry()
+ audio_path = this_dir / config.get_output_directory() / filename
+ if not audio_path.is_file():
+ print_message(f"Audio file not found: {filename}", "error", "API")
+ raise HTTPException(status_code=404, detail="File not found")
return FileResponse(audio_path)
##################################
# API Endpoint - /api/audiocache #
##################################
@app.get("/audiocache/{filename}")
-async def apifunction_get_audio(filename: str):
- audio_path = Path({output_directory}) / filename
+async def apifunction_get_audiocache(filename: str):
+ """Serve cached audio file with caching headers."""
+ debug_func_entry()
+ audio_path = Path(config.get_output_directory()) / filename
if not audio_path.is_file():
+ print_message(f"Cached audio file not found: {filename}", "error", "API")
raise HTTPException(status_code=404, detail="File not found")
+
response = FileResponse(path=audio_path, media_type='audio/wav', filename=filename)
- # Set caching headers
- response.headers["Cache-Control"] = "public, max-age=604800" # Cache for one week
- response.headers["ETag"] = str(audio_path.stat().st_mtime) # Use the file's last modified time as a simple ETag
+ response.headers["Cache-Control"] = "public, max-age=604800"
+ response.headers["ETag"] = str(audio_path.stat().st_mtime)
return response
##############################
@@ -365,303 +353,489 @@ async def apifunction_get_audio(filename: str):
##############################
@app.get("/api/voices")
async def apifunction_get_voices():
- if not model_engine.multivoice_capable:
- return {"status": "error", "message": f"The currently loaded TTS engine '{model_engine.engine_loaded}' does not support multiple voices."}
-
- available_voices = model_engine.voices_file_list()
- return {"status": "success", "voices": available_voices}
+ """Get available voices for current TTS engine."""
+ debug_func_entry()
+
+ try:
+ if not model_engine.multivoice_capable:
+ print_message(f"Engine '{model_engine.engine_loaded}' does not support multiple voices", "warning", "API")
+ return {"status": "error", "message": f"The currently loaded TTS engine '{model_engine.engine_loaded}' does not support multiple voices."}
+
+ available_voices = model_engine.voices_file_list()
+ print_message(f"Successfully retrieved {len(available_voices)} available voices", "debug_api", "API")
+ return {"status": "success", "voices": available_voices}
+ except AttributeError as e:
+ print_message(f"Attribute error in model engine: {str(e)}", "error", "API")
+ return JSONResponse(
+ content={"status": "error", "message": "Model engine configuration is invalid. Please check your setup."},
+ status_code=500
+ )
+ except FileNotFoundError as e:
+ print_message(f"Voices file not found: {str(e)}", "error", "API")
+ return JSONResponse(
+ content={"status": "error", "message": "The voices file could not be located. Please ensure the file exists."},
+ status_code=404
+ )
#################################
# API Endpoint - /api/rvcvoices #
#################################
@app.get("/api/rvcvoices")
-async def apifunction_get_voices():
- global params, api_defaults, rvc_settings, infer_pipeline
- params, api_defaults, rvc_settings, infer_pipeline = load_config()
- if not rvc_settings["rvc_enabled"]:
- return {"status": "success", "rvcvoices": ["Disabled"]}
- # Define the directory path
- directory = os.path.join(this_dir, "models", "rvc_voices")
- # Check if the directory exists
- if not os.path.exists(directory):
- return {"status": "success", "rvcvoices": ["Disabled"]}
- # List all .pth files recursively in the directory
- pth_files = []
- for root, dirs, files in os.walk(directory):
- for file in files:
- if file.endswith(".pth"):
- pth_files.append(os.path.relpath(os.path.join(root, file), start=directory))
- # If no .pth files found, return "Disabled"
- if not pth_files:
- return {"status": "success", "rvcvoices": ["Disabled"]}
- # Sort the list alphabetically
- pth_files.sort()
- # Add "Disabled" at the beginning of the list
- pth_files.insert(0, "Disabled")
- return {"status": "success", "rvcvoices": pth_files}
+async def apifunction_get_rvcvoices():
+ """Get available RVC voice models."""
+ debug_func_entry()
+ response_data = {"status": "success", "rvcvoices": ["Disabled"]}
+
+ try:
+ load_config()
+ if not config.rvc_settings.rvc_enabled:
+ return response_data
+
+ directory = os.path.join(this_dir, "models", "rvc_voices")
+ if not os.path.exists(directory):
+ print_message("RVC voices directory not found", "warning", "API")
+ return response_data
+
+ if not os.access(directory, os.R_OK):
+ print_message("No read permission for RVC voices directory", "error", "API")
+ raise PermissionError("Cannot access RVC voices directory")
+
+ pth_files = []
+ for root, _dirs, files in os.walk(directory):
+ for file in files:
+ if file.endswith(".pth"):
+ pth_files.append(os.path.relpath(os.path.join(root, file), start=directory))
+
+ if pth_files:
+ response_data["rvcvoices"] = ["Disabled"] + sorted(pth_files)
+ return response_data
+
+ except FileNotFoundError:
+ print_message("RVC voices directory not found", "error", "API")
+ return JSONResponse(
+ content={"status": "error", "message": "RVC voices directory not found"},
+ status_code=404
+ )
+ except PermissionError as e:
+ print_message(f"Permission denied accessing RVC directory: {str(e)}", "error", "API")
+ return JSONResponse(
+ content={"status": "error", "message": "Permission denied accessing RVC directory"},
+ status_code=403
+ )
+ except OSError as e:
+ print_message(f"OS error processing RVC voices: {str(e)}", "error", "API")
+ return JSONResponse(
+ content={"status": "error", "message": "System error accessing RVC voices"},
+ status_code=500
+ )
#####################################
# API Endpoint - /api/reload_config #
#####################################
@app.get("/api/reload_config")
async def apifunction_reload_config():
- global params, api_defaults, rvc_settings, infer_pipeline
- model_engine.available_models = model_engine.scan_models_folder()
- params, api_defaults, rvc_settings, infer_pipeline = load_config()
- return Response("Config file reloaded successfully")
+ """Reload configuration settings."""
+ debug_func_entry()
+
+ try:
+ load_config(True)
+ print_message("Configuration reloaded successfully", "debug_api", "API")
+ return Response("Config file reloaded successfully")
+ except json.JSONDecodeError as e:
+ print_message(f"Invalid JSON in configuration: {str(e)}", "error", "API")
+ return JSONResponse(content={"status": "error", "message": "Invalid configuration format"}, status_code=400)
+ except (ValueError, TypeError) as e:
+ print_message(f"Invalid configuration value: {str(e)}", "error", "API")
+ return JSONResponse(content={"status": "error", "message": "Invalid configuration"}, status_code=400)
+ except OSError as e:
+ print_message(f"Error accessing configuration file: {str(e)}", "error", "API")
+ return JSONResponse(content={"status": "error", "message": "Could not access configuration"}, status_code=500)
#############################
# API Endpoint - /api/ready #
#############################
@app.get("/api/ready")
async def apifunction_ready():
- if model_engine.setup_has_run:
- return Response("Ready")
- else:
- return Response("Unloaded")
+ """Check if model engine setup has completed."""
+ debug_func_entry()
+ status = "Ready" if model_engine.setup_has_run else "Unloaded"
+ print_message(f"Engine status: {status}", "debug_api", "API")
+ return Response(status)
#######################################
# API Endpoint - /api/currentsettings #
#######################################
@app.get('/api/currentsettings')
def apifunction_get_current_settings():
- settings = {
- "engines_available": engines_available, # Lists the currently available TTS engines
- "current_engine_loaded": model_engine.engine_loaded, # Lists the currently loaded TTS engine
- "models_available": [{"name": name} for name in model_engine.available_models.keys()], # Lists the currently available models that can be loaded on this TTS engine.
- "current_model_loaded": model_engine.current_model_loaded, # Lists the currently loaded model
- "manufacturer_name": model_engine.manufacturer_name, # The name of the person/company/group who made the current TTS engine
- "audio_format": model_engine.audio_format, # The default audio format the the currently loaded TTS engine generates in
- "deepspeed_capable": model_engine.deepspeed_capable, # Is the TTS engine capable of supporting DeepSpeed
- "deepspeed_available": model_engine.deepspeed_available, # Was DeepSpeed detected as being available for use
- "deepspeed_enabled": model_engine.deepspeed_enabled, # Is DeepSpeed current enabled or disabled
- "generationspeed_capable": model_engine.generationspeed_capable, # Does the TTS engine support changing speed of generated TTS
- "generationspeed_set": model_engine.generationspeed_set, # What is the currently set TTS speed
- "lowvram_capable": model_engine.lowvram_capable, # Does the TTS engine support moving the model between VRAM and System Ram
- "lowvram_enabled": model_engine.lowvram_enabled, # What is the currently enabled/disabled state of low vram
- "pitch_capable": model_engine.pitch_capable, # Does the TTS engine support pitch on tts generation
- "pitch_set": model_engine.pitch_set, # What is the currently set value for pitch
- "repetitionpenalty_capable": model_engine.repetitionpenalty_capable, # Does the TTS engine support differnt repitition penalties
- "repetitionpenalty_set": model_engine.repetitionpenalty_set, # What is the currently set repetition penalty
- "streaming_capable": model_engine.streaming_capable, # Does the model support streaming TTS generation
- "temperature_capable": model_engine.temperature_capable, # Does the TTS engine support model temperature settings
- "temperature_set": model_engine.temperature_set, # What is the currently set temperature
- "ttsengines_installed": model_engine.engine_installed, #
- "languages_capable": model_engine.languages_capable, # Does the TTS engine support multi languages
- "multivoice_capable": model_engine.multivoice_capable, # Does the TTS engine support multi voice models
- "multimodel_capable": model_engine.multimodel_capable, # Does the engine support loading many different models
- }
- return settings
+ """Get comprehensive dictionary of current engine settings and capabilities."""
+ debug_func_entry()
+ try:
+ load_config()
+ settings = {
+ "engines_available": tts_engines_config.get_engine_names_available(),
+ "current_engine_loaded": model_engine.engine_loaded,
+ "models_available": [{"name": name} for name in model_engine.available_models.keys()],
+ "current_model_loaded": model_engine.current_model_loaded,
+ "manufacturer_name": model_engine.manufacturer_name,
+ "audio_format": model_engine.audio_format,
+ "deepspeed_capable": model_engine.deepspeed_capable,
+ "deepspeed_available": model_engine.deepspeed_available,
+ "deepspeed_enabled": model_engine.deepspeed_enabled,
+ "generationspeed_capable": model_engine.generationspeed_capable,
+ "generationspeed_set": model_engine.generationspeed_set,
+ "lowvram_capable": model_engine.lowvram_capable,
+ "lowvram_enabled": model_engine.lowvram_enabled,
+ "pitch_capable": model_engine.pitch_capable,
+ "pitch_set": model_engine.pitch_set,
+ "repetitionpenalty_capable": model_engine.repetitionpenalty_capable,
+ "repetitionpenalty_set": model_engine.repetitionpenalty_set,
+ "streaming_capable": model_engine.streaming_capable,
+ "temperature_capable": model_engine.temperature_capable,
+ "temperature_set": model_engine.temperature_set,
+ "ttsengines_installed": model_engine.engine_installed,
+ "languages_capable": model_engine.languages_capable,
+ "multivoice_capable": model_engine.multivoice_capable,
+ "multimodel_capable": model_engine.multimodel_capable,
+ }
+ print_message("Current settings retrieved successfully", "debug_api", "API")
+ return settings
+ except AttributeError as e:
+ print_message(f"Missing required engine attribute: {str(e)}", "error", "API")
+ return JSONResponse(
+ content={"status": "error", "message": "Invalid engine configuration"},
+ status_code=500
+ )
+ except KeyError as e:
+ print_message(f"Missing required configuration key: {str(e)}", "error", "API")
+ return JSONResponse(
+ content={"status": "error", "message": "Invalid engine configuration"},
+ status_code=500
+ )
+ except (ValueError, TypeError) as e:
+ print_message(f"Invalid configuration value: {str(e)}", "error", "API")
+ return JSONResponse(
+ content={"status": "error", "message": "Invalid configuration value"},
+ status_code=500
+ )
######################################
# API Endpoint - /api/lowvramsetting #
-######################################
+######################################
@app.post("/api/lowvramsetting")
-async def apifunction_low_vram(request: Request, new_low_vram_value: bool):
+async def apifunction_low_vram(_request: Request, new_low_vram_value: bool):
+ """Handle LowVRAM mode toggle. Updates model device allocation between VRAM and system RAM."""
+ debug_func_entry()
+
+ response_content = {"status": "error", "message": ""}
+
+ # Early return for unsupported engine
if not model_engine.lowvram_capable:
- return {"status": "error", "message": f"The currently loaded TTS engine '{model_engine.engine_loaded}' does not support lowvram."}
+ msg = f"The currently loaded TTS engine '{model_engine.engine_loaded}' does not support lowvram."
+ print_message(f"Engine '{model_engine.engine_loaded}' does not support lowvram", "error", "API")
+ response_content["message"] = msg
+ return Response(content=json.dumps(response_content))
+
try:
if new_low_vram_value is None:
raise ValueError("Missing 'low_vram' parameter")
+
+ # Handle no-change case
if model_engine.lowvram_enabled == new_low_vram_value:
- return Response(content=json.dumps({"status": "success", "message": f"[{branding}Model] LowVRAM is already {'enabled' if new_low_vram_value else 'disabled'}.",}))
- model_engine.lowvram_enabled = new_low_vram_value
- await model_engine.unload_model()
-
- if model_engine.cuda_is_available:
- if model_engine.lowvram_enabled:
- model_engine.device = "cpu"
- print(f"[{branding}Engine] \033[94mLowVRAM Enabled.\033[0m Model will move between \033[93mVRAM(cuda) <> System RAM(cpu)\033[0m")
- else:
- model_engine.device = "cuda"
- print(f"[{branding}Engine] \033[94mLowVRAM Disabled.\033[0m Model will stay in \033[93mVRAM(cuda)\033[0m")
- await model_engine.setup()
+ msg = f"LowVRAM is already {'enabled' if new_low_vram_value else 'disabled'}"
+ print_message(msg, "debug_api", "API")
+ response_content.update({"status": "success", "message": msg})
else:
- # Handle the case where CUDA is not available
- print(f"[{branding}Engine] \033[91mError:\033[0m Nvidia CUDA is not available on this system. Unable to use LowVRAM mode.")
- model_engine.lowvram_enabled = False
-
- return Response(content=json.dumps({"status": "lowvram-success"}))
- except Exception as e:
- return Response(content=json.dumps({"status": "error", "message": str(e)}))
+ # Process LowVRAM change
+ model_engine.lowvram_enabled = new_low_vram_value
+ await model_engine.unload_model()
+
+ if model_engine.cuda_is_available:
+ if model_engine.lowvram_enabled:
+ model_engine.device = "cpu"
+ print_message("\033[94mLowVRAM Enabled.\033[0m Model will move between \033[93mVRAM(cuda) <> System RAM(cpu)\033[0m", component="ENG")
+ else:
+ model_engine.device = "cuda"
+ print_message("\033[94mLowVRAM Disabled.\033[0m Model will stay in \033[93mVRAM(cuda)\033[0m", component="ENG")
+ await model_engine.setup()
+ else:
+ print_message("Nvidia CUDA is not available on this system. Unable to use LowVRAM mode.", "error", "ENG")
+ model_engine.lowvram_enabled = False
+
+ response_content.update({"status": "lowvram-success", "message": ""})
+
+ except ValueError as e:
+ print_message(f"Invalid parameter value: {str(e)}", "error", "API")
+ response_content["message"] = str(e)
+ except AttributeError as e:
+ print_message(f"Model engine configuration error: {str(e)}", "error", "API")
+ response_content["message"] = "Invalid model engine configuration"
+ except RuntimeError as e:
+ print_message(f"CUDA/Device error: {str(e)}", "error", "API")
+ response_content["message"] = "Device allocation error"
+ except json.JSONEncodeError as e:
+ print_message(f"JSON encoding error: {str(e)}", "error", "API")
+ response_content["message"] = "Response encoding error"
+ except OSError as e:
+ print_message(f"System resource error: {str(e)}", "error", "API")
+ response_content["message"] = "System resource error"
+
+ return Response(content=json.dumps(response_content))
#################################
# API Endpoint - /api/deepspeed #
#################################
@app.post("/api/deepspeed")
-async def deepspeed(request: Request, new_deepspeed_value: bool):
+async def deepspeed(_request: Request, new_deepspeed_value: bool):
+ """Handle DeepSpeed mode toggle. Verifies capability and handles model changes."""
+ debug_func_entry()
+
+ response_content = {"status": "error", "message": ""}
+
+ # Early check for DeepSpeed capability
if not model_engine.deepspeed_capable or not model_engine.deepspeed_available:
- return {"status": "error", "message": f"The currently loaded TTS engine '{model_engine.engine_loaded}' does not support DeepSpeed or DeepSpeed is not available on this system."}
+ msg = f"The currently loaded TTS engine '{model_engine.engine_loaded}' does not support DeepSpeed or DeepSpeed is not available on this system."
+ print_message("DeepSpeed not supported or available", "error", "API")
+ response_content["message"] = msg
+ return Response(content=json.dumps(response_content))
+
try:
if new_deepspeed_value is None:
raise ValueError("Missing 'deepspeed' parameter")
+
+ # Handle no-change case
if model_engine.deepspeed_enabled == new_deepspeed_value:
- return Response(
- content=json.dumps({"status": "success", "message": f"DeepSpeed is already {'enabled' if new_deepspeed_value else 'disabled'}.",}))
- model_engine.deepspeed_enabled = new_deepspeed_value
- await model_engine.handle_deepspeed_change(new_deepspeed_value)
- return Response(content=json.dumps({"status": "deepspeed-success"}))
- except Exception as e:
- return Response(content=json.dumps({"status": "error", "message": str(e)}))
+ msg = f"DeepSpeed is already {'enabled' if new_deepspeed_value else 'disabled'}"
+ print_message(msg, "debug_api", "API")
+ response_content = {"status": "success", "message": msg}
+ else:
+ # Process DeepSpeed change
+ model_engine.deepspeed_enabled = new_deepspeed_value
+ await model_engine.handle_deepspeed_change(new_deepspeed_value)
+ print_message(f"DeepSpeed {'enabled' if new_deepspeed_value else 'disabled'}", "debug_api", "API")
+ response_content = {"status": "deepspeed-success"}
+
+ except ValueError as e:
+ print_message(f"Value error: {str(e)}", "error", "API")
+ response_content["message"] = f"Invalid input: {str(e)}"
+ except AttributeError as e:
+ print_message(f"Attribute error: {str(e)}", "error", "API")
+ response_content["message"] = "Unexpected attribute error. Check model engine configuration."
+ except RuntimeError as e:
+ print_message(f"Runtime error: {str(e)}", "error", "API")
+ response_content["message"] = f"Runtime error: {str(e)}"
+ except TypeError as e:
+ print_message(f"Type error: {str(e)}", "error", "API")
+ response_content["message"] = f"Type error: {str(e)}"
+ except OSError as e:
+ print_message(f"System error: {str(e)}", "error", "API")
+ response_content["message"] = "System resource or file error"
+ except json.JSONEncodeError as e:
+ print_message(f"JSON encoding error: {str(e)}", "error", "API")
+ response_content["message"] = "Error encoding response"
+ except ImportError as e:
+ print_message(f"DeepSpeed import error: {str(e)}", "error", "API")
+ response_content["message"] = "DeepSpeed module not properly installed"
+ except MemoryError as e:
+ print_message(f"Memory allocation error: {str(e)}", "error", "API")
+ response_content["message"] = "Insufficient memory for DeepSpeed operation"
+
+ return Response(content=json.dumps(response_content))
#################################
# API Endpoint - /api/voice2rvc #
#################################
@app.post("/api/voice2rvc")
-async def voice2rvc(input_tts_path: str = Form(...), output_rvc_path: str = Form(...), pth_name: str = Form(...), pitch: str = Form(...), method: str = Form(...)):
- try:
- # Handle "Disabled" case
- if pth_name.lower() in ["disabled", "disable"]:
- print(f"[{branding}TTS] \033[94mVoice2RVC Convert: No voice was specified or the name was Disabled\033[0m")
- return {"status": "error", "message": "No voice was specified or the name was Disabled"}
- input_tts_path = Path(input_tts_path)
- output_rvc_path = Path(output_rvc_path)
- # Check if input file exists
- if not input_tts_path.is_file():
- raise HTTPException(status_code=400, detail=f"Input file {input_tts_path} does not exist.")
- # Define the model path based on the selected model name
- pth_path = this_dir / "models" / "rvc_voices" / pth_name
- # Check if model file exists
- if not pth_path.is_file():
- raise HTTPException(status_code=400, detail=f"Model file {pth_path} does not exist.")
- # Run the RVC conversion
- result_path = run_voice2rvc(input_tts_path, output_rvc_path, pth_path, pitch, method)
- if result_path:
- return {"status": "success", "output_path": str(result_path)}
- else:
- raise HTTPException(status_code=500, detail="RVC conversion failed.")
- except Exception as e:
- print(f"Error during Voice2RVC conversion: {e}")
- raise HTTPException(status_code=500, detail=str(e))
+async def voice2rvc(input_tts_path: str = Form(...), output_rvc_path: str = Form(...),
+ pth_name: str = Form(...), pitch: str = Form(...), method: str = Form(...)):
+ """Handle voice conversion using RVC. Processes input audio through specified RVC model."""
+ debug_func_entry()
-# Define the run_rvc function
-def run_voice2rvc(input_tts_path, output_rvc_path, pth_path, pitch, method):
- print(f"[{branding}GEN] \033[94mVoice2RVC Convert: Started\033[0m")
+ try:
+ if pth_name.lower() in ["disabled", "disable"]:
+ print_message("\033[94mVoice2RVC Convert: No voice was specified or the name was Disabled\033[0m")
+ return {"status": "error", "message": "No voice was specified or the name was Disabled"}
+
+ input_tts_path = Path(input_tts_path)
+ output_rvc_path = Path(output_rvc_path)
+
+ if not input_tts_path.is_file():
+ print_message(f"Input file not found: {input_tts_path}", "error")
+ raise HTTPException(status_code=400, detail=f"Input file {input_tts_path} does not exist.")
+
+ pth_path = this_dir / "models" / "rvc_voices" / pth_name
+ if not pth_path.is_file():
+ print_message(f"RVC model file not found: {pth_path}", "error")
+ raise HTTPException(status_code=400, detail=f"Model file {pth_path} does not exist.")
+
+ print_message(f"Starting RVC conversion with model: {pth_name}", "debug_rvc")
+ result_path = run_voice2rvc(input_tts_path, output_rvc_path, pth_path, pitch, method)
+
+ if result_path:
+ print_message("RVC conversion completed successfully", "debug_rvc")
+ return {"status": "success", "output_path": str(result_path)}
+ print_message("RVC conversion failed", "error")
+ raise HTTPException(status_code=500, detail="RVC conversion failed.")
+
+ except Exception as e:
+ print_message(f"Error during Voice2RVC conversion: {e}", "error")
+ raise HTTPException(status_code=500, detail=str(e)) from e
+
+def run_voice2rvc(input_tts_path, output_rvc_path, pth_path, pitch, method) -> Optional[str]:
+ """Run RVC conversion on input audio using specified model and parameters."""
+ debug_func_entry()
+
+ print_message("\033[94mVoice2RVC Convert: Started\033[0m", component="GEN")
generate_start_time = time.time()
- f0up_key = pitch
- filter_radius = rvc_settings["filter_radius"]
- index_rate = rvc_settings["index_rate"]
- rms_mix_rate = rvc_settings["rms_mix_rate"]
- protect = rvc_settings["protect"]
- hop_length = rvc_settings["hop_length"]
- f0method = method
- split_audio = rvc_settings["split_audio"]
- f0autotune = rvc_settings["autotune"]
- embedder_model = rvc_settings["embedder_model"]
- training_data_size = rvc_settings["training_data_size"]
- # Convert path variables to strings
+
+ # Get settings from config
+ settings = {
+ "f0up_key": pitch,
+ "filter_radius": config.rvc_settings.filter_radius,
+ "index_rate": config.rvc_settings.index_rate,
+ "rms_mix_rate": config.rvc_settings.rms_mix_rate,
+ "protect": config.rvc_settings.protect,
+ "hop_length": config.rvc_settings.hop_length,
+ "f0method": method, # This might need to be converted or mapped
+ "split_audio": config.rvc_settings.split_audio,
+ "f0autotune": config.rvc_settings.autotune,
+ "embedder_model": config.rvc_settings.embedder_model,
+ "training_data_size": config.rvc_settings.training_data_size,
+ }
+
input_tts_path = str(input_tts_path)
pth_path = str(pth_path)
- # Check if the model file exists
+
if not os.path.isfile(pth_path):
- print(f"Model file {pth_path} does not exist. Exiting.")
- return
- # Get the directory of the model file
+ print_message(f"Model file {pth_path} does not exist. Exiting.", "error", "GEN")
+ return None
+
model_dir = os.path.dirname(pth_path)
- # Find all .index files in the model directory
index_files = [file for file in os.listdir(model_dir) if file.endswith(".index")]
- if len(index_files) == 1:
- index_path = str(os.path.join(model_dir, index_files[0]))
- else:
- index_path = ""
- # Call the infer_pipeline function
- infer_pipeline(f0up_key, filter_radius, index_rate, rms_mix_rate, protect, hop_length, f0method,
- input_tts_path, output_rvc_path, pth_path, index_path, split_audio, f0autotune, embedder_model, training_data_size, debug_rvc)
- generate_end_time = time.time()
- generate_elapsed_time = generate_end_time - generate_start_time
- print(f"[{branding}GEN] \033[94mVoice2RVC Convert: \033[91m{generate_elapsed_time:.2f} seconds.\033[0m")
+ index_path = str(os.path.join(model_dir, index_files[0])) if len(index_files) == 1 else ""
+
+ print_message(f"Using RVC model: {os.path.basename(pth_path)}", "debug_rvc", "GEN")
+ print_message(f"Using index file: {os.path.basename(index_path) if index_path else 'None'}", "debug_rvc", "GEN")
+
+ infer_pipeline(settings["f0up_key"], settings["filter_radius"], settings["index_rate"],
+ settings["rms_mix_rate"], settings["protect"], settings["hop_length"],
+ settings["f0method"], input_tts_path, output_rvc_path, pth_path, index_path,
+ settings["split_audio"], settings["f0autotune"], settings["embedder_model"],
+ settings["training_data_size"], config.debugging.debug_rvc)
+
+ generate_elapsed_time = time.time() - generate_start_time
+ print_message(f"\033[94mVoice2RVC Convert: \033[91m{generate_elapsed_time:.2f} seconds.\033[0m", component="GEN")
+
return output_rvc_path
##################################
# Transcode between file formats #
##################################
async def transcode_audio(input_file, output_format):
- print(f"[{branding}Debug] *************************************************",) if debug_transcode else None
- print(f"[{branding}Debug] transcode_audio function called (debug_transcode)",) if debug_transcode else None
- print(f"[{branding}Debug] *************************************************",) if debug_transcode else None
- print(f"[{branding}Debug] Input file : {input_file}") if debug_transcode else None
- print(f"[{branding}Debug] Output format : {output_format}") if debug_transcode else None
+ """Transcode audio files between different formats using FFmpeg. Supports wav, mp3, flac, aac, and opus formats."""
+ debug_func_entry()
+
+ print_message("*************************************************", "debug_transcode")
+ print_message("transcode_audio function called (debug_transcode)", "debug_transcode")
+ print_message("*************************************************", "debug_transcode")
+ print_message(f"Input file : {input_file}", "debug_transcode")
+ print_message(f"Output format : {output_format}", "debug_transcode")
+
if output_format == "Disabled":
- print(f"[{branding}Debug] Transcode format is set to Disabled so skipping transcode.") if debug_transcode else None
+ print_message("Transcode format is set to Disabled so skipping transcode.", "debug_transcode")
return input_file
+
if not ffmpeg_installed:
- print(f"[{branding}TTS] FFmpeg is not installed. Format conversion is not possible.")
- raise Exception("FFmpeg is not installed. Format conversion is not possible.")
- # Get the file extension of the input file
+ print_message("FFmpeg is not installed. Format conversion is not possible.", "error")
+ raise RuntimeError("FFmpeg is not installed. Format conversion is not possible.")
+
input_extension = os.path.splitext(input_file)[1][1:].lower()
- print(f"[{branding}Debug] Input file extension: {input_extension}") if debug_transcode else None
- # Check if the input extension matches the requested output format
+ print_message(f"Input file extension: {input_extension}", "debug_transcode")
+
if input_extension == output_format.lower():
- print(f"[{branding}Debug] Input file is already in the requested format: {output_format}") if debug_transcode else None
+ print_message(f"Input file is already in the requested format: {output_format}", "debug_transcode")
return input_file
+
output_file = os.path.splitext(input_file)[0] + f".{output_format}"
- print(f"[{branding}Debug] Output file: {output_file}") if debug_transcode else None
- ffmpeg_path = "ffmpeg" # Default path for Linux and macOS
- if sys.platform == "win32":
- ffmpeg_path = os.path.join(this_dir, "system", "win_ffmpeg", "ffmpeg.exe")
- ffmpeg = (FFmpeg(ffmpeg_path).option("y").input(input_file).output(output_file))
- print(f"[{branding}Debug] Transcoding to {output_format}") if debug_transcode else None
+ print_message(f"Output file: {output_file}", "debug_transcode")
+
+ ffmpeg_path = os.path.join(this_dir, "system", "win_ffmpeg", "ffmpeg.exe") if sys.platform == "win32" else "ffmpeg"
+ ffmpeg = FFmpeg(ffmpeg_path).option("y").input(input_file).output(output_file)
+
+ print_message(f"Transcoding to {output_format}", "debug_transcode")
+
+ # Configure format-specific options
if output_format == "opus":
- print(f"[{branding}Debug] Configuring Opus options") if debug_transcode else None
- ffmpeg.output(output_file, {"codec:a": "libopus", "b:a": "128k", "vbr": "on", "compression_level": 10, "frame_duration": 60, "application": "voip"})
+ print_message("Configuring Opus options", "debug_transcode")
+ ffmpeg.output(output_file, {
+ "codec:a": "libopus",
+ "b:a": "128k",
+ "vbr": "on",
+ "compression_level": 10,
+ "frame_duration": 60,
+ "application": "voip"
+ })
elif output_format == "aac":
- print(f"[{branding}Debug] Configuring AAC options") if debug_transcode else None
+ print_message("Configuring AAC options", "debug_transcode")
ffmpeg.output(output_file, {"codec:a": "aac", "b:a": "192k"})
elif output_format == "flac":
- print(f"[{branding}Debug] Configuring FLAC options") if debug_transcode else None
+ print_message("Configuring FLAC options", "debug_transcode")
ffmpeg.output(output_file, {"codec:a": "flac", "compression_level": 8})
elif output_format == "wav":
- print(f"[{branding}Debug] Configuring WAV options") if debug_transcode else None
+ print_message("Configuring WAV options", "debug_transcode")
ffmpeg.output(output_file, {"codec:a": "pcm_s16le"})
elif output_format == "mp3":
- print(f"[{branding}Debug] Configuring MP3 options") if debug_transcode else None
+ print_message("Configuring MP3 options", "debug_transcode")
ffmpeg.output(output_file, {"codec:a": "libmp3lame", "b:a": "192k"})
else:
- print(f"[{branding}TTS] Unsupported output format: {output_format}")
+ print_message(f"Unsupported output format: {output_format}", "error")
raise ValueError(f"Unsupported output format: {output_format}")
+
try:
- print(f"[{branding}Debug] Starting transcoding process") if debug_transcode else None
+ print_message("Starting transcoding process", "debug_transcode")
await ffmpeg.execute()
- print(f"[{branding}Debug] Transcoding completed successfully") if debug_transcode else None
+ print_message("Transcoding completed successfully", "debug_transcode")
except Exception as e:
- print(f"[{branding}TTS] Error occurred during transcoding: {str(e)}")
+ print_message(f"Error occurred during transcoding: {str(e)}", "error")
raise
- print(f"[{branding}Debug] Deleting original input file") if debug_transcode else None
+
+ print_message("Deleting original input file", "debug_transcode")
os.remove(input_file)
- print(f"[{branding}Debug] Transcoding process completed") if debug_transcode else None
- print(f"[{branding}Debug] Transcoded file: {output_file}") if debug_transcode else None
+
+ print_message("Transcoding process completed", "debug_transcode")
+ print_message(f"Transcoded file: {output_file}", "debug_transcode")
return output_file
##############################
# Central Transcode function #
##############################
async def transcode_audio_if_necessary(output_file, model_audio_format, output_audio_format):
- if debug_transcode:
- print(f"[{branding}Debug] model_engine.audio_format is:", model_audio_format)
- print(f"[{branding}Debug] audio format is:", output_audio_format)
- print(f"[{branding}Debug] Entering the transcode condition")
+ """Transcode audio to requested format if needed and generate appropriate URLs."""
+ debug_func_entry()
+
+ print_message(f"model_engine.audio_format is: {model_audio_format}", "debug_transcode")
+ print_message(f"audio format is: {output_audio_format}", "debug_transcode")
+ print_message("Entering the transcode condition", "debug_transcode")
+
try:
- if debug_transcode:
- print(f"[{branding}Debug] Calling transcode_audio function")
+ print_message("Calling transcode_audio function", "debug_transcode")
output_file = await transcode_audio(output_file, output_audio_format)
- if debug_transcode:
- print(f"[{branding}Debug] Transcode completed successfully")
+ print_message("Transcode completed successfully", "debug_transcode")
except Exception as e:
- print(f"[{branding}Debug] Error occurred during transcoding:", str(e))
+ print_message(f"Error occurred during transcoding: {str(e)}", "error")
raise
- if debug_transcode:
- print(f"[{branding}Debug] Transcode condition completed")
- # Update the output file path and URLs based on the transcoded file
- if debug_tts:
- print(f"[{branding}Debug] Updating output file paths and URLs")
- if params["api_def"]["api_use_legacy_api"]:
- output_file_url = f'http://{api_defaults["api_legacy_ip_address"]}:{api_defaults["api_port_number"]}/audio/{os.path.basename(output_file)}'
- output_cache_url = f'http://{api_defaults["api_legacy_ip_address"]}:{api_defaults["api_port_number"]}/audiocache/{os.path.basename(output_file)}'
+
+ print_message("Transcode condition completed", "debug_transcode")
+ print_message("Updating output file paths and URLs", "debug_tts")
+
+ # Generate appropriate URLs based on API configuration
+ if config.api_def.api_use_legacy_api:
+ output_file_url = f'http://{config.api_def.api_legacy_ip_address}:{config.api_def.api_port_number}/audio/{os.path.basename(output_file)}'
+ output_cache_url = f'http://{config.api_def.api_legacy_ip_address}:{config.api_def.api_port_number}/audiocache/{os.path.basename(output_file)}'
else:
output_file_url = f'/audio/{os.path.basename(output_file)}'
output_cache_url = f'/audiocache/{os.path.basename(output_file)}'
- if debug_tts:
- print(f"[{branding}Debug] Output file paths and URLs updated")
+
+ print_message("Output file paths and URLs updated", "debug_tts")
return output_file, output_file_url, output_cache_url
##############################
@@ -669,105 +843,146 @@ async def transcode_audio_if_necessary(output_file, model_audio_format, output_a
##############################
@app.get("/api/tts-generate-streaming", response_class=StreamingResponse)
async def apifunction_generate_streaming(text: str, voice: str, language: str, output_file: str):
- if model_engine.streaming_capable == False:
- print(f"[{branding}GEN] The selected TTS Engine does not support streaming. To use streaming, please select a TTS")
- print(f"[{branding}GEN] Engine that has streaming capability. You can find the streaming support information for")
- print(f"[{branding}GEN] each TTS Engine in the 'Engine Information' section of the Gradio interface.")
+ """Handle streaming TTS generation via GET request."""
+ debug_func_entry()
+
+ if not model_engine.streaming_capable:
+ print_message("The selected TTS Engine does not support streaming. To use streaming, please select a TTS", "warning", "GEN")
+ print_message("Engine that has streaming capability. You can find the streaming support information for", "warning", "GEN")
+ print_message("each TTS Engine in its 'Engine Information' section of the Gradio interface.", "warning", "GEN")
+
try:
- output_file_path = f'{this_dir / output_directory / output_file}.{model_engine.audio_format}'
- stream = await generate_audio(text, voice, language, model_engine.temperature_set, model_engine.repetitionpenalty_set, 1.0, 1.0, output_file_path, streaming=True)
+ output_file_path = f'{this_dir / config.get_output_directory() / output_file}.{model_engine.audio_format}'
+ print_message(f"Starting streaming TTS generation for: {text[:50]}{'...' if len(text) > 50 else ''}")
+ stream = await generate_audio(text, voice, language, model_engine.temperature_set,
+ model_engine.repetitionpenalty_set, 1.0, 1.0,
+ output_file_path, streaming=True)
return StreamingResponse(stream, media_type="audio/wav")
- except Exception as e:
- print(f"An error occurred: {e}")
- return JSONResponse(content={"error": "An error occurred"}, status_code=500)
+ except ValueError as e:
+ print_message(f"Value error occurred: {str(e)}", "error", "GEN")
+ return JSONResponse(content={"error": f"Invalid value: {str(e)}"}, status_code=400)
+ except KeyError as e:
+ print_message(f"Key error occurred: Missing key {str(e)}", "error", "GEN")
+ return JSONResponse(content={"error": f"Missing required field: {str(e)}"}, status_code=400)
+ except FileNotFoundError as e:
+ print_message(f"File not found: {str(e)}", "error", "GEN")
+ return JSONResponse(content={"error": f"File not found: {str(e)}"}, status_code=404)
+ except TypeError as e:
+ print_message(f"Type error occurred: {str(e)}", "error", "GEN")
+ return JSONResponse(content={"error": f"Type error: {str(e)}"}, status_code=400)
+ except RuntimeError as e:
+ print_message(f"Runtime error occurred: {str(e)}", "error", "GEN")
+ return JSONResponse(content={"error": "An internal runtime error occurred"}, status_code=500)
@app.post("/api/tts-generate-streaming", response_class=JSONResponse)
-async def tts_generate_streaming(request: Request, text: str = Form(...), voice: str = Form(...), language: str = Form(...), output_file: str = Form(...)):
- if model_engine.streaming_capable == False:
- print(f"[{branding}GEN] The selected TTS Engine does not support streaming. To use streaming, please select a TTS")
- print(f"[{branding}GEN] Engine that has streaming capability. You can find the streaming support information for")
- print(f"[{branding}GEN] each TTS Engine in the 'Engine Information' section of the Gradio interface.")
+async def tts_generate_streaming(_request: Request, text: str = Form(...), voice: str = Form(...),
+ language: str = Form(...), output_file: str = Form(...)):
+ """Handle streaming TTS generation via POST request."""
+ debug_func_entry()
+
+ if not model_engine.streaming_capable:
+ print_message("The selected TTS Engine does not support streaming. To use streaming, please select a TTS", "warning", "GEN")
+ print_message("Engine that has streaming capability. You can find the streaming support information for", "warning", "GEN")
+ print_message("each TTS Engine in its 'Engine Information' section of the Gradio interface.", "warning", "GEN")
+
try:
- output_file_path = f'{this_dir / output_directory / output_file}.{model_engine.audio_format}'
- await generate_audio(text, voice, language, model_engine.temperature_set, model_engine.repetitionpenalty_set, "1.0", "1.0", output_file_path, streaming=False)
+ output_file_path = f'{this_dir / config.get_output_directory() / output_file}.{model_engine.audio_format}'
+ print_message(f"Starting TTS generation for file: {os.path.basename(output_file)}")
+ await generate_audio(text, voice, language, model_engine.temperature_set,
+ model_engine.repetitionpenalty_set, "1.0", "1.0",
+ output_file_path, streaming=False)
return JSONResponse(content={"output_file_path": str(output_file)}, status_code=200)
- except Exception as e:
- print(f"An error occurred: {e}")
- return JSONResponse(content={"error": "An error occurred"}, status_code=500)
+ except ValueError as e:
+ print_message(f"Invalid streaming parameters: {str(e)}", "error", "GEN")
+ raise HTTPException(status_code=400, detail=str(e)) from e
+ except IOError as e:
+ print_message(f"IO error during streaming: {str(e)}", "error", "GEN")
+ raise HTTPException(status_code=500, detail="Audio streaming failed") from e
+ except RuntimeError as e:
+ print_message(f"Runtime error during streaming: {str(e)}", "error", "GEN")
+ raise HTTPException(status_code=500, detail="Audio generation failed") from e
+
###################################
# Central generate_audio function #
###################################
async def generate_audio(text, voice, language, temperature, repetition_penalty, speed, pitch, output_file, streaming=False):
- if model_engine.streaming_capable == False and streaming==True:
- print(f"[{branding}GEN] The selected TTS Engine does not support streaming. To use streaming, please select a TTS")
- print(f"[{branding}GEN] Engine that has streaming capability. You can find the streaming support information for")
- print(f"[{branding}GEN] each TTS Engine in the 'Engine Information' section of the Gradio interface.")
- # Get the async generator from the internal function
+ """Generate TTS audio with specified parameters. Supports streaming and non-streaming modes."""
+ debug_func_entry()
+
+ if not model_engine.streaming_capable and streaming:
+ print_message("The selected TTS Engine does not support streaming. To use streaming, please select a TTS", "warning", "GEN")
+ print_message("Engine that has streaming capability. You can find the streaming support information for", "warning", "GEN")
+ print_message("each TTS Engine in the 'Engine Information' section of the Gradio interface.", "warning", "GEN")
+ raise ValueError("Streaming not supported by current TTS engine")
+
response = model_engine.generate_tts(text, voice, language, temperature, repetition_penalty, speed, pitch, output_file, streaming)
- # If streaming, then return the generator as-is, otherwise just exhaust it and return
+
if streaming:
- # Return an async generator that yields from the response
async def stream_response():
- async for chunk in response:
- yield chunk
+ try:
+ async for chunk in response:
+ yield chunk
+ except Exception as e:
+ print_message(f"Error during streaming audio generation: {str(e)}", "error", "GEN")
+ raise
return stream_response()
- else:
- # Exhaust the generator and handle any errors
- try:
- async for _ in response:
- pass
- except Exception as e:
- print(f"{branding}[GEN] Error during audio generation: {str(e)}")
- raise
+ try:
+ async for _ in response:
+ pass
+ except Exception as e:
+ print_message(f"Error during audio generation: {str(e)}", "error", "GEN")
+ raise
###########################
#### PREVIEW VOICE API ####
###########################
@app.post("/api/previewvoice/", response_class=JSONResponse)
async def apifunction_preview_voice(
- request: Request,
+ _request: Request,
voice: str = Form(...),
rvccharacter_voice_gen: Optional[str] = Form(None),
rvccharacter_pitch: Optional[float] = Form(None)
):
+ """Generate preview audio for selected voice with optional RVC processing."""
+ debug_func_entry()
+
try:
- # Hardcoded settings
language = "en"
- output_file_name = "api_preview_voice"
- # Clean the voice filename for inclusion in the text
+ output_file_name = "api_preview_voice"
+
clean_voice_filename = re.sub(r'\.wav$', '', voice.replace(' ', '_'))
clean_voice_filename = re.sub(r'[^a-zA-Z0-9]', ' ', clean_voice_filename)
- # Generate the audio text
text = f"Hello, this is a preview of voice {clean_voice_filename}."
- # Set default values for new parameters if not provided
+
rvccharacter_voice_gen = rvccharacter_voice_gen or "Disabled"
rvccharacter_pitch = rvccharacter_pitch if rvccharacter_pitch is not None else 0
- # Generate the audio
- output_file_path = this_dir / output_directory / f'{output_file_name}.{model_engine.audio_format}'
+
+ print_message(f"Generating preview for voice: {clean_voice_filename}", "debug_tts", "GEN")
+ output_file_path = this_dir / config.get_output_directory() / f'{output_file_name}.{model_engine.audio_format}'
+
await generate_audio(
- text,
- voice,
- language,
- model_engine.temperature_set,
- model_engine.repetitionpenalty_set,
- 1.0,
- 0,
- output_file_path,
+ text, voice, language,
+ model_engine.temperature_set,
+ model_engine.repetitionpenalty_set,
+ 1.0, 0, output_file_path,
streaming=False
- )
- if rvc_settings["rvc_enabled"]:
+ )
+
+ if config.rvc_settings.rvc_enabled:
if rvccharacter_voice_gen.lower() in ["disabled", "disable"]:
- print(f"[{branding}Debug] PREVIEW VOICE - Pass rvccharacter_voice_gen") if debug_tts else None
- pass # Skip RVC processing for character part
+ print_message("def apifunction_preview_voice RVC processing skipped", "debug_tts")
else:
- print(f"[{branding}Debug] PREVIEW VOICE - send to rvc") if debug_tts else None
+ print_message(f"def apifunction_preview_voice processing with RVC: {rvccharacter_voice_gen}", "debug_tts")
rvccharacter_voice_gen = this_dir / "models" / "rvc_voices" / rvccharacter_voice_gen
- pth_path = rvccharacter_voice_gen if rvccharacter_voice_gen else rvc_settings["rvc_char_model_file"]
- run_rvc(output_file_path, pth_path, rvccharacter_pitch, infer_pipeline)
- # Generate the URL
+ pth_path = rvccharacter_voice_gen if rvccharacter_voice_gen else config.rvc_settings.rvc_char_model_file
+
+ if not os.path.exists(pth_path):
+ raise FileNotFoundError(f"RVC model not found: {pth_path}")
+
+ run_rvc(output_file_path, pth_path, rvccharacter_pitch, infer_pipeline)
+
output_file_url = f'/audio/{output_file_name}.{model_engine.audio_format}'
- # Return the response with both local file path and URL
return JSONResponse(
content={
"status": "generate-success",
@@ -776,32 +991,59 @@ async def apifunction_preview_voice(
},
status_code=200,
)
- except Exception as e:
- print(f"An error occurred: {e}")
- return JSONResponse(content={"error": "An error occurred"}, status_code=500)
+
+ except ValueError as e:
+ print_message(f"Value error occurred: {str(e)}", "error", "GEN")
+ return JSONResponse(content={"error": f"Invalid value: {str(e)}"}, status_code=400)
+ except KeyError as e:
+ print_message(f"Key error occurred: Missing key {str(e)}", "error", "GEN")
+ return JSONResponse(content={"error": f"Missing required field: {str(e)}"}, status_code=400)
+ except FileNotFoundError as e:
+ print_message(str(e), "error", "GEN")
+ return JSONResponse(content={"error": f"File not found: {str(e)}"}, status_code=404)
+ except TypeError as e:
+ print_message(f"Type error occurred: {str(e)}", "error", "GEN")
+ return JSONResponse(content={"error": f"Type error: {str(e)}"}, status_code=400)
+ except RuntimeError as e:
+ print_message(f"Runtime error occurred: {str(e)}", "error", "GEN")
+ return JSONResponse(content={"error": "An internal runtime error occurred"}, status_code=500)
+
##################################################################
# API Endpoint - OpenAI Speech API compatable endpoint Validator #
##################################################################
class OpenAIInput(BaseModel):
+ """Validates input parameters for OpenAI TTS generation."""
model: str = Field(..., description="The TTS model to use. Currently ignored.")
input: str = Field(..., max_length=4096, description="The text to generate audio for.")
voice: str = Field(..., description="The voice to use when generating the audio.")
response_format: str = Field(default="wav", description="The format of the audio. Currently only 'wav' is supported.")
speed: float = Field(default=1.0, ge=0.25, le=4.0, description="The speed of the generated audio.")
- @field_validator('voice')
+ @field_validator('voice', mode='before')
+ @classmethod
def validate_voice(cls, value):
+ """Validate that the requested voice is supported by OpenAI TTS."""
supported_voices = ["alloy", "echo", "fable", "nova", "onyx", "shimmer"]
if value not in supported_voices:
- raise ValueError(f'Voice must be one of {supported_voices}')
+ raise ValueError(f"Voice must be one of {supported_voices}")
return value
class OpenAIGenerator:
+ """
+ Validates the input parameters for an OpenAI TTS request.
+
+ This method checks the provided JSON data against the expected schema defined by the `OpenAIInput` model.
+ If validation succeeds, it returns `None`. If validation fails, it returns a string detailing the errors,
+ including the field name, validation error message, and field description.
+ """
@staticmethod
def validate_input(json_data: dict) -> Union[None, str]:
+ """Validate OpenAI TTS request parameters."""
+ debug_func_entry()
try:
OpenAIInput(**json_data)
+ print_message("OpenAI input validation successful", "debug_openai", "TTS")
return None
except ValidationError as e:
errors = []
@@ -810,30 +1052,44 @@ class OpenAIGenerator:
message = err['msg']
description = OpenAIInput.model_fields[field].field_info.description
errors.append(f"Error in field '{field}': {message}. Description: {description}")
- return ', '.join(errors)
+ error_msg = ', '.join(errors)
+ print_message(f"OpenAI input validation failed: {error_msg}", "error", "TTS")
+ return error_msg
#########################################################################
# API Endpoint - OpenAI Speech API compatable endpoint /v1/audio/speech #
#########################################################################
@app.post("/v1/audio/speech", response_class=JSONResponse)
async def openai_tts_generate(request: Request):
+ """Handle OpenAI-compatible TTS generation requests."""
+ debug_func_entry()
+
+ # Initialize response variables
+ response = None
+ status_code = 200
+
try:
json_data = await request.json()
- print(f"[{branding}Debug] Received JSON data: {json_data}") if debug_openai else None
+ print_message(f"Received JSON data: {json_data}", "debug_openai", "TTS")
+
+ # Validation check
validation_error = OpenAIGenerator.validate_input(json_data)
if validation_error:
- print(f"Validation error: {validation_error}") if debug_openai else None
- return JSONResponse(content={"error": validation_error}, status_code=400)
- model = json_data["model"] # Currently ignored
+ print_message(f"Validation error: {validation_error}", "debug_openai", "TTS")
+ raise ValueError(validation_error)
+
+ # Extract and validate parameters
input_text = json_data["input"]
voice = json_data["voice"]
response_format = json_data.get("response_format", "wav").lower()
speed = json_data.get("speed", 1.0)
- print(f"[{branding}Debug] Input text: {input_text}") if debug_openai else None
- print(f"[{branding}Debug] Voice: {voice}") if debug_openai else None
- print(f"[{branding}Debug] Speed: {speed}") if debug_openai else None
+
+ print_message(f"Input text: {input_text}", "debug_openai", "TTS")
+ print_message(f"Voice: {voice}", "debug_openai", "TTS")
+ print_message(f"Speed: {speed}", "debug_openai", "TTS")
+
+ # Process text and map voice
cleaned_string = html.unescape(standard_filtering(input_text))
- # Map the OpenAI voice to the corresponding internal voice
voice_mapping = {
"alloy": model_engine.openai_alloy,
"echo": model_engine.openai_echo,
@@ -842,104 +1098,139 @@ async def openai_tts_generate(request: Request):
"onyx": model_engine.openai_onyx,
"shimmer": model_engine.openai_shimmer
}
+
mapped_voice = voice_mapping.get(voice)
if not mapped_voice:
- print(f"Unsupported voice: {voice}")
- return JSONResponse(content={"error": "Unsupported voice"}, status_code=400)
- print(f"[{branding}Debug]Mapped voice: {mapped_voice}") if debug_openai else None
- # Generate the audio
- # Generate a unique filename
+ print_message(f"Unsupported voice: {voice}", "error", "TTS")
+ raise ValueError("Unsupported voice")
+
+ print_message(f"Mapped voice: {mapped_voice}", "debug_openai", "TTS")
+
+ # Generate audio
unique_id = uuid.uuid4()
timestamp = int(time.time())
- output_file_path = f'{this_dir / output_directory / f"openai_output_{unique_id}_{timestamp}.{model_engine.audio_format}"}'
+ output_file_path = f'{this_dir / config.get_output_directory() / f"openai_output_{unique_id}_{timestamp}.{model_engine.audio_format}"}'
+
+ if config.debugging.debug_fullttstext:
+ print_message(cleaned_string, component="TTS")
+ else:
+ print_message(f"{cleaned_string[:90]}{'...' if len(cleaned_string) > 90 else ''}", component="TTS")
+
await generate_audio(cleaned_string, mapped_voice, "en", model_engine.temperature_set,
- model_engine.repetitionpenalty_set, speed, model_engine.pitch_set,
- output_file_path, streaming=False)
- print(f"[{branding}Debug] Audio generated at: {output_file_path}") if debug_openai else None
- if rvc_settings["rvc_enabled"]:
- if rvc_settings["rvc_char_model_file"].lower() in ["disabled", "disable"]:
- print(f"[{branding}Debug] Pass rvccharacter_voice_gen") if debug_openai else None
- pass # Skip RVC processing for character part
+ model_engine.repetitionpenalty_set, speed, model_engine.pitch_set,
+ output_file_path, streaming=False)
+
+ print_message(f"Audio generated at: {output_file_path}", "debug_openai", "TTS")
+
+ # Handle RVC processing
+ if config.rvc_settings.rvc_enabled:
+ if config.rvc_settings.rvc_char_model_file.lower() in ["disabled", "disable"]:
+ print_message("Pass rvccharacter_voice_gen", "debug_openai", "TTS")
else:
- print(f"[{branding}Debug] send to rvc") if debug_openai else None
- pth_path = this_dir / "models" / "rvc_voices" / rvc_settings["rvc_char_model_file"]
- pitch = rvc_settings["pitch"]
+ print_message("send to rvc", "debug_openai", "TTS")
+ pth_path = this_dir / "models" / "rvc_voices" / config.rvc_settings.rvc_char_model_file
+ pitch = config.rvc_settings.pitch
run_rvc(output_file_path, pth_path, pitch, infer_pipeline)
- # Transcode the audio to the requested format
+
transcoded_file_path = await transcode_for_openai(output_file_path, response_format)
- print(f"[{branding}Debug] Audio transcoded to: {transcoded_file_path}") if debug_openai else None
- # Return the audio file
- return FileResponse(transcoded_file_path, media_type=f"audio/{response_format}", filename=f"output.{response_format}")
- except Exception as e:
- print(f"[{branding}Debug] An error occurred: {str(e)}") if debug_openai else None
- return JSONResponse(content={"error": "An error occurred"}, status_code=500)
+ print_message(f"Audio transcoded to: {transcoded_file_path}", "debug_openai", "TTS")
+
+ response = FileResponse(transcoded_file_path, media_type=f"audio/{response_format}",
+ filename=f"output.{response_format}")
+
+ except ValueError as e:
+ print_message(f"Value error occurred: {str(e)}", "error", "TTS")
+ response = JSONResponse(content={"error": f"Invalid value: {str(e)}"})
+ status_code = 400
+ except KeyError as e:
+ print_message(f"Key error occurred: Missing key {str(e)}", "error", "TTS")
+ response = JSONResponse(content={"error": f"Missing required field: {str(e)}"})
+ status_code = 400
+ except FileNotFoundError as e:
+ print_message(f"File not found error: {str(e)}", "error", "TTS")
+ response = JSONResponse(content={"error": "Required file not found"})
+ status_code = 404
+ except TypeError as e:
+ print_message(f"Type error occurred: {str(e)}", "error", "TTS")
+ response = JSONResponse(content={"error": f"Type error: {str(e)}"})
+ status_code = 400
+ except RuntimeError as e:
+ print_message(f"Runtime error occurred: {str(e)}", "error", "TTS")
+ response = JSONResponse(content={"error": "An internal runtime error occurred"})
+ status_code = 500
+
+ if status_code != 200:
+ return response.status_code(status_code)
+ return response
###########################################################################
# API Endpoint - OpenAI Speech API compatable endpoint Transcode Function #
###########################################################################
async def transcode_for_openai(input_file, output_format):
- print(f"[{branding}Debug] ************************************") if debug_openai else None
- print(f"[{branding}Debug] transcode_for_openai function called") if debug_openai else None
- print(f"[{branding}Debug] ************************************") if debug_openai else None
- print(f"[{branding}Debug] Input file : {input_file}") if debug_openai else None
- print(f"[{branding}Debug] Output format : {output_format}") if debug_openai else None
+ """Transcode audio files for OpenAI API compatibility. Handles additional formats like ogg and m4a."""
+ debug_func_entry()
+
+ print_message("************************************", "debug_openai", "TTS")
+ print_message("transcode_for_openai function called", "debug_openai", "TTS")
+ print_message("************************************", "debug_openai", "TTS")
+ print_message(f"Input file : {input_file}", "debug_openai", "TTS")
+ print_message(f"Output format : {output_format}", "debug_openai", "TTS")
+
if not ffmpeg_installed:
- print(f"[{branding}TTS] FFmpeg is not installed. Format conversion is not possible.")
- raise Exception("FFmpeg is not installed. Format conversion is not possible.")
- # Get the file extension of the input file
+ print_message("FFmpeg is not installed. Format conversion is not possible.", "error")
+ raise RuntimeError("FFmpeg is not installed. Format conversion is not possible.")
+
input_extension = os.path.splitext(input_file)[1][1:].lower()
- print(f"[{branding}Debug] Input file extension: {input_extension}") if debug_openai else None
- # Check if the input extension matches the requested output format
+ print_message(f"Input file extension: {input_extension}", "debug_openai", "TTS")
+
if input_extension == output_format.lower():
- print(f"[{branding}Debug] Input file is already in the requested format: {output_format}") if debug_openai else None
+ print_message(f"Input file is already in the requested format: {output_format}", "debug_openai", "TTS")
return input_file
+
output_file = os.path.splitext(input_file)[0] + f".{output_format}"
- print(f"[{branding}Debug] Output file: {output_file}") if debug_openai else None
- ffmpeg_path = "ffmpeg" # Default path for Linux and macOS
- if sys.platform == "win32":
- ffmpeg_path = os.path.join(this_dir, "system", "win_ffmpeg", "ffmpeg.exe")
- ffmpeg = (FFmpeg(ffmpeg_path).option("y").input(input_file).output(output_file))
- print(f"[{branding}Debug] Transcoding to {output_format}") if debug_openai else None
- if output_format == "opus":
- print(f"[{branding}Debug] Configuring Opus options") if debug_openai else None
- ffmpeg.output(output_file, {"codec:a": "libopus", "b:a": "128k", "vbr": "on", "compression_level": 10, "frame_duration": 60, "application": "voip"})
- elif output_format == "aac":
- print(f"[{branding}Debug] Configuring AAC options") if debug_openai else None
- ffmpeg.output(output_file, {"codec:a": "aac", "b:a": "192k"})
- elif output_format == "flac":
- print(f"[{branding}Debug] Configuring FLAC options") if debug_openai else None
- ffmpeg.output(output_file, {"codec:a": "flac", "compression_level": 8})
- elif output_format == "wav":
- print(f"[{branding}Debug] Configuring WAV options") if debug_openai else None
- ffmpeg.output(output_file, {"codec:a": "pcm_s16le"})
- elif output_format == "mp3":
- print(f"[{branding}Debug] Configuring MP3 options") if debug_openai else None
- ffmpeg.output(output_file, {"codec:a": "libmp3lame", "b:a": "192k"})
- elif output_format in ["ogg", "m4a"]:
- if output_format == "ogg":
- print(f"[{branding}Debug] Configuring OGG options") if debug_openai else None
- ffmpeg.output(output_file, {"codec:a": "libvorbis"})
- elif output_format == "m4a":
- print(f"[{branding}Debug] Configuring M4A options") if debug_openai else None
- ffmpeg.output(output_file, {"codec:a": "aac", "b:a": "192k"})
+ print_message(f"Output file: {output_file}", "debug_openai", "TTS")
+
+ ffmpeg_path = os.path.join(this_dir, "system", "win_ffmpeg", "ffmpeg.exe") if sys.platform == "win32" else "ffmpeg"
+ ffmpeg = FFmpeg(ffmpeg_path).option("y").input(input_file).output(output_file)
+
+ print_message(f"Transcoding to {output_format}", "debug_openai", "TTS")
+
+ codec_settings = {
+ "opus": {"codec:a": "libopus", "b:a": "128k", "vbr": "on", "compression_level": 10,
+ "frame_duration": 60, "application": "voip"},
+ "aac": {"codec:a": "aac", "b:a": "192k"},
+ "flac": {"codec:a": "flac", "compression_level": 8},
+ "wav": {"codec:a": "pcm_s16le"},
+ "mp3": {"codec:a": "libmp3lame", "b:a": "192k"},
+ "ogg": {"codec:a": "libvorbis"},
+ "m4a": {"codec:a": "aac", "b:a": "192k"}
+ }
+
+ if output_format in codec_settings:
+ print_message(f"Configuring {output_format.upper()} options", "debug_openai", "TTS")
+ ffmpeg.output(output_file, codec_settings[output_format])
else:
- print(f"[{branding}TTS] Unsupported output format: {output_format}")
+ print_message(f"Unsupported output format: {output_format}", "error", "TTS")
raise ValueError(f"Unsupported output format: {output_format}")
+
try:
- print(f"[{branding}Debug] Starting transcoding process") if debug_openai else None
+ print_message("Starting transcoding process", "debug_openai", "TTS")
await ffmpeg.execute()
- print(f"[{branding}Debug] Transcoding completed successfully") if debug_openai else None
+ print_message("Transcoding completed successfully", "debug_openai", "TTS")
except Exception as e:
- print(f"[{branding}TTS] Error occurred during transcoding: {str(e)}")
+ print_message(f"Error occurred during transcoding: {str(e)}", "error", "TTS")
raise
- print(f"[{branding}Debug] Transcoding process completed") if debug_openai else None
- print(f"[{branding}Debug] Transcoded file: {output_file}") if debug_openai else None
+
+ print_message("Transcoding process completed", "debug_openai", "TTS")
+ print_message(f"Transcoded file: {output_file}", "debug_openai", "TTS")
return output_file
+
######################################################################################
# API Endpoint - OpenAI Speech API compatable endpoint change engine voices Function #
######################################################################################
class VoiceMappings(BaseModel):
+ """OpenAI to engine voice mapping configuration."""
alloy: str
echo: str
fable: str
@@ -949,272 +1240,363 @@ class VoiceMappings(BaseModel):
@app.put("/api/openai-voicemap")
async def update_openai_voice_mappings(mappings: VoiceMappings):
- # Update in-memory versions (wont display in Gradio without a restart as gradio caches)
- model_engine.openai_alloy = mappings.alloy
- model_engine.openai_echo = mappings.echo
- model_engine.openai_fable = mappings.fable
- model_engine.openai_nova = mappings.nova
- model_engine.openai_onyx = mappings.onyx
- model_engine.openai_shimmer = mappings.shimmer
- # Update model_settings.json file for the currently loaded TTS engine
+ """Update OpenAI voice mappings in memory and settings file."""
+ debug_func_entry()
+
try:
+ # Update in-memory mappings
+ print_message("Updating in-memory voice mappings", "debug_openai", "TTS")
+ model_engine.openai_alloy = mappings.alloy
+ model_engine.openai_echo = mappings.echo
+ model_engine.openai_fable = mappings.fable
+ model_engine.openai_nova = mappings.nova
+ model_engine.openai_onyx = mappings.onyx
+ model_engine.openai_shimmer = mappings.shimmer
+
+ # Update settings file
settings_file = this_dir / "system" / "tts_engines" / model_engine.engine_loaded / "model_settings.json"
- with open(settings_file, "r+") as f:
- settings = json.load(f)
+ with open(settings_file, "r+", encoding="utf-8") as model_file:
+ settings = json.load(model_file)
settings["openai_voices"] = mappings.dict()
- f.seek(0)
- json.dump(settings, f, indent=4)
- f.truncate()
+ model_file.seek(0)
+ json.dump(settings, model_file, indent=4)
+ model_file.truncate()
+
+ print_message("Voice mappings updated successfully", "debug_openai", "TTS")
+ return {"message": "OpenAI voice mappings updated successfully"}
+
except Exception as e:
- raise HTTPException(status_code=500, detail=f"Failed to update model settings file: {str(e)}")
- return {"message": "OpenAI voice mappings updated successfully"}
+ print_message(f"Failed to update voice mappings: {str(e)}", "error", "TTS")
+ raise HTTPException(status_code=500, detail=f"Failed to update model settings file: {str(e)}") from e
#######################
# Play at the console #
#######################
def play_audio(file_path, volume):
- print(f"[{branding}GEN] \033[94m File path is: {file_path}") if debug_tts else None
- print(f"[{branding}GEN] \033[94m Volume is: {volume}") if debug_tts else None
- normalized_file_path = os.path.normpath(file_path)
- print(f"[{branding}GEN] \033[94m Normalized file path is: {normalized_file_path}") if debug_tts else None
- directory = os.path.dirname(normalized_file_path)
- if os.path.isdir(directory):
- print(f"[{branding}GEN] \033[94m Directory contents: {os.listdir(directory)}") if debug_tts else None
- else:
- print(f"[{branding}GEN] \033[94mError: Directory does not exist: {directory}") if debug_tts else None
- if not os.path.isfile(normalized_file_path):
- print(f"[{branding}GEN] \033[94mError: File does not exist: {normalized_file_path}\033[0m") if debug_tts else None
- return
- # Check for AAC format
- if normalized_file_path.lower().endswith('.aac'):
- print(f"[{branding}GEN] \033[94mPlay Audio : \033[0mAAC format files cannot be played at the console. Please choose another format")
- return
+ """Play audio file with specified volume through system speakers at the console/terminal window"""
+ debug_func_entry()
+
try:
- print(f"[{branding}GEN] \033[94mPlay Audio : \033[0mPlaying audio at console")
+ print_message(f"\033[94mFile path is: {file_path}", "debug_tts", "GEN")
+ print_message(f"\033[94mVolume is: {volume}", "debug_tts", "GEN")
+
+ normalized_file_path = os.path.normpath(file_path)
+ print_message(f"\033[94mNormalized file path is: {normalized_file_path}", "debug_tts", "GEN")
+
+ directory = os.path.dirname(normalized_file_path)
+ if not os.path.isdir(directory):
+ print_message(f"\033[94mError: Directory does not exist: {directory}", "error", "GEN")
+ return
+
+ print_message(f"\033[94mDirectory contents: {os.listdir(directory)}", "debug_tts", "GEN")
+
+ if not os.path.isfile(normalized_file_path):
+ print_message(f"\033[94mError: File does not exist: {normalized_file_path}\033[0m", "error", "GEN")
+ return
+
+ if normalized_file_path.lower().endswith('.aac'):
+ print_message("\033[94mPlay Audio : \033[0mAAC format files cannot be played at the console. Please choose another format", "error", "GEN")
+ return
+
+ print_message("\033[94mPlay Audio : \033[0mPlaying audio at console", "debug_tts", "GEN")
data, fs = sf.read(normalized_file_path)
sd.play(volume * data, fs)
sd.wait()
- except Exception as e:
- print(f"[{branding}GEN] \033[94mError playing audio file: {e}\033[0m")
-
-class Request(BaseModel):
- # Define the structure of the 'Request' class if needed
- pass
+ except FileNotFoundError as e:
+ print_message(f"\033[94mAudio file not found: {e}\033[0m", "error", "GEN")
+ except IOError as e:
+ print_message(f"\033[94mError reading audio file: {e}\033[0m", "error", "GEN")
+ except sd.PortAudioError as e:
+ print_message(f"\033[94mAudio playback error: {e}\033[0m", "error", "GEN")
class TTSGenerator:
+ """
+ Validate JSON input parameters for TTS generation.
+ """
@staticmethod
- def validate_json_input(json_data: Union[Dict, str]) -> Union[None, str]:
+ def validate_json_input(json_input_data):
+ """
+ Validate JSON input against JSONInput model schema.
+ """
+ debug_func_entry()
try:
- if isinstance(json_data, str):
- json_data = json.loads(json_data)
- JSONInput(**json_data)
- return None # JSON is valid
+ JSONInput(**json_input_data)
+ return None
except ValidationError as e:
- return str(e)
+ error_messages = []
+ for error in e.errors():
+ field = error["loc"][0]
+ description = JSONInput.model_fields[field].description # Updated for Pydantic v2.x
+ error_messages.append(f"{field}: {description}")
+ error_message = "\n".join(error_messages)
+ print_message(f"Error with API request: {error_message}", "error", "API")
+ return error_message
#################################################################
# /api/tts-generate Generation API Endpoint Narration Filtering #
#################################################################
def process_text(text):
- # Normalize HTML encoded quotes
+ """Parse text into narrator, character, and ambiguous speech segments. Handles mixed formatting and nested quotes."""
+ debug_func_entry()
+
+ # Normalize and clean text
text = html.unescape(text)
- # Replace ellipsis with a single dot
text = re.sub(r'\.{3,}', '.', text)
- # Pattern to identify combined narrator and character speech
+
+ # Pattern for identifying speech segments
combined_pattern = r'(\*[^*"]+\*|"[^"*]+")'
- # List to hold parts of speech along with their type
ordered_parts = []
- # Track the start of the next segment
start = 0
- # Find all matches
+
+ # Process text segments
for match in re.finditer(combined_pattern, text):
- # Add the text before the match, if any, as ambiguous
+ # Handle pre-match ambiguous text
if start < match.start():
ambiguous_text = text[start:match.start()].strip()
if ambiguous_text:
ordered_parts.append(('ambiguous', ambiguous_text))
- # Add the matched part as either narrator or character
+ print_message(f"Added ambiguous segment: {ambiguous_text}", "debug_tts", "GEN")
+
+ # Process matched segment
matched_text = match.group(0)
if matched_text.startswith('*') and matched_text.endswith('*'):
- ordered_parts.append(('narrator', matched_text.strip('*').strip()))
+ text_part = matched_text.strip('*').strip()
+ ordered_parts.append(('narrator', text_part))
+ print_message(f"Added narrator segment: {text_part}", "debug_tts", "GEN")
elif matched_text.startswith('"') and matched_text.endswith('"'):
- ordered_parts.append(('character', matched_text.strip('"').strip()))
+ text_part = matched_text.strip('"').strip()
+ ordered_parts.append(('character', text_part))
+ print_message(f"Added character segment: {text_part}", "debug_tts", "GEN")
else:
- # In case of mixed or improperly formatted parts
+ text_part = matched_text.strip('*').strip('"')
if '*' in matched_text:
- ordered_parts.append(('narrator', matched_text.strip('*').strip('"')))
+ ordered_parts.append(('narrator', text_part))
+ print_message(f"Added mixed narrator segment: {text_part}", "debug_tts", "GEN")
else:
- ordered_parts.append(('character', matched_text.strip('"').strip('*')))
- # Update the start of the next segment
+ ordered_parts.append(('character', text_part))
+ print_message(f"Added mixed character segment: {text_part}", "debug_tts", "GEN")
+
start = match.end()
- # Add any remaining text after the last match as ambiguous
+
+ # Handle remaining text
if start < len(text):
ambiguous_text = text[start:].strip()
if ambiguous_text:
ordered_parts.append(('ambiguous', ambiguous_text))
+ print_message(f"Added final ambiguous segment: {ambiguous_text}", "debug_tts", "GEN")
+
return ordered_parts
def standard_filtering(text_input):
+ """Remove special characters and normalize text formatting."""
+ debug_func_entry()
+
text_output = (text_input
- .replace("***", "")
- .replace("**", "")
- .replace("*", "")
- .replace("\n\n", "\n")
- .replace("'", "'")
- )
+ .replace("***", "")
+ .replace("**", "")
+ .replace("*", "")
+ .replace("\n\n", "\n")
+ .replace("'", "'")
+ )
+
+ print_message(f"Filtered text: {text_output[:50]}{'...' if len(text_output) > 50 else ''}",
+ "debug_tts", "GEN")
return text_output
#################################################################
# /api/tts-generate Generation API Endpoint Narration Combining #
#################################################################
-def combine(output_file_timestamp, output_file_name, audio_files, target_sample_rate=44100):
- audio = np.array([])
+def combine(output_file_timestamp, output_file_name, audio_files, target_sample_rate=44100, delete_originals=True):
+ """
+ Combine multiple audio files into one, with optional resampling and timestamping.
+
+ Args:
+ output_file_timestamp (bool): Whether to add timestamp to output filename
+ output_file_name (str): Base name for output file
+ audio_files (list): List of audio files to combine
+ target_sample_rate (int, optional): Target sample rate for output. Defaults to 44100.
+ delete_originals (bool, optional): Whether to delete original files after combining. Defaults to False.
+ """
+ debug_func_entry()
+
+ audio = np.array([])
+ processed_files = [] # Keep track of successfully processed files
+
try:
for audio_file in audio_files:
normalized_audio_file = os.path.normpath(audio_file)
- print(f"[{branding}Debug] Processing file: {normalized_audio_file}") if debug_concat else None
- # Check if the file exists
+ print_message(f"Processing file: {normalized_audio_file}", "debug_concat", "TTS")
+
if not os.path.isfile(normalized_audio_file):
- print(f"[{branding}Debug] Error: File does not exist: {normalized_audio_file}") if debug_concat else None
- return None, None
- # Read the audio file
+ print_message(f"Error: File does not exist: {normalized_audio_file}", "error", "TTS")
+ return None, None, None
+
audio_data, current_sample_rate = sf.read(normalized_audio_file)
- print(f"[{branding}Debug] Read file: {normalized_audio_file}, Sample rate: {current_sample_rate}, Data shape: {audio_data.shape}") if debug_concat else None
- # Resample if necessary
+ print_message(f"Read file: {normalized_audio_file}, Sample rate: {current_sample_rate}, Data shape: {audio_data.shape}",
+ "debug_concat", "TTS")
+
if current_sample_rate != target_sample_rate:
- print(f"[{branding}Debug] Resampling file from {current_sample_rate} to {target_sample_rate} Hz") if debug_concat else None
+ print_message(f"Resampling file from {current_sample_rate} to {target_sample_rate} Hz", "debug_concat", "TTS")
audio_data = librosa.resample(audio_data, orig_sr=current_sample_rate, target_sr=target_sample_rate)
- # Concatenate audio data
- if audio.size == 0:
- audio = audio_data
- else:
- audio = np.concatenate((audio, audio_data))
+
+ audio = audio_data if audio.size == 0 else np.concatenate((audio, audio_data))
+ processed_files.append(normalized_audio_file) # Add to processed files list
+
+ # Prepare output paths
if output_file_timestamp:
timestamp = int(time.time())
- output_file_path = os.path.join(this_dir / "outputs" / f'{output_file_name}_{timestamp}_combined.wav')
- sf.write(output_file_path, audio, target_sample_rate)
- # Legacy API or New API return
- if params["api_def"]["api_use_legacy_api"]:
- output_file_url = f"http://{api_defaults['api_legacy_ip_address']}:{params['api_def']['api_port_number']}/audio/{output_file_name}_{timestamp}_combined.wav"
- output_cache_url = f"http://{api_defaults['api_legacy_ip_address']}:{params['api_def']['api_port_number']}/audiocache/{output_file_name}_{timestamp}_combined.wav"
- else:
- output_file_url = f'/audio/{output_file_name}_{timestamp}_combined.wav'
- output_cache_url = f'/audiocache/{output_file_name}_{timestamp}_combined.wav'
+ filename = f'{output_file_name}_{timestamp}_combined.wav'
else:
- output_file_path = os.path.join(this_dir / "outputs" / f'{output_file_name}_combined.wav')
- sf.write(output_file_path, audio, target_sample_rate)
- # Legacy API or New API return
- if params["api_def"]["api_use_legacy_api"]:
- output_file_url = f"http://{api_defaults['api_legacy_ip_address']}:{api_defaults['api_port_number']}/audio/{output_file_name}_combined.wav"
- output_cache_url = f"http://{api_defaults['api_legacy_ip_address']}:{api_defaults['api_port_number']}/audiocache/{output_file_name}_combined.wav"
- else:
- output_file_url = f'/audio/{output_file_name}_combined.wav'
- output_cache_url = f'/audiocache/{output_file_name}_combined.wav'
- print(f"[{branding}Debug] Output file changed to:", output_file_path) if debug_concat else None
- print(f"[{branding}Debug] Output file changed to:", output_file_url) if debug_concat else None
- print(f"[{branding}Debug] Output file changed to:", output_cache_url) if debug_concat else None
- except Exception as e:
- print(f"Error occurred: {e}")
- return output_file_path, output_file_url, output_cache_url
+ filename = f'{output_file_name}_combined.wav'
+
+ output_file_path = os.path.join(this_dir / "outputs" / filename)
+ sf.write(output_file_path, audio, target_sample_rate)
+
+ # Generate URLs based on API configuration
+ base_url = f"http://{config.api_def.api_legacy_ip_address}:{config.api_def.api_port_number}" if config.api_def.api_use_legacy_api else ""
+ output_file_url = f"{base_url}/audio/{filename}"
+ output_cache_url = f"{base_url}/audiocache/{filename}"
+
+ print_message(f"Output file path: {output_file_path}", "debug_concat", "TTS")
+ print_message(f"Output file URL: {output_file_url}", "debug_concat", "TTS")
+ print_message(f"Output cache URL: {output_cache_url}", "debug_concat", "TTS")
+
+ # Delete original files if requested and combination was successful
+ if delete_originals:
+ for file_path in processed_files:
+ try:
+ os.remove(file_path)
+ print_message(f"Deleted original file: {file_path}", "debug_concat", "TTS")
+ except OSError as e:
+ print_message(f"Error deleting file {file_path}: {str(e)}", "error", "TTS")
+
+ return output_file_path, output_file_url, output_cache_url
+
+ except (OSError, IOError) as e:
+ print_message(f"File system error while processing audio: {str(e)}", "error", "TTS")
+ except sf.SoundFileError as e:
+ print_message(f"Error reading audio file: {str(e)}", "error", "TTS")
+ except ValueError as e:
+ print_message(f"Invalid audio data or sample rate: {str(e)}", "error", "TTS")
+ except MemoryError as e:
+ print_message(f"Out of memory while processing audio: {str(e)}", "error", "TTS")
+ except librosa.ParameterError as e:
+ print_message(f"Error during audio resampling: {str(e)}", "error", "TTS")
+ except Exception as e: # pylint: disable=broad-exception-caught
+ print_message(f"An unexpected error occurred: {str(e)}", "error", "TTS")
+ return None, None, None
##################################
# Central RVC Generation request #
##################################
-def run_rvc(input_tts_path, pth_path, pitch, infer_pipeline):
+def run_rvc(input_tts_path, pth_path, pitch, inference_pipeline):
+ """Run RVC (Retrieval-based Voice Conversion) processing on input audio."""
+ debug_func_entry()
+
generate_start_time = time.time()
- f0up_key = pitch
- filter_radius = rvc_settings["filter_radius"]
- index_rate = rvc_settings["index_rate"]
- rms_mix_rate = rvc_settings["rms_mix_rate"]
- protect = rvc_settings["protect"]
- hop_length = rvc_settings["hop_length"]
- f0method = rvc_settings["f0method"]
- split_audio = rvc_settings["split_audio"]
- f0autotune = rvc_settings["autotune"]
- embedder_model = rvc_settings["embedder_model"]
- training_data_size = rvc_settings["training_data_size"]
- # Convert path variables to strings
+
+ # Config settings
+ settings = {
+ "f0up_key": pitch,
+ "filter_radius": config.rvc_settings.filter_radius,
+ "index_rate": config.rvc_settings.index_rate,
+ "rms_mix_rate": config.rvc_settings.rms_mix_rate,
+ "protect": config.rvc_settings.protect,
+ "hop_length": config.rvc_settings.hop_length,
+ "f0method": config.rvc_settings.f0method,
+ "split_audio": config.rvc_settings.split_audio,
+ "f0autotune": config.rvc_settings.autotune,
+ "embedder_model": config.rvc_settings.embedder_model,
+ "training_data_size": config.rvc_settings.training_data_size,
+ }
+
input_tts_path = str(input_tts_path)
pth_path = str(pth_path)
- # Check if the model file exists
+ output_rvc_path = input_tts_path
+
if not os.path.isfile(pth_path):
- print(f"Model file {pth_path} does not exist. Exiting.")
- return
- # Get the directory of the model file
+ print_message(f"Model file {pth_path} does not exist. Exiting.", "error", "GEN")
+ return
+
model_dir = os.path.dirname(pth_path)
- # Get the filename of pth_path
pth_filename = os.path.basename(pth_path)
- # Find all .index files in the model directory
index_files = [file for file in os.listdir(model_dir) if file.endswith(".index")]
+
if len(index_files) == 1:
index_path = str(os.path.join(model_dir, index_files[0]))
- # Get the filename of index_path
- index_filename = os.path.basename(index_path)
- index_filename_print = index_filename
- index_size_print = training_data_size
- elif len(index_files) > 1:
- print(f"[{branding}GEN] \033[94mRVC Convert :\033[0m Multiple RVC index files found in the models folder where \033[93m{pth_filename}\033[0m is")
- print(f"[{branding}GEN] \033[94mRVC Convert :\033[0m located. Unable to determine which index to use. Continuing without an index file.")
- index_path = ""
- index_filename = None
- index_filename_print = "None used"
- index_size_print = "N/A"
+ index_filename_print = os.path.basename(index_path)
+ index_size_print = settings["training_data_size"]
else:
+ if len(index_files) > 1:
+ print_message(f"RVC Convert: Multiple RVC index files found in the models folder where {pth_filename} is", "warning", "GEN")
+ print_message("RVC Convert: located. Unable to determine which index to use. Continuing without an index file.", "warning", "GEN")
index_path = ""
- index_filename = None
index_filename_print = "None used"
index_size_print = "N/A"
- # Set the output path to be the same as the input path
- output_rvc_path = input_tts_path
- if debug_rvc:
- debug_lines = [
- f"[{branding}Debug] ************************************",
- f"[{branding}Debug] run_rvc function called (debug_rvc)",
- f"[{branding}Debug] ***********************************",
- f"[{branding}Debug] f0up_key : {f0up_key}",
- f"[{branding}Debug] filter_radius : {filter_radius}",
- f"[{branding}Debug] index_rate : {index_rate}",
- f"[{branding}Debug] rms_mix_rate : {rms_mix_rate}",
- f"[{branding}Debug] protect : {protect}",
- f"[{branding}Debug] hop_length : {hop_length}",
- f"[{branding}Debug] f0method : {f0method}",
- f"[{branding}Debug] input_tts_path : {input_tts_path}",
- f"[{branding}Debug] output_rvc_path : {output_rvc_path}",
- f"[{branding}Debug] pth_path : {pth_path}",
- f"[{branding}Debug] index_path : {index_path}",
- f"[{branding}Debug] split_audio : {split_audio}",
- f"[{branding}Debug] f0autotune : {f0autotune}",
- f"[{branding}Debug] embedder_model : {embedder_model}",
- f"[{branding}Debug] training_data_size: {training_data_size}"
- ]
- debug_info = "\n".join(debug_lines)
- print(debug_info)
- # Call the infer_pipeline function
- infer_pipeline(f0up_key, filter_radius, index_rate, rms_mix_rate, protect, hop_length, f0method,
- input_tts_path, output_rvc_path, pth_path, index_path, split_audio, f0autotune, embedder_model, training_data_size, debug_rvc)
- generate_end_time = time.time()
- generate_elapsed_time = generate_end_time - generate_start_time
- print(f"[{branding}GEN] \033[94mRVC Convert : \033[94mModel: \033[93m{pth_filename} \033[94mIndex: \033[93m{index_filename_print}\033[0m")
- print(f"[{branding}GEN] \033[94mRVC Convert : \033[93m{generate_elapsed_time:.2f} seconds. \033[94mMethod: \033[93m{f0method} \033[94mIndex size used \033[93m{index_size_print}\033[0m")
+
+ if config.debugging.debug_rvc:
+ print_message("RVC Pipeline Configuration:", "debug_rvc", "GEN")
+ for key, value in settings.items():
+ print_message(f"{key}: {value}", "debug_rvc", "GEN")
+ print_message(f"input_tts_path: {input_tts_path}", "debug_rvc", "GEN")
+ print_message(f"output_rvc_path: {output_rvc_path}", "debug_rvc", "GEN")
+ print_message(f"pth_path: {pth_path}", "debug_rvc", "GEN")
+ print_message(f"index_path: {index_path}", "debug_rvc", "GEN")
+
+ inference_pipeline(settings["f0up_key"], settings["filter_radius"], settings["index_rate"],
+ settings["rms_mix_rate"], settings["protect"], settings["hop_length"],
+ settings["f0method"], input_tts_path, output_rvc_path, pth_path, index_path,
+ settings["split_audio"], settings["f0autotune"], settings["embedder_model"],
+ settings["training_data_size"], config.debugging.debug_rvc)
+
+ generate_elapsed_time = time.time() - generate_start_time
+ print_message(f"RVC Convert: Model: {pth_filename} Index: {index_filename_print}", "debug_rvc", "GEN")
+ print_message(f"RVC Convert: {generate_elapsed_time:.2f} seconds. Method: {settings['f0method']} Index size used {index_size_print}",
+ "debug_rvc", "GEN")
return
####################################################################
# /api/tts-generate Generation API Endpoint Narration RVC handling #
####################################################################
-def process_rvc_narrator(part_type, voice_gen, pitch, default_model_file, output_file, infer_pipeline):
+def process_rvc_narrator(part_type, voice_gen, pitch, default_model_file, output_file, narr_infer_pipeline):
+ """Process RVC voice conversion for narrator segments."""
+ debug_func_entry()
+
if voice_gen.lower() in ["disabled", "disable"]:
+ print_message(f"RVC processing skipped for {part_type} segment - voice disabled", "debug_rvc", "GEN")
return
- else:
+ try:
voice_gen_path = this_dir / "models" / "rvc_voices" / voice_gen
pth_path = voice_gen_path if voice_gen else default_model_file
- run_rvc(output_file, pth_path, pitch, infer_pipeline)
+
+ if not os.path.exists(pth_path):
+ print_message(f"RVC model not found: {pth_path}", "error", "GEN")
+ return
+
+ print_message(f"Processing {part_type} segment with RVC model: {os.path.basename(pth_path)}", "debug_rvc", "GEN")
+ run_rvc(output_file, pth_path, pitch, narr_infer_pipeline)
+ except FileNotFoundError as e:
+ print_message(f"File not found during RVC processing for {part_type} segment: {str(e)}", "error", "GEN")
+ except TypeError as e:
+ print_message(f"Type error during RVC processing for {part_type} segment: {str(e)}", "error", "GEN")
+ except ValueError as e:
+ print_message(f"Value error during RVC processing for {part_type} segment: {str(e)}", "error", "GEN")
+ except OSError as e:
+ print_message(f"OS error during RVC processing for {part_type} segment: {str(e)}", "error", "GEN")
#############################################################
# /api/tts-generate Generation API Endpoint JSON validation #
#############################################################
class JSONInput(BaseModel):
- text_input: str = Field(..., max_length=int(api_defaults["api_max_characters"]), description=f"text_input needs to be {api_defaults['api_max_characters']} characters or less.")
+ """
+ Represents the schema for validating input parameters for JSON-based TTS generation.
+ This class defines the expected structure and constraints for all input fields, such as `text_input`,
+ `language`, `output_file_name`, and TTS-related settings like `autoplay_volume`, `pitch`, and `temperature`.
+ Validation is performed using Pydantic's `Field` definitions with patterns, ranges, and descriptions.
+ Custom validation methods ensure specific fields like RVC voice files and pitch values meet the required formats.
+ """
+ text_input: str = Field(..., max_length=int(config.api_def.api_max_characters), description=f"text_input needs to be {config.api_def.api_max_characters} characters or less.")
text_filtering: str = Field(..., pattern="^(none|standard|html)$", description="text_filtering needs to be 'none', 'standard' or 'html'.")
#character_voice_gen: str = Field(..., pattern=r'^[\(\)a-zA-Z0-9\_\-./\s]+$', description="character_voice_gen needs to be the name of a valid voice for the loaded TTS engine.")
rvccharacter_voice_gen: str = Field(..., description="rvccharacter_voice_gen needs to be the name of a valid pth file in the 'folder\\file.pth' format or the word 'Disabled'.")
@@ -1222,7 +1604,7 @@ class JSONInput(BaseModel):
narrator_enabled: str = Field(..., pattern="^(true|false|silent)$", description="narrator_enabled needs to be 'true', 'false' or 'silent'.")
narrator_voice_gen: str = Field(..., pattern=r'^[\(\)a-zA-Z0-9\_\-./\s]+$', description="character_voice_gen needs to be the name of a valid voice for the loaded TTS engine.")
rvcnarrator_voice_gen: str = Field(..., description="rvcnarrator_voice_gen needs to be the name of a valid pth file in the 'folder\\file.pth' format or the word 'Disabled'.")
- rvcnarrator_pitch: float = Field(..., description="RVC Narrator pitch needs to be a number between -24 and 24")
+ rvcnarrator_pitch: float = Field(..., description="RVC Narrator pitch needs to be a number between -24 and 24")
text_not_inside: str = Field(..., pattern="^(character|narrator|silent)$", description="text_not_inside needs to be 'character', 'narrator' or 'silent'.")
language: str = Field(..., pattern="^(ar|zh-cn|zh|cs|nl|en|fr|de|hu|hi|it|ja|ko|pl|pt|ru|es|tr)$", description="language needs to be one of the following: ar, zh-cn, zh, cs, nl, en, fr, de, hu, hi, it, ja, ko, pl, pt, ru, es, tr.")
output_file_name: str = Field(..., pattern="^[a-zA-Z0-9_]+$", description="output_file_name needs to be the name without any special characters or file extension, e.g., 'filename'.")
@@ -1236,93 +1618,487 @@ class JSONInput(BaseModel):
@classmethod
def validate_autoplay_volume(cls, value):
- if not (0.1 <= value <= 1.0):
- raise ValueError("Autoplay volume must be between 0.1 and 1.0")
- return value
+ """Validate autoplay volume is within acceptable range (0.1-1.0)."""
+ debug_func_entry()
+ try:
+ if not 0.1 <= value <= 1.0:
+ print_message(f"Invalid autoplay volume: {value}", "error", "GEN")
+ raise ValueError("Autoplay volume must be between 0.1 and 1.0")
+ print_message(f"Autoplay volume validated: {value}", "debug_tts", "GEN")
+ return value
+ except Exception as e:
+ print_message(f"Error validating autoplay volume: {str(e)}", "error", "GEN")
+ raise
+ @classmethod
def validate_rvccharacter_voice_gen(cls, v):
- if v.lower() == "disabled":
+ """Validate RVC character voice file format (folder/file.pth or 'Disabled')."""
+ debug_func_entry()
+ try:
+ if v.lower() == "disabled":
+ print_message("RVC character voice disabled", "debug_rvc", "GEN")
+ return v
+ pattern = re.compile(r'^.*\.(pth)$')
+ if not pattern.match(v):
+ print_message(f"Invalid RVC character voice format: {v}", "error", "GEN")
+ raise ValueError("rvccharacter_voice_gen needs to be the name of a valid pth file in the 'folder\\file.pth' format or 'Disabled'.")
+ print_message(f"RVC character voice validated: {v}", "debug_rvc", "GEN")
return v
- pattern = re.compile(r'^.*\.(pth)$')
- if not pattern.match(v):
- raise ValueError("rvccharacter_voice_gen needs to be the name of a valid pth file in the 'folder\\file.pth' format or 'Disabled'.")
- return v
+ except Exception as e:
+ print_message(f"Error validating RVC character voice: {str(e)}", "error", "GEN")
+ raise
+ @classmethod
def validate_rvnarrator_voice_gen(cls, v):
- if v.lower() == "disabled":
+ """Validate RVC narrator voice file format (folder/file.pth or 'Disabled')."""
+ debug_func_entry()
+ try:
+ if v.lower() == "disabled":
+ print_message("RVC narrator voice disabled", "debug_rvc", "GEN")
+ return v
+ pattern = re.compile(r'^.*\.(pth)$')
+ if not pattern.match(v):
+ print_message(f"Invalid RVC narrator voice format: {v}", "error", "GEN")
+ raise ValueError("rvcnarrator_voice_gen needs to be the name of a valid pth file in the 'folder\\file.pth' format or 'Disabled'.")
+ print_message(f"RVC narrator voice validated: {v}", "debug_rvc", "GEN")
return v
- pattern = re.compile(r'^.*\.(pth)$')
- if not pattern.match(v):
- raise ValueError("rvcnarrator_voice_gen needs to be the name of a valid pth file in the 'folder\\file.pth' format or 'Disabled'.")
- return v
-
+ except Exception as e:
+ print_message(f"Error validating RVC narrator voice: {str(e)}", "error", "GEN")
+ raise
+
+ @classmethod
def validate_pitches(cls, value):
+ """Validate pitch value is within acceptable range (-24 to 24)."""
+ debug_func_entry()
try:
num_value = float(value)
- except ValueError:
- raise ValueError("Pitch must be a number or a string representing a number.")
- if not -24 <= num_value <= 24:
- raise ValueError("Pitch needs to be a number between -24 and 24.")
- return value
+ if not -24 <= num_value <= 24:
+ print_message(f"Invalid pitch value: {value}", "error", "GEN")
+ raise ValueError("Pitch needs to be a number between -24 and 24.")
+ print_message(f"Pitch validated: {value}", "debug_rvc", "GEN")
+ return value
+ except ValueError as exc:
+ print_message(f"Invalid pitch format: {value}", "error", "GEN")
+ raise ValueError("Pitch must be a number or a string representing a number.") from exc
+ except Exception as e:
+ print_message(f"Error validating pitch: {str(e)}", "error", "GEN")
+ raise
def validate_json_input(json_input_data):
+ """Validate JSON input against JSONInput model schema."""
+ debug_func_entry()
try:
JSONInput(**json_input_data)
+ return None
except ValidationError as e:
error_messages = []
for error in e.errors():
field = error["loc"][0]
- description = JSONInput.__fields__[field].description
+ description = JSONInput.__fields__.get(field).field_info.description
error_messages.append(f"{field}: {description}")
error_message = "\n".join(error_messages)
- print(f"[{branding}API] \033[91mError with API request:\033[0m", error_message)
+ print_message(f"Error with API request: {error_message}", "error", "API")
return error_message
- return None
-###############################################################
-# /api/tts-generate Generation API Endpoint pull fresh values #
-###############################################################
+# API Configuration Getters
def get_api_text_filtering():
- return api_defaults["api_text_filtering"]
+ """Get text filtering setting."""
+ return config.api_def.api_text_filtering
def get_api_narrator_enabled():
- return api_defaults["api_narrator_enabled"]
+ """Get narrator mode status."""
+ return config.api_def.api_narrator_enabled
def get_api_text_not_inside():
- return api_defaults["api_text_not_inside"]
+ """Get text parsing mode."""
+ return config.api_def.api_text_not_inside
def get_api_language():
- return api_defaults["api_language"]
+ """Get API language setting."""
+ return config.api_def.api_language
def get_api_output_file_name():
- return api_defaults["api_output_file_name"]
+ """Get output filename template."""
+ return config.api_def.api_output_file_name
def get_api_output_file_timestamp():
- return api_defaults["api_output_file_timestamp"]
+ """Get file timestamp setting."""
+ return config.api_def.api_output_file_timestamp
def get_api_autoplay():
- return api_defaults["api_autoplay"]
+ """Get autoplay status."""
+ return config.api_def.api_autoplay
def get_api_autoplay_volume():
- return api_defaults["api_autoplay_volume"]
+ """Get autoplay volume level."""
+ return config.api_def.api_autoplay_volume
+
+# Engine Parameter Getters
def get_params_speed():
+ """Get speech generation speed."""
return model_engine.generationspeed_set
def get_params_temperature():
+ """Get model temperature."""
return model_engine.temperature_set
def get_params_repetition():
- value = model_engine.repetitionpenalty_set
- return float(str(value).replace(',', '.'))
+ """Get repetition penalty."""
+ return float(str(model_engine.repetitionpenalty_set).replace(',', '.'))
def get_params_pitch():
+ """Get voice pitch setting."""
return model_engine.pitch_set
def get_character_voice_gen():
+ """Get default character voice."""
return model_engine.def_character_voice
-def get_rvccharacter_voice_gen():
- return rvc_settings["rvc_char_model_file"]
-def get_rvccharacter_pitch():
- return rvc_settings["pitch"]
def get_narrator_voice_gen():
+ """Get default narrator voice."""
return model_engine.def_narrator_voice
+
+# RVC Settings Getters
+def get_rvccharacter_voice_gen():
+ """Get RVC character model."""
+ return config.rvc_settings.rvc_char_model_file
+def get_rvccharacter_pitch():
+ """Get RVC character pitch."""
+ return config.rvc_settings.pitch
def get_rvcnarrator_voice_gen():
- return rvc_settings["rvc_narr_model_file"]
+ """Get RVC narrator model."""
+ return config.rvc_settings.rvc_narr_model_file
def get_rvcnarrator_pitch():
- return rvc_settings["pitch"]
+ """Get RVC narrator pitch."""
+ return config.rvc_settings.pitch
#############################################
# /api/tts-generate Generation API Endpoint #
#############################################
+async def tts_validate_and_prepare_input(
+ text_input: str,
+ request_params: dict,
+ default_params: dict
+) -> Tuple[dict, Union[None, str]]:
+ """Validate TTS input parameters and prepare settings."""
+ debug_func_entry()
+
+ if config.debugging.debug_tts or config.debugging.debug_tts_variables:
+ print_message("\033[94mPre-validation parameter check > tts_validate_and_prepare_input > tts_server.py\033[0m", "debug_tts_variables", "GEN")
+ print_message("API Configuration:", "debug_tts_variables", "GEN")
+ config_items = {
+ "max_characters": config.api_def.api_max_characters,
+ "length_stripping": config.api_def.api_length_stripping,
+ "legacy_api": config.api_def.api_use_legacy_api,
+ "legacy_api_ip": config.api_def.api_legacy_ip_address,
+ "allowed_filter": config.api_def.api_allowed_filter
+ }
+ keys = list(config_items.keys())
+ for i, key in enumerate(keys):
+ # Use └─ for the last item, and ├─ for others
+ prefix = "└─" if i == len(keys) - 1 else "├─"
+ print_message(f"{prefix} {key}: {config_items[key]}", "debug_tts_variables", "GEN")
+
+ print_message("\033[94mIncoming TTS Variables > tts_validate_and_prepare_input > tts_server.py\033[0m", "debug_tts_variables", "GEN")
+ keys = list(request_params.keys())
+ for i, key in enumerate(keys):
+ # Use └─ for the last item, and ├─ for others
+ prefix = "└─" if i == len(keys) - 1 else "├─"
+ print_message(f"{prefix} {key}: {request_params[key]}", "debug_tts_variables", "GEN")
+
+
+ # Merge request parameters with defaults
+ params = {}
+ for key, default_value in default_params.items():
+ params[key] = request_params.get(key) if request_params.get(key) is not None else default_value
+
+ # Validate merged parameters
+ json_input_data = {
+ "text_input": text_input,
+ **params
+ }
+
+ validation_result = validate_json_input(json_input_data)
+
+ if config.debugging.debug_tts or config.debugging.debug_tts_variables:
+ print_message("\033[94mPost Validation Variables > tts_validate_and_prepare_input > tts_server.py\033[0m", "debug_tts_variables", "GEN")
+ keys = list(params.keys())
+ for i, key in enumerate(keys):
+ # Use └─ for the last item, and ├─ for others
+ prefix = "└─" if i == len(keys) - 1 else "├─"
+ print_message(f"{prefix} {key}: {params[key]}", "debug_tts_variables", "GEN")
+
+ return params, validation_result
+
+async def tts_handle_output_paths(output_file_name: str, timestamp: bool = True) -> Tuple[Path, str, str]:
+ """Generate output paths and URLs for TTS files."""
+ debug_func_entry()
+
+ if timestamp:
+ hash_object = hashlib.sha256(str(uuid.uuid4()).encode())
+ short_uuid = hash_object.hexdigest()[:5]
+ timestamp_str = str(int(time.time()))
+ filename = f'{output_file_name}_{timestamp_str}{short_uuid}.{model_engine.audio_format}'
+ else:
+ filename = f"{output_file_name}.{model_engine.audio_format}"
+
+ output_path = this_dir / config.get_output_directory() / filename
+
+ if config.api_def.api_use_legacy_api:
+ base_url = f'http://{config.api_def.api_legacy_ip_address}:{config.api_def.api_port_number}'
+ file_url = f'{base_url}/audio/{filename}'
+ cache_url = f'{base_url}/audiocache/{filename}'
+ else:
+ file_url = f'/audio/{filename}'
+ cache_url = f'/audiocache/{filename}'
+
+ print_message(f"Generated output path: {output_path}", "debug_tts", "GEN")
+ print_message(f"Generated URLs: {file_url}, {cache_url}", "debug_tts", "GEN")
+
+ return output_path, file_url, cache_url
+
+def tts_clean_text(text: str, filtering_type: str) -> str:
+ """Clean and filter input text based on specified filtering type."""
+ debug_func_entry()
+
+ print_message(f"Cleaning text with filter type: {filtering_type}", "debug_tts", "GEN")
+
+ if filtering_type == "html":
+ cleaned = html.unescape(standard_filtering(text))
+ elif filtering_type == "standard":
+ cleaned = text
+ else:
+ print_message("No filtering applied", "debug_tts", "GEN")
+ return text
+
+ # Common cleaning steps
+ cleaned = re.sub(r'([!?.])\1+', r'\1', cleaned)
+ cleaned = re.sub(rf'{config.api_def.api_allowed_filter}', '', cleaned)
+ cleaned = re.sub(r'\n+', ' ', cleaned)
+
+ print_message(f"Cleaned text: {cleaned}", "debug_tts", "GEN")
+
+ return cleaned
+
+async def tts_process_narrator_mode(params: dict, text_input: str) -> Tuple[Path, str, str]:
+ """Process text with narrator mode, handling different voice types."""
+ debug_func_entry()
+
+ if config.debugging.debug_fullttstext:
+ print_message(text_input, component="TTS")
+ else:
+ print_message(f"{text_input[:90]}{'...' if len(text_input) > 90 else ''}", component="TTS")
+
+ if params['narrator_enabled'].lower() == "silent" and params['text_not_inside'].lower() == "silent":
+ print_message("Both Narrator & Text-not-inside are set to silent. If you get no TTS, this is why.", "warning", "GEN")
+
+ if model_engine.lowvram_enabled and model_engine.device == "cpu":
+ await model_engine.handle_lowvram_change()
+
+ model_engine.tts_narrator_generatingtts = True
+ print_message("Processing with narrator mode", "debug_tts", "GEN")
+
+ processed_parts = process_text(text_input)
+ audio_files = []
+
+ for part_type, part in processed_parts:
+ if len(part.strip()) <= int(config.api_def.api_length_stripping):
+ continue
+
+ voice_to_use = tts_get_voice_for_part(part_type, params, part)
+ if not voice_to_use:
+ continue
+
+ output_file = await tts_generate_part(part, voice_to_use, params)
+ if output_file:
+ await tts_apply_rvc(output_file, part_type, params)
+ audio_files.append(output_file)
+
+ model_engine.tts_narrator_generatingtts = False
+
+ if audio_files:
+ print_message("\033[92mNarrated TTS generation complete\033[0m", component="GEN")
+ return await tts_finalize_output(audio_files, params)
+
+ print_message("\033[92mNarrated TTS generation complete\033[0m", component="GEN")
+ return await tts_finalize_output(audio_files, params)
+
+def tts_get_voice_for_part(part_type: str, params: dict, part: str) -> Optional[str]:
+ """Determine appropriate voice for text part."""
+ debug_func_entry()
+ preview = part[:50] + ('...' if len(part) > 50 else '') if not config.debugging.debug_fullttstext else part
+
+ if part_type == 'narrator':
+ if params['narrator_enabled'].lower() == "silent":
+ print_message(f"\033[95mNarrator Silent:\033[0m {preview}", component="GEN")
+ return None
+ voice = params['narrator_voice_gen']
+ print_message(f"\033[92mNarrator:\033[0m {preview}", component="GEN")
+
+ elif part_type == 'character':
+ voice = params['character_voice_gen']
+ print_message(f"\033[96mCharacter:\033[0m {preview}", component="GEN")
+
+ else:
+ if params['text_not_inside'] == "silent":
+ print_message(f"\033[95mText-not-inside Silent:\033[0m {preview}", component="GEN")
+ return None
+
+ voice = params['character_voice_gen'] if params['text_not_inside'] == "character" else params['narrator_voice_gen']
+ voice_type = "\033[96mCharacter (Text-not-inside)\033[0m" if params['text_not_inside'] == "character" else "\033[92mNarrator (Text-not-inside)\033[0m"
+ print_message(f"{voice_type}: {preview}", component="GEN")
+
+ return voice
+
+async def tts_generate_part(part: str, voice: str, params: dict) -> Optional[Path]:
+ """Generate audio for text part."""
+ debug_func_entry()
+ cleaned_part = tts_clean_text(part, params['text_filtering'])
+ output_file = await tts_handle_output_paths(params['output_file_name'])
+
+ try:
+ await generate_audio(
+ cleaned_part, voice, params['language'],
+ params['temperature'], params['repetition_penalty'],
+ params['speed'], params['pitch'],
+ output_file[0], False
+ )
+ return output_file[0]
+ except ValueError as e:
+ print_message(f"Invalid parameter value: {str(e)}", "error", "GEN")
+ return None
+ except (OSError, IOError) as e:
+ print_message(f"File system error: {str(e)}", "error", "GEN")
+ return None
+ except RuntimeError as e:
+ print_message(f"Audio generation runtime error: {str(e)}", "error", "GEN")
+ return None
+ except KeyError as e:
+ print_message(f"Missing required parameter: {str(e)}", "error", "GEN")
+ return None
+ except MemoryError as e:
+ print_message(f"Insufficient memory for audio generation: {str(e)}", "error", "GEN")
+ return None
+
+async def tts_apply_rvc(output_file: Path, part_type: str, params: dict):
+ """Apply RVC processing if enabled."""
+ if not config.rvc_settings.rvc_enabled:
+ return
+
+ if part_type == 'character':
+ process_rvc_narrator(part_type, params['rvccharacter_voice_gen'],
+ params['rvccharacter_pitch'],
+ config.rvc_settings.rvc_char_model_file,
+ output_file, infer_pipeline)
+ elif part_type == 'narrator':
+ process_rvc_narrator(part_type, params['rvcnarrator_voice_gen'],
+ params['rvcnarrator_pitch'],
+ config.rvc_settings.rvc_narr_model_file,
+ output_file, infer_pipeline)
+ else:
+ if params['text_not_inside'] == 'character':
+ process_rvc_narrator('character', params['rvccharacter_voice_gen'],
+ params['rvccharacter_pitch'],
+ config.rvc_settings.rvc_char_model_file,
+ output_file, infer_pipeline)
+ elif params['text_not_inside'] == 'narrator':
+ process_rvc_narrator('narrator', params['rvcnarrator_voice_gen'],
+ params['rvcnarrator_pitch'],
+ config.rvc_settings.rvc_narr_model_file,
+ output_file, infer_pipeline)
+
+async def tts_process_standard_mode(params: dict, text_input: str) -> Union[StreamingResponse, Tuple[Path, str, str]]:
+ """Process text with standard mode, without narrator functionality."""
+ debug_func_entry()
+
+ print_message("Standard generation mode", "debug_tts", "GEN")
+
+ output_file_path, output_file_url, output_cache_url = await tts_handle_output_paths( # pylint: disable=unused-variable # Do not remove output_file_url, output_cache_url
+ params['output_file_name'],
+ params['output_file_timestamp']
+ )
+
+ cleaned_text = tts_clean_text(text_input, params['text_filtering'])
+
+ if config.debugging.debug_fullttstext:
+ print_message(cleaned_text, component="TTS")
+ else:
+ print_message(f"{cleaned_text[:90]}{'...' if len(cleaned_text) > 90 else ''}", component="TTS")
+
+ try:
+ if params.get('streaming', False):
+ stream = await generate_audio(
+ cleaned_text, params['character_voice_gen'], params['language'],
+ params['temperature'], params['repetition_penalty'],
+ params['speed'], params['pitch'],
+ output_file_path, True
+ )
+ return StreamingResponse(stream, media_type="audio/wav")
+
+ await generate_audio(
+ cleaned_text, params['character_voice_gen'], params['language'],
+ params['temperature'], params['repetition_penalty'],
+ params['speed'], params['pitch'],
+ output_file_path, False
+ )
+
+ if config.rvc_settings.rvc_enabled:
+ await tts_handle_rvc_processing(output_file_path, params)
+
+ return await tts_handle_audio_output(output_file_path, params['autoplay'], params['autoplay_volume'])
+
+ except Exception as e:
+ print_message(f"Error in standard processing: {str(e)}", "error", "GEN")
+ raise
+
+async def tts_handle_rvc_processing(output_file_path: Path, params: dict):
+ """Handle RVC voice conversion if enabled."""
+ debug_func_entry()
+ if params['rvccharacter_voice_gen'].lower() in ["disabled", "disable"]:
+ print_message("RVC processing skipped", "debug_tts", "GEN")
+ return
+
+ print_message("Processing with RVC", "debug_tts", "GEN")
+ rvc_model_path = this_dir / "models" / "rvc_voices" / params['rvccharacter_voice_gen']
+ pth_path = rvc_model_path if rvc_model_path else config.rvc_settings.rvc_char_model_file
+ run_rvc(output_file_path, pth_path, params['rvccharacter_pitch'], infer_pipeline)
+
+async def tts_handle_audio_output(output_file_path: Path, autoplay: bool, volume: float) -> Tuple[Path, str, str]:
+ """Handle audio transcoding and playback."""
+ debug_func_entry()
+ model_format = str(model_engine.audio_format).lower()
+ output_format = str(config.transcode_audio_format).lower()
+
+ output_file_url = f'/audio/{output_file_path.name}'
+ output_cache_url = f'/audiocache/{output_file_path.name}'
+
+ if config.api_def.api_use_legacy_api:
+ base_url = f'http://{config.api_def.api_legacy_ip_address}:{config.api_def.api_port_number}'
+ output_file_url = f'{base_url}{output_file_url}'
+ output_cache_url = f'{base_url}{output_cache_url}'
+
+ if output_format not in ("disabled", model_format):
+ output_file_path, output_file_url, output_cache_url = await transcode_audio_if_necessary(
+ output_file_path, model_format, output_format
+ )
+
+ if sounddevice_installed and autoplay:
+ play_audio(output_file_path, volume)
+
+ return output_file_path, output_file_url, output_cache_url
+
+async def tts_finalize_output(audio_files: List[Path], params: dict) -> Tuple[Path, str, str]:
+ """Combine audio files and handle final processing."""
+ debug_func_entry()
+
+ output_file_path, output_file_url, output_cache_url = combine(
+ params['output_file_timestamp'],
+ params['output_file_name'],
+ audio_files
+ )
+
+ model_format = str(model_engine.audio_format).lower()
+ output_format = str(config.transcode_audio_format).lower()
+
+ if output_format not in ("disabled", model_format):
+ if not model_engine.tts_narrator_generatingtts:
+ output_file_path, output_file_url, output_cache_url = await transcode_audio_if_necessary(
+ output_file_path, model_format, output_format
+ )
+
+ if sounddevice_installed and params['autoplay']:
+ play_audio(output_file_path, params['autoplay_volume'])
+
+ return output_file_path, output_file_url, output_cache_url
+
@app.post("/api/tts-generate", response_class=JSONResponse)
async def apifunction_generate_tts_standard(
text_input: str = Form(...),
@@ -1364,61 +2140,11 @@ async def apifunction_generate_tts_standard(
_repetition_penalty: float = Depends(get_params_repetition),
_pitch: float = Depends(get_params_pitch),
):
- if debug_tts or debug_tts_variables:
- debug_lines = [
- f"[{branding}Debug] *******************************************************",
- f"[{branding}Debug] /api/tts_generate function called (debug_tts_variables)",
- f"[{branding}Debug] **** PRE validation checks ****",
- f"[{branding}Debug] *******************************************************",
- f"[{branding}Debug] Defaults will be pulled from Settings if not specified in API Request!",
- f"[{branding}Debug] max_characters : {api_defaults['api_max_characters']}",
- f"[{branding}Debug] length_stripping : {api_defaults['api_length_stripping']}",
- f"[{branding}Debug] legacy_api : {api_defaults['api_use_legacy_api']}",
- f"[{branding}Debug] legacy_api IPadd : {api_defaults['api_legacy_ip_address']}",
- f"[{branding}Debug] allowed_filter : {api_defaults['api_allowed_filter']}",
- f"[{branding}Debug] text_filtering : {text_filtering}",
- f"[{branding}Debug] character_voice_gen : {character_voice_gen}",
- f"[{branding}Debug] rvccharacter_voice_gen : {rvccharacter_voice_gen}",
- f"[{branding}Debug] rvccharacter_pitch : {rvccharacter_pitch}",
- f"[{branding}Debug] narrator_enabled : {narrator_enabled}",
- f"[{branding}Debug] narrator_voice_gen : {narrator_voice_gen}",
- f"[{branding}Debug] rvcnarrator_voice_gen : {rvcnarrator_voice_gen}",
- f"[{branding}Debug] rvcnarrator_pitch : {rvcnarrator_pitch}",
- f"[{branding}Debug] text_not_inside : {text_not_inside}",
- f"[{branding}Debug] language : {language}",
- f"[{branding}Debug] output_file_name : {output_file_name}",
- f"[{branding}Debug] output_file_timestamp : {output_file_timestamp}",
- f"[{branding}Debug] autoplay : {autoplay}",
- f"[{branding}Debug] autoplay_volume : {autoplay_volume}",
- f"[{branding}Debug] streaming : {streaming}",
- f"[{branding}Debug] speed : {speed}",
- f"[{branding}Debug] temperature : {temperature}",
- f"[{branding}Debug] repetition_penalty : {repetition_penalty}",
- f"[{branding}Debug] pitch : {pitch}"
- ]
- debug_info = "\n".join(debug_lines)
- print(debug_info)
- text_filtering = text_filtering or _text_filtering
- character_voice_gen = character_voice_gen or _character_voice_gen
- rvccharacter_voice_gen = rvccharacter_voice_gen or _rvccharacter_voice_gen
- rvccharacter_pitch = rvccharacter_pitch if rvccharacter_pitch is not None else _rvccharacter_pitch
- narrator_enabled = narrator_enabled or _narrator_enabled # NEED TO COME BACK AND LOOK AT THIS!!!
- narrator_voice_gen = narrator_voice_gen or _narrator_voice_gen
- rvcnarrator_voice_gen = rvcnarrator_voice_gen or _rvcnarrator_voice_gen
- rvcnarrator_pitch = rvcnarrator_pitch if rvcnarrator_pitch is not None else _rvcnarrator_pitch
- text_not_inside = text_not_inside or _text_not_inside
- language = language or _language
- output_file_name = output_file_name or _output_file_name
- output_file_timestamp = output_file_timestamp if output_file_timestamp is not None else _output_file_timestamp
- autoplay = autoplay if autoplay is not None else _autoplay
- autoplay_volume = autoplay_volume or _autoplay_volume
- speed = speed or _speed
- temperature = temperature or _temperature
- repetition_penalty = repetition_penalty or _repetition_penalty
- pitch = pitch or _pitch
+ """Generate TTS audio with optional narrator mode and RVC processing."""
+ debug_func_entry()
try:
- json_input_data = {
- "text_input": text_input,
+ # Prepare request parameters
+ request_params = {
"text_filtering": text_filtering,
"character_voice_gen": character_voice_gen,
"rvccharacter_voice_gen": rvccharacter_voice_gen,
@@ -1437,209 +2163,87 @@ async def apifunction_generate_tts_standard(
"speed": speed,
"temperature": temperature,
"repetition_penalty": repetition_penalty,
- "pitch": pitch,
+ "pitch": pitch
}
- JSONresult = validate_json_input(json_input_data)
- if JSONresult is None:
- pass
- else:
- return JSONResponse(content={"error": JSONresult}, status_code=400)
-
- if debug_tts or debug_tts_variables:
- debug_lines = [
- f"[{branding}Debug] *******************************************************",
- f"[{branding}Debug] /api/tts_generate function called (debug_tts_variables)",
- f"[{branding}Debug] **** POST validation checks ****",
- f"[{branding}Debug] *******************************************************",
- f"[{branding}Debug] Defaults will be pulled from Settings if not specified in API Request!",
- f"[{branding}Debug] max_characters : {api_defaults['api_max_characters']}",
- f"[{branding}Debug] length_stripping : {api_defaults['api_length_stripping']}",
- f"[{branding}Debug] legacy_api : {api_defaults['api_use_legacy_api']}",
- f"[{branding}Debug] legacy_api IPadd : {api_defaults['api_legacy_ip_address']}",
- f"[{branding}Debug] allowed_filter : {api_defaults['api_allowed_filter']}",
- f"[{branding}Debug] text_filtering : {text_filtering}",
- f"[{branding}Debug] character_voice_gen : {character_voice_gen}",
- f"[{branding}Debug] rvccharacter_voice_gen : {rvccharacter_voice_gen}",
- f"[{branding}Debug] rvccharacter_pitch : {rvccharacter_pitch}",
- f"[{branding}Debug] narrator_enabled : {narrator_enabled}",
- f"[{branding}Debug] narrator_voice_gen : {narrator_voice_gen}",
- f"[{branding}Debug] rvcnarrator_voice_gen : {rvcnarrator_voice_gen}",
- f"[{branding}Debug] rvcnarrator_pitch : {rvcnarrator_pitch}",
- f"[{branding}Debug] text_not_inside : {text_not_inside}",
- f"[{branding}Debug] language : {language}",
- f"[{branding}Debug] output_file_name : {output_file_name}",
- f"[{branding}Debug] output_file_timestamp : {output_file_timestamp}",
- f"[{branding}Debug] autoplay : {autoplay}",
- f"[{branding}Debug] autoplay_volume : {autoplay_volume}",
- f"[{branding}Debug] streaming : {streaming}",
- f"[{branding}Debug] speed : {speed}",
- f"[{branding}Debug] temperature : {temperature}",
- f"[{branding}Debug] repetition_penalty : {repetition_penalty}",
- f"[{branding}Debug] pitch : {pitch}"
- ]
- debug_info = "\n".join(debug_lines)
- print(debug_info)
- if narrator_enabled.lower() == "silent" and text_not_inside.lower() == "silent":
- print(f"[{branding}GEN] \033[92mBoth Narrator & Text-not-inside are set to \033[91msilent\033[92m. If you get no TTS, this is why.\033[0m")
- if narrator_enabled.lower() in ["true", "silent"]:
- if model_engine.lowvram_enabled and model_engine.device == "cpu":
- await model_engine.handle_lowvram_change()
- model_engine.tts_narrator_generatingtts = True
- print(f"[{branding}Debug] Moved into Narrator") if debug_tts else None
- print(f"[{branding}Debug] original text : {text_input}") if debug_tts else None
- processed_parts = process_text(text_input)
- audio_files_all_paragraphs = []
- for part_type, part in processed_parts:
- # Skip parts that are too short
- if len(part.strip()) <= int(params["api_def"]["api_length_stripping"]):
- continue
- # Determine the voice to use based on the part type
- if part_type == 'narrator':
- if narrator_enabled.lower() == "silent":
- print(f"[{branding}GEN] \033[95mNarrator Silent:\033[0m", part) # Green
- continue # Skip generating audio for narrator parts if set to "silent"
- voice_to_use = narrator_voice_gen
- print(f"[{branding}GEN] \033[92mNarrator:\033[0m", part) # Purple
- elif part_type == 'character':
- voice_to_use = character_voice_gen
- print(f"[{branding}GEN] \033[36mCharacter:\033[0m", part) # Yellow
- else:
- # Handle ambiguous parts based on user preference
- if text_not_inside == "silent":
- print(f"[{branding}GEN] \033[95mText-not-inside Silent:\033[0m", part) # Purple
- continue # Skip generating audio for ambiguous parts if set to "silent"
- voice_to_use = character_voice_gen if text_not_inside == "character" else narrator_voice_gen
- voice_description = "\033[36mCharacter (Text-not-inside)\033[0m" if text_not_inside == "character" else "\033[92mNarrator (Text-not-inside)\033[0m"
- print(f"[{branding}GEN] {voice_description}:", part)
- # Replace multiple exclamation marks, question marks, or other punctuation with a single instance
- cleaned_part = re.sub(r'([!?.])\1+', r'\1', part)
- # Further clean to remove any other unwanted characters
- cleaned_part = re.sub(rf'{api_defaults["api_allowed_filter"]}', '', cleaned_part)
- print(f"[{branding}Debug] text after api_allowed: {text_input}") if debug_tts else None
- # Remove all newline characters (single or multiple)
- cleaned_part = re.sub(r'\n+', ' ', cleaned_part)
- output_file = this_dir / output_directory / f'{output_file_name}_{uuid.uuid4()}_{int(time.time())}.{model_engine.audio_format}'
- output_file_str = output_file.as_posix()
- response = await generate_audio(cleaned_part, voice_to_use, language,temperature, repetition_penalty, speed, pitch, output_file_str, streaming)
- audio_path = output_file_str
- if rvc_settings["rvc_enabled"]:
- if part_type == 'character':
- process_rvc_narrator(part_type, rvccharacter_voice_gen, rvccharacter_pitch, rvc_settings["rvc_char_model_file"], output_file, infer_pipeline)
- elif part_type == 'narrator':
- process_rvc_narrator(part_type, rvcnarrator_voice_gen, rvcnarrator_pitch, rvc_settings["rvc_narr_model_file"], output_file, infer_pipeline)
- else:
- if text_not_inside == 'character':
- process_rvc_narrator('character', rvccharacter_voice_gen, rvccharacter_pitch, rvc_settings["rvc_char_model_file"], output_file, infer_pipeline)
- elif text_not_inside == 'narrator':
- process_rvc_narrator('narrator', rvcnarrator_voice_gen, rvcnarrator_pitch, rvc_settings["rvc_narr_model_file"], output_file, infer_pipeline)
- print(f"[{branding}Debug] Appending audio path to list") if debug_tts else None
- audio_files_all_paragraphs.append(audio_path)
- # Combine audio files across paragraphs
- model_engine.tts_narrator_generatingtts = False
- print(f"[{branding}Debug] Narrator sending to combine") if debug_tts else None
- output_file_path, output_file_url, output_cache_url = combine(output_file_timestamp, output_file_name, audio_files_all_paragraphs)
- # Transcode audio if necessary
- model_audio_format = str(model_engine.audio_format).lower()
- output_audio_format = str(params["transcode_audio_format"]).lower()
- if output_audio_format == "disabled":
- pass
- else:
- if model_audio_format != output_audio_format and not model_engine.tts_narrator_generatingtts:
- print(f"[{branding}Debug] Pre Transcode call - Output file: {output_file_path}") if debug_tts else None
- output_file_path, output_file_url, output_cache_url = await transcode_audio_if_necessary(output_file_path, model_audio_format, output_audio_format)
- print(f"[{branding}Debug] Post Transcode call - Output file: {output_file_path}") if debug_tts else None
- if sounddevice_installed == False:
- autoplay = False
- if autoplay:
- play_audio(output_file_path, autoplay_volume)
- # Move model back to cpu system ram if needed.
- if model_engine.lowvram_enabled and model_engine.device == "cuda" and model_engine.lowvram_capable:
- await model_engine.handle_lowvram_change()
- return JSONResponse(content={"status": "generate-success", "output_file_path": str(output_file_path), "output_file_url": str(output_file_url), "output_cache_url": str(output_cache_url)}, status_code=200)
- else:
- print(f"[{branding}Debug] Moved into Standard generation") if debug_tts else None
- print(f"[{branding}Debug] original text : {text_input}") if debug_tts else None
- if output_file_timestamp:
- timestamp = int(time.time())
- # Generate a standard UUID
- original_uuid = uuid.uuid4()
- # Hash the UUID using SHA-256
- hash_object = hashlib.sha256(str(original_uuid).encode())
- hashed_uuid = hash_object.hexdigest()
- # Truncate to the desired length, for example, 16 characters
- short_uuid = hashed_uuid[:5]
- output_file_path = this_dir / output_directory / f'{output_file_name}_{timestamp}{short_uuid}.{model_engine.audio_format}'
- #Legacy API or New API return
- if params["api_def"]["api_use_legacy_api"]:
- output_file_url = f'http://{api_defaults["api_legacy_ip_address"]}:{api_defaults["api_port_number"]}/audio/{output_file_name}_{timestamp}{short_uuid}.{model_engine.audio_format}'
- output_cache_url = f'http://{api_defaults["api_legacy_ip_address"]}:{api_defaults["api_port_number"]}/audiocache/{output_file_name}_{timestamp}{short_uuid}.{model_engine.audio_format}'
- else:
- output_file_url = f'/audio/{output_file_name}_{timestamp}{short_uuid}.{model_engine.audio_format}'
- output_cache_url = f'/audiocache/{output_file_name}_{timestamp}{short_uuid}.{model_engine.audio_format}'
- else:
- output_file_path = this_dir / output_directory / f"{output_file_name}.{model_engine.audio_format}"
- # Legacy API or New API return
- if params["api_def"]["api_use_legacy_api"]:
- output_file_url = f'http://{api_defaults["api_legacy_ip_address"]}:{api_defaults["api_port_number"]}/audio/{output_file_name}.{model_engine.audio_format}'
- output_cache_url = f'http://{api_defaults["api_legacy_ip_address"]}:{api_defaults["api_port_number"]}/audiocache/{output_file_name}.{model_engine.audio_format}'
- else:
- output_file_url = f'/audio/{output_file_name}.{model_engine.audio_format}'
- output_cache_url = f'/audiocache/{output_file_name}.{model_engine.audio_format}'
- if text_filtering == "html":
- cleaned_string = html.unescape(standard_filtering(text_input))
- cleaned_string = re.sub(r'([!?.])\1+', r'\1', cleaned_string)
- # Further clean to remove any other unwanted characters
- cleaned_string = re.sub(rf'{api_defaults["api_allowed_filter"]}', '', cleaned_string)
- print(f"[{branding}Debug] HTML Filtering - text after api_allowed: {cleaned_string}") if debug_tts else None
- # Remove all newline characters (single or multiple)
- cleaned_string = re.sub(r'\n+', ' ', cleaned_string)
- elif text_filtering == "standard":
- cleaned_string = re.sub(r'([!?.])\1+', r'\1', text_input)
- # Further clean to remove any other unwanted characters
- cleaned_string = re.sub(rf'{api_defaults["api_allowed_filter"]}', '', cleaned_string)
- print(f"[{branding}Debug] Standard Filtering - text after api_allowed: {cleaned_string}") if debug_tts else None
- # Remove all newline characters (single or multiple)
- cleaned_string = re.sub(r'\n+', ' ', cleaned_string)
- else:
- print(f"[{branding}Debug] No filtering text : {text_input}") if debug_tts else None
- cleaned_string = text_input
- print(f"[{branding}GEN]", cleaned_string)
- print(f"[{branding}Debug] Sending request to generate_audio in tts_server.py") if debug_tts else None
+
+ default_params = {
+ "text_filtering": _text_filtering,
+ "character_voice_gen": _character_voice_gen,
+ "rvccharacter_voice_gen": _rvccharacter_voice_gen,
+ "rvccharacter_pitch": _rvccharacter_pitch,
+ "narrator_enabled": _narrator_enabled,
+ "narrator_voice_gen": _narrator_voice_gen,
+ "rvcnarrator_voice_gen": _rvcnarrator_voice_gen,
+ "rvcnarrator_pitch": _rvcnarrator_pitch,
+ "text_not_inside": _text_not_inside,
+ "language": _language,
+ "output_file_name": _output_file_name,
+ "output_file_timestamp": _output_file_timestamp,
+ "autoplay": _autoplay,
+ "autoplay_volume": _autoplay_volume,
+ "speed": _speed,
+ "temperature": _temperature,
+ "repetition_penalty": _repetition_penalty,
+ "pitch": _pitch
+ }
+
+ # Validate and prepare input
+ params, validation_error = await tts_validate_and_prepare_input(
+ text_input, request_params, default_params
+ )
+
+ if validation_error:
+ return JSONResponse(content={"error": validation_error}, status_code=400)
+
+ # Process based on mode
+ if params["narrator_enabled"].lower() in ["true", "silent"]:
try:
- if streaming:
- response = await generate_audio(cleaned_string, character_voice_gen, language, temperature, repetition_penalty, speed, pitch, output_file_path, streaming)
- return StreamingResponse(response, media_type="audio/wav")
- else:
- response = await generate_audio(cleaned_string, character_voice_gen, language, temperature, repetition_penalty, speed, pitch, output_file_path, streaming)
- if rvc_settings["rvc_enabled"]:
- if rvccharacter_voice_gen.lower() in ["disabled", "disable"]:
- print(f"[{branding}Debug] Pass rvccharacter_voice_gen") if debug_tts else None
- pass # Skip RVC processing for character part
- else:
- print(f"[{branding}Debug] send to rvc") if debug_tts else None
- rvccharacter_voice_gen = this_dir / "models" / "rvc_voices" / rvccharacter_voice_gen
- pth_path = rvccharacter_voice_gen if rvccharacter_voice_gen else rvc_settings["rvc_char_model_file"]
- run_rvc(output_file_path, pth_path, rvccharacter_pitch, infer_pipeline)
- # Transcode audio if necessary
- model_audio_format = str(model_engine.audio_format).lower()
- output_audio_format = str(params["transcode_audio_format"]).lower()
- print(f"[{branding}Debug] At Model audio format") if debug_tts else None
- if output_audio_format == "disabled":
- pass
- else:
- if model_audio_format != output_audio_format and not model_engine.tts_narrator_generatingtts:
- print(f"[{branding}Debug] Pre Transcode call - Output file: {output_file_path}") if debug_tts else None
- output_file_path, output_file_url, output_cache_url = await transcode_audio_if_necessary(output_file_path, model_audio_format, output_audio_format)
- print(f"[{branding}Debug] Post Transcode call - Output file: {output_file_path}") if debug_tts else None
- if sounddevice_installed == False:
- autoplay = False
- if autoplay:
- play_audio(output_file_path, autoplay_volume)
- return JSONResponse(content={"status": "generate-success", "output_file_path": str(output_file_path), "output_file_url": str(output_file_url), "output_cache_url": str(output_cache_url)}, status_code=200)
- except Exception as e:
- return JSONResponse(content={"status": "generate-failure", "error": "An error occurred"}, status_code=500)
- except Exception as e:
- return JSONResponse(content={"status": "generate-failure", "error": "An error occurred"}, status_code=500)
+ output_file_path, output_file_url, output_cache_url = await tts_process_narrator_mode(
+ params, text_input
+ )
+ finally:
+ # Ensure model returns to CPU if using low VRAM
+ if model_engine.lowvram_enabled and model_engine.device == "cuda" and model_engine.lowvram_capable:
+ await model_engine.handle_lowvram_change()
+ else:
+ output_file_path, output_file_url, output_cache_url = await tts_process_standard_mode(
+ params, text_input
+ )
+
+ if isinstance(output_file_path, StreamingResponse):
+ return output_file_path
+
+ return JSONResponse(
+ content={
+ "status": "generate-success",
+ "output_file_path": str(output_file_path),
+ "output_file_url": str(output_file_url),
+ "output_cache_url": str(output_cache_url)
+ },
+ status_code=200
+ )
+
+ except ValueError as e:
+ print_message(f"Invalid parameter value: {str(e)}", "error", "GEN")
+ return JSONResponse(content={"status": "generate-failure", "error": str(e)}, status_code=400)
+ except ValidationError as e:
+ print_message(f"Input validation error: {str(e)}", "error", "GEN")
+ return JSONResponse(content={"status": "generate-failure", "error": str(e)}, status_code=400)
+ except FileNotFoundError as e:
+ print_message(f"File or directory not found: {str(e)}", "error", "GEN")
+ return JSONResponse(content={"status": "generate-failure", "error": "Resource not found"}, status_code=404)
+ except PermissionError as e:
+ print_message(f"Permission denied: {str(e)}", "error", "GEN")
+ return JSONResponse(content={"status": "generate-failure", "error": "Permission denied"}, status_code=403)
+ except (OSError, IOError) as e:
+ print_message(f"I/O operation failed: {str(e)}", "error", "GEN")
+ return JSONResponse(content={"status": "generate-failure", "error": "File operation failed"}, status_code=500)
+ except RuntimeError as e:
+ print_message(f"Runtime error in TTS generation: {str(e)}", "error", "GEN")
+ return JSONResponse(content={"status": "generate-failure", "error": "TTS generation failed"}, status_code=500)
+ except MemoryError as e:
+ print_message(f"Out of memory: {str(e)}", "error", "GEN")
+ return JSONResponse(content={"status": "generate-failure", "error": "Insufficient memory"}, status_code=507)
#############################
#### Word Add-in Sharing ####
@@ -1650,8 +2254,8 @@ app.mount("/api/word_addin", StaticFiles(directory=os.path.join(this_dir / 'syst
#############################################
#### TTS Generator Comparision Endpoints ####
#############################################
-import aiofiles
class TTSItem(BaseModel):
+ """Individual TTS item structure for saving."""
id: int
fileUrl: str
text: str
@@ -1659,76 +2263,207 @@ class TTSItem(BaseModel):
language: str
class TTSData(BaseModel):
+ """Collection of TTS items."""
ttsList: List[TTSItem]
@app.post("/api/save-tts-data")
async def apifunction_save_tts_data(tts_data: List[TTSItem]):
- # Convert the list of Pydantic models to a list of dictionaries
- tts_data_list = [item.dict() for item in tts_data]
- # Serialize the list of dictionaries to a JSON string
- tts_data_json = json.dumps(tts_data_list, indent=4)
- async with aiofiles.open(this_dir / output_directory / "ttsList.json", 'w') as f:
- await f.write(tts_data_json)
- return {"message": "Data saved successfully"}
+ """Save TTS data to JSON file in output directory."""
+ debug_func_entry()
+
+ try:
+ print_message(f"Saving {len(tts_data)} TTS items", "debug_tts", "GEN")
+
+ # Convert Pydantic models to dictionaries
+ tts_data_list = [item.model_dump() for item in tts_data]
+ tts_data_json = json.dumps(tts_data_list, indent=4)
+
+ output_path = this_dir / config.get_output_directory() / "ttsList.json"
+
+ async with aiofiles.open(output_path, 'w') as f:
+ await f.write(tts_data_json)
+
+ print_message(f"TTS data saved to {output_path}", "debug_tts", "GEN")
+ return {"message": "Data saved successfully"}
+
+ except Exception as e:
+ print_message(f"Error saving TTS data: {str(e)}", "error", "GEN")
+ raise HTTPException(status_code=500, detail="Failed to save TTS data") from e
########################################
# Trigger TTS Gen Text/Speech Analysis #
########################################
@app.get("/api/trigger-analysis")
-async def apifunction_trigger_analysis(threshold: int = Query(default=98)):
- venv_path = sys.prefix
- env = os.environ.copy()
- env["PATH"] = os.path.join(venv_path, "bin") + ":" + env["PATH"]
- ttslist_path = this_dir / output_directory / "ttsList.json"
- wavfile_path = this_dir / output_directory
- subprocess.run([sys.executable, "tts_diff.py", f"--threshold={threshold}", f"--ttslistpath={ttslist_path}", f"--wavfilespath={wavfile_path}"], cwd=this_dir / "system" / "tts_diff", env=env)
- # Read the analysis summary
+async def apifunction_trigger_analysis(threshold: int = Query(default=98), whisper_model: str = Query(default="large-v3")):
+ """Trigger Whisper analysis to compare generated TTS against original text.
+
+ Args:
+ threshold: Minimum acceptable match percentage (default: 98)
+ """
+ debug_func_entry()
+
try:
- with open(this_dir / output_directory / "analysis_summary.json", "r") as summary_file:
- summary_data = json.load(summary_file)
- except FileNotFoundError:
- summary_data = {"error": "Analysis summary file not found."}
- return {"message": "Analysis Completed", "summary": summary_data}
+ # Setup environment
+ venv_path = sys.prefix
+ env = os.environ.copy()
+ env["PATH"] = os.path.join(venv_path, "bin") + ":" + env["PATH"]
+
+ # Define paths
+ ttslist_path = this_dir / config.get_output_directory() / "ttsList.json"
+ wavfile_path = this_dir / config.get_output_directory()
+
+ if not ttslist_path.exists():
+ print_message("TTS list file not found", "error", "GEN")
+ raise HTTPException(status_code=404, detail="TTS list file not found")
+
+ print_message(f"Starting TTS analysis with threshold: {threshold}%, using Whisper model: {whisper_model}", "debug_tts", "GEN")
+
+ # Run analysis script
+ result = subprocess.run(
+ [
+ sys.executable,
+ "tts_diff.py",
+ f"--threshold={threshold}",
+ f"--ttslistpath={ttslist_path}",
+ f"--wavfilespath={wavfile_path}",
+ f"--whisper-model={whisper_model}"
+ ],
+ cwd=this_dir / "system" / "tts_diff",
+ env=env,
+ text=True,
+ check=True
+ )
+
+ if result.returncode != 0:
+ print_message(f"Analysis script error: {result.stderr}", "error", "GEN")
+ raise HTTPException(status_code=500, detail="Analysis script failed")
+
+ # Read analysis results
+ try:
+ summary_path = this_dir / config.get_output_directory() / "analysis_summary.json"
+ with open(summary_path, "r", encoding="utf-8") as summary_file:
+ summary_data = json.load(summary_file)
+ print_message("Analysis summary loaded successfully", "debug_tts", "GEN")
+ except FileNotFoundError:
+ print_message("Analysis summary file not found", "error", "GEN")
+ summary_data = {"error": "Analysis summary file not found."}
+ except json.JSONDecodeError as e:
+ print_message(f"Error parsing analysis summary: {str(e)}", "error", "GEN")
+ summary_data = {"error": "Invalid analysis summary format."}
+
+ return {"message": "Analysis Completed", "summary": summary_data}
+
+ except Exception as e:
+ print_message(f"Error during analysis: {str(e)}", "error", "GEN")
+ raise HTTPException(status_code=500, detail=str(e)) from e
###########################################
# TTS Generator SRT Subtitiles generation #
###########################################
@app.get("/api/srt-generation")
async def apifunction_srt_generation():
- venv_path = sys.prefix
- env = os.environ.copy()
- env["PATH"] = os.path.join(venv_path, "bin") + ":" + env["PATH"]
- ttslist_path = this_dir / output_directory / "ttsList.json"
- wavfile_path = this_dir / output_directory
- subprocess.run([sys.executable, "tts_srt.py", f"--ttslistpath={ttslist_path}", f"--wavfilespath={wavfile_path}"], cwd=this_dir / "system" / "tts_srt", env=env)
- srt_file_path = this_dir / output_directory / "subtitles.srt"
- if not srt_file_path.exists():
- raise HTTPException(status_code=404, detail="Subtitle file not found.")
- return FileResponse(path=srt_file_path, filename="subtitles.srt", media_type='application/octet-stream')
+ """Generate SRT subtitles for TTS audio files using speech-to-text analysis."""
+ debug_func_entry()
+
+ try:
+ # Setup environment
+ venv_path = sys.prefix
+ env = os.environ.copy()
+ env["PATH"] = os.path.join(venv_path, "bin") + ":" + env["PATH"]
+
+ # Define paths
+ ttslist_path = this_dir / config.get_output_directory() / "ttsList.json"
+ wavfile_path = this_dir / config.get_output_directory()
+ srt_file_path = this_dir / config.get_output_directory() / "subtitles.srt"
+
+ # Validate input file existence
+ if not ttslist_path.exists():
+ print_message("TTS list file not found", "error", "GEN")
+ raise HTTPException(status_code=404, detail="TTS list file not found")
+
+ print_message("Starting SRT generation process", "debug_tts", "GEN")
+
+ # Run SRT generation script
+ result = subprocess.run(
+ [
+ sys.executable,
+ "tts_srt.py",
+ f"--ttslistpath={ttslist_path}",
+ f"--wavfilespath={wavfile_path}"
+ ],
+ cwd=this_dir / "system" / "tts_srt",
+ env=env,
+ capture_output=True,
+ text=True,
+ check=True
+ )
+
+ # Validate subprocess result
+ if result.returncode != 0:
+ stderr = result.stderr.strip() if result.stderr else "No error details available."
+ print_message(f"SRT generation script error: {stderr}", "error", "GEN")
+ raise HTTPException(status_code=500, detail=f"SRT generation failed: {stderr}")
+
+ # Verify output file
+ if not srt_file_path.exists():
+ print_message("Generated SRT file not found", "error", "GEN")
+ raise HTTPException(status_code=404, detail="Subtitle file not found")
+
+ print_message("SRT generation completed successfully", "debug_tts", "GEN")
+ return FileResponse(
+ path=srt_file_path,
+ filename="subtitles.srt",
+ media_type='application/octet-stream'
+ )
+
+ except subprocess.CalledProcessError as e:
+ print_message(f"Subprocess execution failed: {str(e)}", "error", "GEN")
+ raise HTTPException(status_code=500, detail=f"Subprocess execution failed: {str(e)}") from e
+
+ except Exception as e:
+ print_message(f"Error during SRT generation: {str(e)}", "error", "GEN")
+ raise HTTPException(status_code=500, detail=str(e)) from e
+
#################################
# Static Mount for file serving #
#################################
app.mount("/static", StaticFiles(directory=str(this_dir / "system")), name="static")
-
+
########################################
# Legacy JSON update settings function #
########################################
# Setup logging
logging.basicConfig(level=logging.DEBUG)
-def get_json_data():
- with open(this_dir / "confignew.json", "r") as json_file:
- data = json.load(json_file)
- return data
@app.get("/settings")
async def get_settings(request: Request):
- data = get_json_data()
- return templates.TemplateResponse("admin.html", {"request": request, "data": data})
+ """Return HTML template with current configuration settings."""
+ debug_func_entry()
+
+ try:
+ settings_data = config.to_dict()
+ print_message("Rendering admin settings page", "debug_api", "API")
+ return templates.TemplateResponse("admin.html", {
+ "request": request,
+ "data": settings_data
+ })
+ except Exception as e:
+ print_message(f"Error rendering settings page: {str(e)}", "error", "API")
+ raise HTTPException(status_code=500, detail="Failed to load settings page") from e
@app.get("/settings-json")
async def get_settings_json():
- return get_json_data()
+ """Return current configuration settings as JSON."""
+ debug_func_entry()
+
+ try:
+ settings_data = config.to_dict()
+ print_message("Returning JSON settings data", "debug_api", "API")
+ return settings_data
+ except Exception as e:
+ print_message(f"Error retrieving settings data: {str(e)}", "error", "API")
+ raise HTTPException(status_code=500, detail="Failed to retrieve settings") from e
@app.post("/update-settings")
async def update_settings(
@@ -1738,44 +2473,101 @@ async def update_settings(
gradio_port_number: int = Form(...),
api_port_number: int = Form(...),
):
- data = get_json_data()
- # Update the settings based on the form values
- data["delete_output_wavs"] = delete_output_wavs
- data["gradio_interface"] = gradio_interface.lower() == 'true'
- data["gradio_port_number"] = gradio_port_number
- data["api_def"]["api_port_number"] = api_port_number
- # Save the updated settings back to the JSON file
- with open(this_dir / "confignew.json", "w") as json_file:
- json.dump(data, json_file, indent=4)
- # Redirect to the settings page to display the updated settings
- return templates.TemplateResponse("admin.html", {"request": request, "data": data})
-
+ """Update configuration settings from admin form submission."""
+ debug_func_entry()
+
+ try:
+ print_message("Updating configuration settings", "debug_api", "API")
+
+ # Log current values before update
+ if config.debugging.debug_api:
+ print_message("Current settings:", "debug_api", "API")
+ print_message(f"delete_output_wavs: {config.delete_output_wavs}", "debug_api", "API")
+ print_message(f"gradio_interface: {config.gradio_interface}", "debug_api", "API")
+ print_message(f"gradio_port: {config.gradio_port_number}", "debug_api", "API")
+ print_message(f"api_port: {config.api_def.api_port_number}", "debug_api", "API")
+
+ # Validate port numbers
+ if not (1024 <= gradio_port_number <= 65535 and 1024 <= api_port_number <= 65535):
+ print_message("Invalid port numbers provided", "error", "API")
+ raise HTTPException(status_code=400, detail="Port numbers must be between 1024 and 65535")
+
+ # Update configuration
+ config.delete_output_wavs = delete_output_wavs
+ config.gradio_interface = gradio_interface.lower() == 'true'
+ config.gradio_port_number = gradio_port_number
+ config.api_def.api_port_number = api_port_number
+
+ # Log new values after update
+ if config.debugging.debug_api:
+ print_message("Updated settings:", "debug_api", "API")
+ print_message(f"delete_output_wavs: {config.delete_output_wavs}", "debug_api", "API")
+ print_message(f"gradio_interface: {config.gradio_interface}", "debug_api", "API")
+ print_message(f"gradio_port: {config.gradio_port_number}", "debug_api", "API")
+ print_message(f"api_port: {config.api_def.api_port_number}", "debug_api", "API")
+
+ # Save configuration
+ config.save()
+ print_message("Settings saved successfully", "debug_api", "API")
+
+ # Return updated settings page
+ return templates.TemplateResponse("admin.html", {
+ "request": request,
+ "data": config.to_dict()
+ })
+
+ except ValueError as e:
+ print_message(f"Validation error in settings update: {str(e)}", "error", "API")
+ raise HTTPException(status_code=400, detail=str(e)) from e
+ except Exception as e:
+ print_message(f"Error updating settings: {str(e)}", "error", "API")
+ raise HTTPException(status_code=500, detail="Failed to update settings") from e
+
# Create an instance of Jinja2Templates for rendering HTML templates
templates = Jinja2Templates(directory=this_dir / "system")
# Get the admin interface template
template = templates.get_template("admin.html")
# Render the template with the dynamic values
-rendered_html = template.render(params=params)
-
+rendered_html = template.render(params=config.to_dict())
+
###################################################
#### Webserver Startup & Initial model Loading ####
###################################################
@app.get("/")
async def read_root():
- return HTMLResponse(content=rendered_html, status_code=200)
+ """Serve the main application HTML page."""
+ debug_func_entry()
+
+ try:
+ print_message("Serving main application page", "debug_api", "API")
+ return HTMLResponse(content=rendered_html, status_code=200)
+ except Exception as e:
+ print_message(f"Error serving main page: {str(e)}", "error", "API")
+ raise HTTPException(status_code=500, detail="Failed to load application page") from e
# Start Uvicorn Webserver
-# port_parameter = int(params["api_def"]["api_port_number"])
+# port_parameter = int(config.api_def.api_port_number)
-if __name__ == "__main__":
- import uvicorn
+def start_server():
+ """
+ Starts the Uvicorn server for the AllTalk TTS application.
+
+ This function initializes the Uvicorn web server using the specified port
+ from command line arguments or a default port from the configuration.
+ It assigns the server instance to the global `uvicorn_server` variable
+ (although Uvicorn's `run` function does not return a useful value).
+ """
+ global uvicorn_server # pylint: disable=global-statement
# Command line argument parser
parser = argparse.ArgumentParser(description="AllTalk TTS Server")
parser.add_argument("--port", type=int, help="Port number for the server")
args = parser.parse_args()
# Determine the port to use
- config_port = int(params["api_def"]["api_port_number"])
+ config_port = int(config.api_def.api_port_number)
port_to_use = args.port if args.port is not None else config_port
# Start Uvicorn Webserver
- uvicorn_server = uvicorn.run(app, host="0.0.0.0", port=port_to_use, log_level="debug")
+ uvicorn_server = uvicorn.run(app, host="0.0.0.0", port=port_to_use, log_level="debug") # pylint: disable=assignment-from-no-return
+if __name__ == "__main__":
+ start_server()
+
\ No newline at end of file
diff --git a/voices/xtts_latents/xtts_latents.txt b/voices/xtts_latents/xtts_latents.txt
new file mode 100644
index 0000000..9032e56
--- /dev/null
+++ b/voices/xtts_latents/xtts_latents.txt
@@ -0,0 +1 @@
+Please see the XTTS Engine help for details of using multi-voice sets or JSON latents
\ No newline at end of file
diff --git a/voices/xtts_multi_voice_sets/xtts_multi_voice_sets.txt b/voices/xtts_multi_voice_sets/xtts_multi_voice_sets.txt
new file mode 100644
index 0000000..9032e56
--- /dev/null
+++ b/voices/xtts_multi_voice_sets/xtts_multi_voice_sets.txt
@@ -0,0 +1 @@
+Please see the XTTS Engine help for details of using multi-voice sets or JSON latents
\ No newline at end of file