Files
diodiogod-TTS-Audio-Suite/core/model_manager.py
T
Claude Code Assistant cd3c7718f7 feat: complete Audio Analyzer interactive waveform visualization
- Fix waveform data display with real audio (30s duration vs fake 10.79s)
- Implement working cache system using web directory for JS-Python data transfer
- Add green region overlays showing detected silence/speech segments
- Fix RMS line zoom/pan compatibility with different data structures
- Consolidate UI layout with single-row button arrangement
- Reduce container height to fit properly within ComfyUI nodes
- Clean up debug logging for better console output
- Implement robust error handling for visualization rendering

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-10 19:24:52 -03:00

331 lines
13 KiB
Python

"""
Model Manager - Centralized model loading and caching for ChatterBox Voice
Handles model discovery, loading, and caching across different sources
"""
import os
import warnings
import torch
import folder_paths
from typing import Optional, List, Tuple, Dict, Any
# Import management with fallbacks
try:
from chatterbox.tts import ChatterboxTTS
from chatterbox.vc import ChatterboxVC
CHATTERBOX_TTS_AVAILABLE = True
CHATTERBOX_VC_AVAILABLE = True
USING_BUNDLED_CHATTERBOX = True
except ImportError:
try:
from chatterbox.tts import ChatterboxTTS
from chatterbox.vc import ChatterboxVC
CHATTERBOX_TTS_AVAILABLE = True
CHATTERBOX_VC_AVAILABLE = True
USING_BUNDLED_CHATTERBOX = False
except ImportError:
CHATTERBOX_TTS_AVAILABLE = False
CHATTERBOX_VC_AVAILABLE = False
USING_BUNDLED_CHATTERBOX = False
class ModelManager:
"""
Centralized model loading and caching manager for ChatterBox Voice.
Handles model discovery, loading from different sources, and caching.
"""
# Class-level cache for shared model instances
_model_cache: Dict[str, Any] = {}
_model_sources: Dict[str, str] = {}
def __init__(self, node_dir: Optional[str] = None):
"""
Initialize ModelManager with optional node directory override.
Args:
node_dir: Optional override for the node directory path
"""
self.node_dir = node_dir or os.path.dirname(os.path.dirname(__file__))
self.bundled_chatterbox_dir = os.path.join(self.node_dir, "chatterbox")
self.bundled_models_dir = os.path.join(self.node_dir, "models", "chatterbox")
# Instance-level model references
self.tts_model: Optional[Any] = None
self.vc_model: Optional[Any] = None
self.current_device: Optional[str] = None
def find_chatterbox_models(self) -> List[Tuple[str, Optional[str]]]:
"""
Find ChatterBox model files in order of priority.
Returns:
List of tuples containing (source_type, path) in priority order:
- bundled: Models bundled with the extension
- comfyui: Models in ComfyUI models directory
- huggingface: Download from Hugging Face (path is None)
"""
model_paths = []
# 1. Check for bundled models in node folder
bundled_model_path = os.path.join(self.bundled_models_dir, "s3gen.pt")
if os.path.exists(bundled_model_path):
model_paths.append(("bundled", self.bundled_models_dir))
return model_paths # Return immediately if bundled models found
# 2. Check ComfyUI models folder - standard location
comfyui_model_path_standard = os.path.join(folder_paths.models_dir, "chatterbox", "s3gen.pt")
if os.path.exists(comfyui_model_path_standard):
model_paths.append(("comfyui", os.path.dirname(comfyui_model_path_standard)))
return model_paths
# 3. Check legacy location (TTS/chatterbox) for backward compatibility
comfyui_model_path_legacy = os.path.join(folder_paths.models_dir, "TTS", "chatterbox", "s3gen.pt")
if os.path.exists(comfyui_model_path_legacy):
model_paths.append(("comfyui", os.path.dirname(comfyui_model_path_legacy)))
return model_paths
# 4. HuggingFace download as fallback
model_paths.append(("huggingface", None))
return model_paths
def get_model_cache_key(self, model_type: str, device: str, source: str, path: Optional[str] = None) -> str:
"""
Generate a cache key for model instances.
Args:
model_type: Type of model ('tts' or 'vc')
device: Target device ('cuda', 'cpu')
source: Model source ('bundled', 'comfyui', 'huggingface')
path: Optional path for local models
Returns:
Cache key string
"""
path_component = path or "default"
return f"{model_type}_{device}_{source}_{path_component}"
def load_tts_model(self, device: str = "auto", force_reload: bool = False) -> Any:
"""
Load ChatterboxTTS model with caching.
Args:
device: Target device ('auto', 'cuda', 'cpu')
force_reload: Force reload even if cached
Returns:
ChatterboxTTS model instance
Raises:
ImportError: If ChatterboxTTS is not available
RuntimeError: If model loading fails
"""
if not CHATTERBOX_TTS_AVAILABLE:
raise ImportError("ChatterboxTTS not available - check installation or add bundled version")
# Resolve auto device
if device == "auto":
device = "cuda" if torch.cuda.is_available() else "cpu"
# Check if we need to load/reload
if not force_reload and self.tts_model is not None and self.current_device == device:
return self.tts_model
# Get available model paths
model_paths = self.find_chatterbox_models()
model_loaded = False
last_error = None
for source, path in model_paths:
try:
cache_key = self.get_model_cache_key("tts", device, source, path)
# Check class-level cache first
if not force_reload and cache_key in self._model_cache:
self.tts_model = self._model_cache[cache_key]
self.current_device = device
self._model_sources[cache_key] = source
model_loaded = True
break
# Load model based on source
if source in ["bundled", "comfyui"]:
with warnings.catch_warnings():
warnings.simplefilter("ignore")
model = ChatterboxTTS.from_local(path, device)
elif source == "huggingface":
model = ChatterboxTTS.from_pretrained(device)
else:
continue
# Cache the loaded model
self._model_cache[cache_key] = model
self._model_sources[cache_key] = source
self.tts_model = model
self.current_device = device
model_loaded = True
break
except Exception as e:
print(f"⚠️ Failed to load TTS model from {source}: {str(e)}")
last_error = e
continue
if not model_loaded:
error_msg = f"Failed to load ChatterboxTTS from any source"
if last_error:
error_msg += f". Last error: {last_error}"
raise RuntimeError(error_msg)
return self.tts_model
def load_vc_model(self, device: str = "auto", force_reload: bool = False) -> Any:
"""
Load ChatterboxVC model with caching.
Args:
device: Target device ('auto', 'cuda', 'cpu')
force_reload: Force reload even if cached
Returns:
ChatterboxVC model instance
Raises:
ImportError: If ChatterboxVC is not available
RuntimeError: If model loading fails
"""
if not CHATTERBOX_VC_AVAILABLE:
raise ImportError("ChatterboxVC not available - check installation or add bundled version")
# Resolve auto device
if device == "auto":
device = "cuda" if torch.cuda.is_available() else "cpu"
# Check if we need to load/reload
if not force_reload and self.vc_model is not None and self.current_device == device:
return self.vc_model
# Get available model paths
model_paths = self.find_chatterbox_models()
model_loaded = False
last_error = None
for source, path in model_paths:
try:
cache_key = self.get_model_cache_key("vc", device, source, path)
# Check class-level cache first
if not force_reload and cache_key in self._model_cache:
self.vc_model = self._model_cache[cache_key]
self.current_device = device
self._model_sources[cache_key] = source
model_loaded = True
break
# Load model based on source
if source in ["bundled", "comfyui"]:
with warnings.catch_warnings():
warnings.simplefilter("ignore")
model = ChatterboxVC.from_local(path, device)
elif source == "huggingface":
model = ChatterboxVC.from_pretrained(device)
else:
continue
# Cache the loaded model
self._model_cache[cache_key] = model
self._model_sources[cache_key] = source
self.vc_model = model
self.current_device = device
model_loaded = True
break
except Exception as e:
last_error = e
continue
if not model_loaded:
error_msg = f"Failed to load ChatterboxVC from any source"
if last_error:
error_msg += f". Last error: {last_error}"
raise RuntimeError(error_msg)
return self.vc_model
def get_model_source(self, model_type: str) -> Optional[str]:
"""
Get the source of the currently loaded model.
Args:
model_type: Type of model ('tts' or 'vc')
Returns:
Model source string or None if no model loaded
"""
if model_type == "tts" and self.tts_model is not None:
device = self.current_device or "cpu"
model_paths = self.find_chatterbox_models()
if model_paths:
source, path = model_paths[0]
cache_key = self.get_model_cache_key("tts", device, source, path)
return self._model_sources.get(cache_key)
elif model_type == "vc" and self.vc_model is not None:
device = self.current_device or "cpu"
model_paths = self.find_chatterbox_models()
if model_paths:
source, path = model_paths[0]
cache_key = self.get_model_cache_key("vc", device, source, path)
return self._model_sources.get(cache_key)
return None
def clear_cache(self, model_type: Optional[str] = None):
"""
Clear model cache.
Args:
model_type: Optional model type to clear ('tts', 'vc'), or None for all
"""
if model_type is None:
# Clear all
self._model_cache.clear()
self._model_sources.clear()
self.tts_model = None
self.vc_model = None
self.current_device = None
elif model_type == "tts":
# Clear TTS models
keys_to_remove = [k for k in self._model_cache.keys() if k.startswith("tts_")]
for key in keys_to_remove:
self._model_cache.pop(key, None)
self._model_sources.pop(key, None)
self.tts_model = None
elif model_type == "vc":
# Clear VC models
keys_to_remove = [k for k in self._model_cache.keys() if k.startswith("vc_")]
for key in keys_to_remove:
self._model_cache.pop(key, None)
self._model_sources.pop(key, None)
self.vc_model = None
@property
def is_available(self) -> Dict[str, bool]:
"""
Check availability of ChatterBox components.
Returns:
Dictionary with availability status
"""
return {
"tts": CHATTERBOX_TTS_AVAILABLE,
"vc": CHATTERBOX_VC_AVAILABLE,
"bundled": USING_BUNDLED_CHATTERBOX,
"any": CHATTERBOX_TTS_AVAILABLE or CHATTERBOX_VC_AVAILABLE
}
# Global model manager instance
model_manager = ModelManager()