Spaces:
Sleeping
Sleeping
Michael Hu
feat: replace legacy TTS providers with Chatterbox as the single, default provider
237cb26
| """TTS provider factory for creating and managing TTS providers.""" | |
| import logging | |
| from typing import Dict, List, Optional, Type | |
| from ..base.tts_provider_base import TTSProviderBase | |
| from ...domain.exceptions import SpeechSynthesisException | |
| logger = logging.getLogger(__name__) | |
| class TTSProviderFactory: | |
| """Factory for creating and managing TTS providers.""" | |
| def __init__(self): | |
| """Initialize the TTS provider factory.""" | |
| self._providers: Dict[str, Type[TTSProviderBase]] = {} | |
| self._provider_instances: Dict[str, TTSProviderBase] = {} | |
| self._register_default_providers() | |
| def _register_default_providers(self): | |
| """Register available TTS providers.""" | |
| # Import providers dynamically to avoid import errors if dependencies are missing | |
| # Register only Chatterbox provider | |
| try: | |
| from .chatterbox_provider import ChatterboxTTSProvider | |
| self._providers['chatterbox'] = ChatterboxTTSProvider | |
| logger.info("Registered Chatterbox TTS provider") | |
| except ImportError as e: | |
| logger.warning(f"Chatterbox TTS provider not available: {e}") | |
| raise SpeechSynthesisException("No TTS providers available - Chatterbox is required") from e | |
| def get_available_providers(self) -> List[str]: | |
| """Get list of available TTS providers.""" | |
| logger.info("π Checking availability of TTS providers...") | |
| available = [] | |
| for name, provider_class in self._providers.items(): | |
| logger.info(f"Checking provider: {name}") | |
| try: | |
| # Create instance if not cached | |
| if name not in self._provider_instances: | |
| logger.info(f"Creating instance for {name} provider") | |
| self._provider_instances[name] = provider_class() | |
| # Check if provider is available | |
| logger.info(f"Checking availability for {name}") | |
| is_available = self._provider_instances[name].is_available() | |
| logger.info(f"Provider {name} availability: {'β Available' if is_available else 'β Not Available'}") | |
| if is_available: | |
| available.append(name) | |
| except Exception as e: | |
| logger.warning(f"β Failed to check availability of {name} provider: {e}") | |
| logger.info(f"Provider check error details: {type(e).__name__}: {e}") | |
| logger.info(f"π Available TTS providers: {available}") | |
| return available | |
| def create_provider(self, provider_name: str, **kwargs) -> TTSProviderBase: | |
| """ | |
| Create a TTS provider instance. | |
| Args: | |
| provider_name: Name of the provider to create | |
| **kwargs: Additional arguments for provider initialization | |
| Returns: | |
| TTSProviderBase: The created provider instance | |
| Raises: | |
| SpeechSynthesisException: If provider is not available or creation fails | |
| """ | |
| if provider_name not in self._providers: | |
| available = list(self._providers.keys()) | |
| raise SpeechSynthesisException( | |
| f"Unknown TTS provider: {provider_name}. Available providers: {available}" | |
| ) | |
| # Check if provider is actually available before creating | |
| available_providers = self.get_available_providers() | |
| if provider_name not in available_providers: | |
| logger.warning(f"TTS provider {provider_name} is registered but not available") | |
| raise SpeechSynthesisException(f"TTS provider {provider_name} is not available") | |
| try: | |
| provider_class = self._providers[provider_name] | |
| # Create instance with appropriate parameters | |
| lang_code = kwargs.get('lang_code', 'en') | |
| provider = provider_class(lang_code=lang_code) | |
| # Verify the provider is available | |
| if not provider.is_available(): | |
| raise SpeechSynthesisException(f"TTS provider {provider_name} is not available") | |
| logger.info(f"Created TTS provider: {provider_name}") | |
| return provider | |
| except Exception as e: | |
| logger.error(f"Failed to create TTS provider {provider_name}: {e}") | |
| raise SpeechSynthesisException(f"Failed to create TTS provider {provider_name}: {e}") from e | |
| def get_provider_with_fallback(self, preferred_providers: List[str] = None, **kwargs) -> TTSProviderBase: | |
| """ | |
| Get a TTS provider with fallback logic. | |
| Args: | |
| preferred_providers: List of preferred providers in order of preference | |
| **kwargs: Additional arguments for provider initialization | |
| Returns: | |
| TTSProviderBase: The first available provider | |
| Raises: | |
| SpeechSynthesisException: If no providers are available | |
| """ | |
| if preferred_providers is None: | |
| preferred_providers = ['chatterbox'] | |
| logger.info(f"π Getting TTS provider with fallback, preferred order: {preferred_providers}") | |
| available_providers = self.get_available_providers() | |
| # Try preferred providers in order | |
| for provider_name in preferred_providers: | |
| logger.info(f"π Trying preferred provider: {provider_name}") | |
| if provider_name in available_providers: | |
| logger.info(f"β Provider {provider_name} is available, attempting to create...") | |
| try: | |
| provider = self.create_provider(provider_name, **kwargs) | |
| logger.info(f"π Successfully created provider: {provider_name}") | |
| return provider | |
| except Exception as e: | |
| logger.warning(f"β Failed to create preferred provider {provider_name}: {e}") | |
| continue | |
| else: | |
| logger.info(f"β Provider {provider_name} is not in available providers list") | |
| # If no preferred providers work, try any available provider | |
| for provider_name in available_providers: | |
| if provider_name not in preferred_providers: | |
| try: | |
| return self.create_provider(provider_name, **kwargs) | |
| except Exception as e: | |
| logger.warning(f"Failed to create fallback provider {provider_name}: {e}") | |
| continue | |
| raise SpeechSynthesisException("No TTS providers are available") | |
| def get_provider_info(self, provider_name: str) -> Dict: | |
| """ | |
| Get information about a specific provider. | |
| Args: | |
| provider_name: Name of the provider | |
| Returns: | |
| Dict: Provider information including availability and supported features | |
| """ | |
| if provider_name not in self._providers: | |
| return {"available": False, "error": "Provider not registered"} | |
| try: | |
| # Create instance if not cached | |
| if provider_name not in self._provider_instances: | |
| provider_class = self._providers[provider_name] | |
| self._provider_instances[provider_name] = provider_class() | |
| provider = self._provider_instances[provider_name] | |
| return { | |
| "available": provider.is_available(), | |
| "name": provider.provider_name, | |
| "supported_languages": provider.supported_languages, | |
| "available_voices": provider.get_available_voices() if provider.is_available() else [] | |
| } | |
| except Exception as e: | |
| return { | |
| "available": False, | |
| "error": str(e) | |
| } | |
| def cleanup_providers(self): | |
| """Clean up provider instances and resources.""" | |
| for provider in self._provider_instances.values(): | |
| try: | |
| if hasattr(provider, '_cleanup_temp_files'): | |
| provider._cleanup_temp_files() | |
| except Exception as e: | |
| logger.warning(f"Failed to cleanup provider {provider.provider_name}: {e}") | |
| self._provider_instances.clear() | |
| logger.info("Cleaned up TTS provider instances") |