Files
diodiogod-TTS-Audio-Suite/utils/models/extra_paths.py
T
diodiogod 9ad7c78893 Add extra_model_paths.yaml support and enhance voice discovery
Technical implementation:
- Add TtsExtraPathsManager for ComfyUI extra_model_paths.yaml integration
- Support shared model directories across multiple ComfyUI installations
- Implement intelligent model discovery across configured paths
- Add voice discovery fallback: models/voices → models/TTS/voices → examples
- Update character alias map loading with proper priority system
- Enhance voice directory scanning to include TTS organization structure
- Add changelog guidelines requiring engine names in titles
- Reference: GitHub issue #63
2025-09-10 12:12:32 -03:00

262 lines
11 KiB
Python

"""
Extra Model Paths Support for TTS Audio Suite
Implements support for ComfyUI's extra_model_paths.yaml configuration file
"""
import os
import yaml
import folder_paths
from typing import List, Dict, Any, Optional
import logging
class TtsExtraPathsManager:
"""
Manager for TTS model paths that respects ComfyUI's extra_model_paths.yaml configuration.
This ensures TTS Audio Suite uses the same shared model directories as other ComfyUI nodes,
preventing model duplication and enabling shared storage configurations.
"""
def __init__(self):
self.tts_folders = {}
self._load_tts_paths()
def _load_tts_paths(self):
"""Load TTS-specific paths from ComfyUI's folder_paths system"""
# First, check if TTS folders are already registered with ComfyUI
if hasattr(folder_paths, 'folder_names_and_paths'):
global_folders = folder_paths.folder_names_and_paths
# Look for existing TTS-related folders
for folder_type in ['TTS', 'tts', 'text_to_speech', 'voice_models']:
if folder_type in global_folders:
self.tts_folders[folder_type] = global_folders[folder_type][0] # Get paths list
# If no TTS folders registered, create default TTS structure
if not self.tts_folders:
self._setup_default_tts_paths()
def _setup_default_tts_paths(self):
"""Set up default TTS paths using ComfyUI's models directory structure"""
models_dir = folder_paths.models_dir
# Register TTS folder types with ComfyUI's folder_paths system
tts_model_types = {
'TTS': {
'paths': [os.path.join(models_dir, 'TTS')],
'extensions': {'.safetensors', '.bin', '.pt', '.pth', '.ckpt', 'folder'}
},
'voices': {
'paths': [
os.path.join(models_dir, 'voices'), # Primary: standard ComfyUI location
os.path.join(models_dir, 'TTS', 'voices') # Fallback: logical TTS organization
],
'extensions': {'.wav', '.mp3', '.flac', '.ogg', 'folder'}
}
}
# Register with ComfyUI's system so other nodes can also use these paths
for folder_type, config in tts_model_types.items():
folder_paths.add_model_folder_path(folder_type, config['paths'][0])
self.tts_folders[folder_type] = config['paths']
def get_tts_model_directory(self, model_type: str = 'TTS') -> str:
"""
Get the primary TTS model directory for a given type.
Args:
model_type: Type of TTS model ('TTS', 'TTS_voices', etc.)
Returns:
Primary directory path for this model type
"""
if model_type in self.tts_folders and self.tts_folders[model_type]:
return self.tts_folders[model_type][0] # Return first (primary) path
# Fallback to default if not configured
models_dir = folder_paths.models_dir
return os.path.join(models_dir, 'TTS')
def get_all_tts_model_paths(self, model_type: str = 'TTS') -> List[str]:
"""
Get all configured TTS model directories for a given type.
This includes both the default ComfyUI models folder and any extra_model_paths
configured directories.
Args:
model_type: Type of TTS model ('TTS', 'TTS_voices', etc.)
Returns:
List of all directory paths for this model type
"""
if model_type in self.tts_folders:
return self.tts_folders[model_type].copy()
# Fallback to default
models_dir = folder_paths.models_dir
return [os.path.join(models_dir, 'TTS')]
def find_model_in_paths(self, model_name: str, model_type: str = 'TTS',
subdirs: Optional[List[str]] = None) -> Optional[str]:
"""
Find a model file or directory in any of the configured TTS paths.
Args:
model_name: Name of the model to find
model_type: Type of TTS model ('TTS', 'TTS_voices', etc.)
subdirs: Optional list of subdirectories to check (e.g., ['chatterbox', 'f5tts'])
Returns:
Full path to the model if found, None otherwise
"""
search_paths = self.get_all_tts_model_paths(model_type)
# Search in all configured paths
for base_path in search_paths:
# Search directly in base path
if self._check_model_at_path(base_path, model_name):
return os.path.join(base_path, model_name)
# Search in subdirectories if specified
if subdirs:
for subdir in subdirs:
subpath = os.path.join(base_path, subdir)
if self._check_model_at_path(subpath, model_name):
return os.path.join(subpath, model_name)
return None
def _check_model_at_path(self, base_path: str, model_name: str) -> bool:
"""Check if a model exists at the given path"""
if not os.path.exists(base_path):
return False
full_path = os.path.join(base_path, model_name)
# Check if it's a file
if os.path.isfile(full_path):
return True
# Check if it's a directory (for model folders)
if os.path.isdir(full_path):
return True
return False
def get_preferred_download_path(self, model_type: str = 'TTS',
engine_name: Optional[str] = None) -> str:
"""
Get the preferred path for downloading new models.
This respects extra_model_paths.yaml configuration - if a user has configured
a shared models directory, new downloads will go there instead of the default.
Args:
model_type: Type of TTS model ('TTS', 'TTS_voices', etc.)
engine_name: Optional engine name for organization (e.g., 'chatterbox', 'f5tts')
Returns:
Full path where new models should be downloaded
"""
# Get the primary (first) configured path
base_path = self.get_tts_model_directory(model_type)
# Add engine subdirectory if specified
if engine_name:
base_path = os.path.join(base_path, engine_name)
# Ensure directory exists
os.makedirs(base_path, exist_ok=True)
return base_path
def register_tts_engine_paths(self, engine_name: str, custom_paths: Dict[str, str]):
"""
Register custom paths for a specific TTS engine.
Args:
engine_name: Name of the engine (e.g., 'chatterbox', 'f5tts')
custom_paths: Dictionary of path types to paths
"""
for path_type, path in custom_paths.items():
folder_type = f"TTS_{engine_name}_{path_type}"
folder_paths.add_model_folder_path(folder_type, path)
self.tts_folders[folder_type] = [path]
def get_voices_directory(self) -> str:
"""Get the primary directory for voice files, respecting extra_model_paths.yaml"""
# Check for configured voices paths first
if 'voices' in self.tts_folders and self.tts_folders['voices']:
primary_path = self.tts_folders['voices'][0]
os.makedirs(primary_path, exist_ok=True)
return primary_path
# Fallback to default models/voices
models_dir = folder_paths.models_dir
voices_dir = os.path.join(models_dir, 'voices')
os.makedirs(voices_dir, exist_ok=True)
return voices_dir
def get_all_voices_paths(self) -> List[str]:
"""Get all configured voice directories including fallbacks"""
paths = []
# Add configured voices paths (includes both models/voices and models/TTS/voices)
if 'voices' in self.tts_folders:
paths.extend(self.tts_folders['voices'])
else:
# Fallback to default structure if not configured
models_dir = folder_paths.models_dir
paths.extend([
os.path.join(models_dir, 'voices'), # Primary
os.path.join(models_dir, 'TTS', 'voices') # Fallback
])
# Note: voices_examples/ is handled by the existing VoiceDiscovery class
# We don't include it here to avoid duplicating that functionality
return paths
# Global instance
_tts_paths_manager = TtsExtraPathsManager()
def get_tts_model_directory(model_type: str = 'TTS') -> str:
"""Get the primary TTS model directory for downloads"""
return _tts_paths_manager.get_tts_model_directory(model_type)
def get_all_tts_model_paths(model_type: str = 'TTS') -> List[str]:
"""Get all TTS model search paths"""
return _tts_paths_manager.get_all_tts_model_paths(model_type)
def find_model_in_paths(model_name: str, model_type: str = 'TTS',
subdirs: Optional[List[str]] = None) -> Optional[str]:
"""Find a model in any configured TTS path"""
return _tts_paths_manager.find_model_in_paths(model_name, model_type, subdirs)
def get_preferred_download_path(model_type: str = 'TTS',
engine_name: Optional[str] = None) -> str:
"""Get preferred path for new model downloads"""
return _tts_paths_manager.get_preferred_download_path(model_type, engine_name)
def get_voices_directory() -> str:
"""Get the voices directory respecting extra_model_paths.yaml"""
return _tts_paths_manager.get_voices_directory()
def get_all_voices_paths() -> List[str]:
"""Get all configured voice search paths"""
return _tts_paths_manager.get_all_voices_paths()
def register_tts_engine_paths(engine_name: str, custom_paths: Dict[str, str]):
"""Register custom paths for a specific TTS engine"""
return _tts_paths_manager.register_tts_engine_paths(engine_name, custom_paths)
# Initialize TTS paths with ComfyUI integration
def initialize_tts_paths():
"""Initialize TTS paths integration with ComfyUI's folder_paths system"""
global _tts_paths_manager
_tts_paths_manager._load_tts_paths()
# Auto-initialize on import
initialize_tts_paths()