Modified /api/tts-audio-suite/available-characters to return both character filenames and their aliases from the character alias map. This allows the tag editor dropdown to show friendly alias names like 'Alice' alongside actual voice files.
229 lines
9.9 KiB
Python
229 lines
9.9 KiB
Python
"""
|
||
TTS Audio Suite - Universal multi-engine TTS extension for ComfyUI
|
||
Unified architecture supporting ChatterBox, F5-TTS, and future engines like RVC:
|
||
• 🎤 TTS Text (unified text-to-speech)
|
||
• 📺 TTS SRT (unified SRT subtitle timing)
|
||
• 🔄 Voice Changer (unified voice conversion)
|
||
• ⚙️ Engine nodes (ChatterBox, F5-TTS)
|
||
• 🎭 Character Voices (voice reference management)
|
||
"""
|
||
|
||
# Import from the main nodes.py file which handles the new unified architecture
|
||
import importlib.util
|
||
import os
|
||
import sys
|
||
|
||
# Smart Numba Compatibility System - tests and applies fixes only when needed
|
||
try:
|
||
from utils.compatibility import setup_numba_compatibility
|
||
# Apply smart compatibility setup (fast startup test)
|
||
compatibility_results = setup_numba_compatibility(quick_startup=True, verbose=True)
|
||
except ImportError:
|
||
# Fallback to simple approach if compatibility module not found
|
||
import sys
|
||
import os
|
||
if sys.version_info >= (3, 13):
|
||
# Basic test: try librosa.stft and apply workaround if it fails
|
||
try:
|
||
import numpy as np
|
||
import librosa
|
||
test_audio = np.random.randn(512).astype(np.float32)
|
||
_ = librosa.stft(test_audio, hop_length=256, n_fft=512)
|
||
# Only show when there's a problem, not success
|
||
# Mark that we've tested numba compatibility
|
||
import sys
|
||
sys.modules['__main__']._tts_numba_tested = True
|
||
except Exception as e:
|
||
if "'function' object has no attribute 'get_call_template'" in str(e):
|
||
os.environ['NUMBA_DISABLE_JIT'] = '1'
|
||
os.environ['NUMBA_ENABLE_CUDASIM'] = '1'
|
||
try:
|
||
import numba
|
||
numba.config.DISABLE_JIT = True
|
||
except ImportError:
|
||
pass
|
||
print("🔧 Applied numba JIT workaround for Python 3.13 compatibility")
|
||
else:
|
||
print(f"⚠️ Librosa test failed with different error: {e}")
|
||
else:
|
||
# Only show warning when JIT is disabled (indicates a problem)
|
||
pass
|
||
|
||
# Apply ComfyUI compatibility patches
|
||
try:
|
||
from utils.comfyui_compatibility import apply_all_compatibility_patches
|
||
apply_all_compatibility_patches()
|
||
except ImportError:
|
||
pass
|
||
|
||
# Suppress torchaudio 2.9+ TorchCodec migration warnings (informational only, no action needed)
|
||
import warnings
|
||
import sys
|
||
import os
|
||
|
||
warnings.filterwarnings("ignore", message="In 2.9, this function's implementation will be changed to use torchaudio.load_with_torchcodec", category=UserWarning)
|
||
warnings.filterwarnings("ignore", message="In 2.9, this function's implementation will be changed to use torchaudio.save_with_torchcodec", category=UserWarning)
|
||
|
||
# Version disclosure for troubleshooting
|
||
def print_critical_versions():
|
||
"""Print versions of critical packages for troubleshooting"""
|
||
critical_packages = [
|
||
('numpy', 'NumPy'),
|
||
('librosa', 'Librosa'),
|
||
('numba', 'Numba'),
|
||
('torch', 'PyTorch'),
|
||
('torchaudio', 'TorchAudio'),
|
||
('transformers', 'Transformers'),
|
||
('accelerate', 'Accelerate'),
|
||
('soundfile', 'SoundFile'),
|
||
]
|
||
|
||
version_info = []
|
||
for pkg_name, display_name in critical_packages:
|
||
try:
|
||
module = __import__(pkg_name)
|
||
version = getattr(module, '__version__', 'unknown')
|
||
version_info.append(f"{display_name} {version}")
|
||
except ImportError:
|
||
version_info.append(f"{display_name} not installed")
|
||
|
||
print(f"ℹ️ Critical package versions: {', '.join(version_info)}")
|
||
|
||
def check_ffmpeg_availability():
|
||
"""Check ffmpeg availability and log status"""
|
||
try:
|
||
from utils.ffmpeg_utils import FFmpegUtils
|
||
if FFmpegUtils.is_available():
|
||
# Only show when unavailable (problem)
|
||
pass
|
||
else:
|
||
print("⚠️ FFmpeg not found - using fallback audio processing (reduced quality)")
|
||
print("💡 Install FFmpeg for optimal performance: https://ffmpeg.org/download.html")
|
||
except ImportError:
|
||
# Fallback check if utils not available yet
|
||
try:
|
||
import subprocess
|
||
result = subprocess.run(['ffmpeg', '-version'], capture_output=True, timeout=5)
|
||
if result.returncode == 0:
|
||
# Only show when unavailable (problem)
|
||
pass
|
||
else:
|
||
print("⚠️ FFmpeg not found - using fallback audio processing (reduced quality)")
|
||
except Exception:
|
||
print("⚠️ FFmpeg not found - using fallback audio processing (reduced quality)")
|
||
print("💡 Install FFmpeg for optimal performance: https://ffmpeg.org/download.html")
|
||
|
||
# Print versions and check dependencies immediately for troubleshooting
|
||
print_critical_versions()
|
||
check_ffmpeg_availability()
|
||
|
||
# Check for old ChatterBox extension conflict
|
||
def check_old_extension_conflict():
|
||
"""Check if the old ComfyUI_ChatterBox_SRT_Voice extension is installed"""
|
||
try:
|
||
import folder_paths
|
||
custom_nodes_path = folder_paths.get_folder_paths("custom_nodes")[0]
|
||
old_extension_path = os.path.join(custom_nodes_path, "ComfyUI_ChatterBox_SRT_Voice")
|
||
|
||
if os.path.exists(old_extension_path):
|
||
print("\n" + "="*80)
|
||
print("⚠️ EXTENSION CONFLICT DETECTED ⚠️")
|
||
print("="*80)
|
||
print("❌ OLD EXTENSION FOUND: ComfyUI_ChatterBox_SRT_Voice")
|
||
print("🆕 CURRENT EXTENSION: ComfyUI_TTS_Audio_Suite")
|
||
print("")
|
||
print("The old 'ComfyUI_ChatterBox_SRT_Voice' extension conflicts with this")
|
||
print("new 'ComfyUI_TTS_Audio_Suite' extension and MUST be removed.")
|
||
print("")
|
||
print("REQUIRED ACTION:")
|
||
print(f"1. Delete the old extension folder: {old_extension_path}")
|
||
print("2. Restart ComfyUI")
|
||
print("")
|
||
print("The TTS Audio Suite is the evolved version with:")
|
||
print("• Unified architecture supporting multiple TTS engines")
|
||
print("• Better performance and stability")
|
||
print("• All features from the old extension plus new capabilities")
|
||
print("")
|
||
print("Your workflows will be compatible - just update node names.")
|
||
print("="*80)
|
||
print("")
|
||
return True
|
||
except Exception as e:
|
||
# Silently continue if we can't check (e.g., folder_paths not available yet)
|
||
pass
|
||
return False
|
||
|
||
# Perform conflict check
|
||
OLD_EXTENSION_CONFLICT = check_old_extension_conflict()
|
||
|
||
# Get the path to the nodes.py file
|
||
nodes_py_path = os.path.join(os.path.dirname(__file__), "nodes.py")
|
||
|
||
# Load nodes.py as a module
|
||
spec = importlib.util.spec_from_file_location("nodes_main", nodes_py_path)
|
||
nodes_module = importlib.util.module_from_spec(spec)
|
||
spec.loader.exec_module(nodes_module)
|
||
|
||
# Import constants and utilities
|
||
IS_DEV = nodes_module.IS_DEV
|
||
VERSION = nodes_module.VERSION
|
||
SEPARATOR = nodes_module.SEPARATOR
|
||
VERSION_DISPLAY = nodes_module.VERSION_DISPLAY
|
||
|
||
# The new unified architecture handles all node registration in nodes.py
|
||
# Just import the mappings that nodes.py creates
|
||
NODE_CLASS_MAPPINGS = nodes_module.NODE_CLASS_MAPPINGS
|
||
NODE_DISPLAY_NAME_MAPPINGS = nodes_module.NODE_DISPLAY_NAME_MAPPINGS
|
||
|
||
# Extension info
|
||
__version__ = VERSION_DISPLAY
|
||
__author__ = "TTS Audio Suite"
|
||
__description__ = "Universal multi-engine TTS extension for ComfyUI with unified architecture supporting ChatterBox, F5-TTS, and future engines like RVC"
|
||
|
||
__all__ = ["NODE_CLASS_MAPPINGS", "NODE_DISPLAY_NAME_MAPPINGS"]
|
||
|
||
# Define web directory for JavaScript files
|
||
WEB_DIRECTORY = "./web"
|
||
|
||
# Register API endpoint for widget data
|
||
def setup_api_routes():
|
||
"""Setup API routes for widget communication"""
|
||
try:
|
||
import json
|
||
from server import PromptServer
|
||
from aiohttp import web
|
||
|
||
@PromptServer.instance.routes.get("/api/tts-audio-suite/available-characters")
|
||
async def get_available_characters_endpoint(request):
|
||
"""API endpoint to get available TTS character voices including aliases"""
|
||
try:
|
||
from utils.voice.discovery import get_available_characters, voice_discovery
|
||
characters = list(get_available_characters())
|
||
# Also get character aliases
|
||
aliases = list(voice_discovery._character_aliases.keys()) if hasattr(voice_discovery, '_character_aliases') else []
|
||
# Combine and deduplicate
|
||
all_chars = sorted(set(characters + aliases))
|
||
return web.json_response({"characters": all_chars})
|
||
except Exception as e:
|
||
print(f"⚠️ Error retrieving available characters: {e}")
|
||
return web.json_response({"characters": [], "error": str(e)})
|
||
|
||
@PromptServer.instance.routes.get("/api/tts-audio-suite/available-languages")
|
||
async def get_available_languages_endpoint(request):
|
||
"""API endpoint to get available language codes from the canonical language mapper"""
|
||
try:
|
||
from utils.models.language_mapper import LANGUAGE_ALIASES
|
||
# Get all unique canonical language codes (the values in LANGUAGE_ALIASES)
|
||
languages = sorted(set(LANGUAGE_ALIASES.values()))
|
||
return web.json_response({"languages": languages})
|
||
except Exception as e:
|
||
print(f"⚠️ Error retrieving available languages: {e}")
|
||
# Fallback list
|
||
return web.json_response({"languages": ["en", "de", "fr", "ja", "es", "it", "pt", "th", "no"], "error": str(e)})
|
||
except Exception as e:
|
||
print(f"⚠️ Could not setup API routes: {e}")
|
||
|
||
# Setup API routes when extension loads
|
||
setup_api_routes()
|
||
|
||
# nodes.py already handles all the startup output and status reporting |