Files
wildminder-ComfyUI-VoxCPM/__init__.py
T

220 lines
7.3 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import os
import sys
import logging
import folder_paths
from .modules.model_info import AVAILABLE_VOXCPM_MODELS, MODEL_CONFIGS
# Late import for dependency check - avoid breaking import when dependencies are missing
try:
from .src.voxcpm.utils.text_normalize import TEXT_NORMALIZATION_AVAILABLE
except Exception:
TEXT_NORMALIZATION_AVAILABLE = False
# Import settings module
from .modules.settings import get_settings
logger = logging.getLogger(__name__)
# Track if config has been sent once
_config_sent_once = False
def send_config_to_client(client_id=None):
"""Send configuration to a specific client or broadcast to all.
Args:
client_id: Optional client ID to send to. If None, broadcasts to all.
"""
global _config_sent_once
try:
from server import PromptServer
if PromptServer.instance is not None:
# Get user settings
settings = get_settings()
config_data = {
"normalization_available": TEXT_NORMALIZATION_AVAILABLE,
"settings": settings.to_dict(),
}
logger.debug(f"Sending config: {config_data} to client: {client_id or 'all'}")
if client_id:
PromptServer.instance.send_sync("voxcpm.config", config_data, client_id)
else:
PromptServer.instance.send_sync("voxcpm.config", config_data)
logger.debug("Config sent successfully")
if not _config_sent_once and not TEXT_NORMALIZATION_AVAILABLE:
_config_sent_once = True
logger.info("ℹ️ Text normalization packages (inflect, wetext) not found. Normalization will be disabled. Install them using: pip install inflect wetext")
except Exception as e:
logger.warning(f"Failed to send config: {e}")
def send_config_event():
"""Send configuration to frontend (legacy function for compatibility)."""
send_config_to_client()
def _schedule_config_send():
"""Schedule config to be sent once after server starts using threading."""
import threading
import time
def send_after_delay():
# Wait for server to be ready
time.sleep(3)
logger.debug("Sending initial config event...")
send_config_event()
logger.debug("Initial config event sent")
thread = threading.Thread(target=send_after_delay, daemon=True)
thread.start()
# Schedule config sender using threading
try:
_schedule_config_send()
except Exception as e:
logger.debug(f"Failed to schedule config sender: {e}")
# Configure logger
logger.setLevel(logging.INFO)
logger.propagate = False
if not logger.hasHandlers():
handler = logging.StreamHandler()
formatter = logging.Formatter(f"[ComfyUI-VoxCPM] %(message)s")
handler.setFormatter(formatter)
logger.addHandler(handler)
current_dir = os.path.dirname(os.path.abspath(__file__))
if current_dir not in sys.path:
sys.path.append(current_dir)
VOXCPM_SUBDIR_NAME = "VoxCPM"
tts_path = os.path.join(folder_paths.models_dir, "tts")
os.makedirs(tts_path, exist_ok=True)
if "tts" not in folder_paths.folder_names_and_paths:
folder_paths.folder_names_and_paths["tts"] = ([tts_path], folder_paths.supported_pt_extensions)
else:
if tts_path not in folder_paths.folder_names_and_paths["tts"][0]:
folder_paths.folder_names_and_paths["tts"][0].append(tts_path)
for model_name, config in MODEL_CONFIGS.items():
AVAILABLE_VOXCPM_MODELS[model_name] = {
"type": "official",
**config
}
voxcpm_search_paths = []
for tts_folder in folder_paths.get_folder_paths("tts"):
potential_path = os.path.join(tts_folder, VOXCPM_SUBDIR_NAME)
if os.path.isdir(potential_path) and potential_path not in voxcpm_search_paths:
voxcpm_search_paths.append(potential_path)
for search_path in voxcpm_search_paths:
if not os.path.isdir(search_path):
continue
for item in os.listdir(search_path):
item_path = os.path.join(search_path, item)
if os.path.isdir(item_path) and item not in AVAILABLE_VOXCPM_MODELS:
config_exists = os.path.exists(os.path.join(item_path, "config.json"))
weights_exist = os.path.exists(os.path.join(item_path, "pytorch_model.bin")) or os.path.exists(os.path.join(item_path, "model.safetensors"))
if config_exists and weights_exist:
AVAILABLE_VOXCPM_MODELS[item] = {
"type": "local",
"path": item_path
}
# ============================================================================
# API Routes for Custom Model Path
# ============================================================================
def scan_custom_model_path(path: str) -> list:
"""Scan a custom path for valid VoxCPM models.
Args:
path: Directory path to scan for models
Returns:
List of model names found in the path
"""
models = []
if not os.path.isdir(path):
logger.warning(f"Custom model path does not exist: {path}")
return models
for item in os.listdir(path):
item_path = os.path.join(path, item)
if os.path.isdir(item_path):
# Check for valid model files
config_exists = os.path.exists(os.path.join(item_path, "config.json"))
weights_exist = (
os.path.exists(os.path.join(item_path, "pytorch_model.bin")) or
os.path.exists(os.path.join(item_path, "model.safetensors"))
)
if config_exists and weights_exist:
models.append(item)
logger.debug(f"Found model: {item} at {item_path}")
return models
# Register API routes
try:
from server import PromptServer
from aiohttp import web
import json
@PromptServer.instance.routes.post("/voxcpm/models")
async def voxcpm_models_handler(request):
"""Handle POST request to scan custom model path.
Expects JSON body: {"path": "/path/to/models"}
Returns: {"models": ["model1", "model2", ...]}
"""
try:
data = await request.json()
path = data.get("path", "")
if not path:
return web.json_response(
{"error": "Path is required"},
status=400
)
# Security: validate path exists and is accessible
if not os.path.isabs(path):
return web.json_response(
{"error": "Path must be absolute"},
status=400
)
models = scan_custom_model_path(path)
return web.json_response({
"path": path,
"models": models,
"count": len(models)
})
except json.JSONDecodeError:
return web.json_response(
{"error": "Invalid JSON"},
status=400
)
except Exception as e:
logger.error(f"Error handling model scan request: {e}")
return web.json_response(
{"error": str(e)},
status=500
)
logger.debug("VoxCPM API routes registered")
except Exception as e:
logger.warning(f"Failed to register API routes: {e}")
from .voxcpm_nodes import comfy_entrypoint
WEB_DIRECTORY = "./js"
__all__ = ['comfy_entrypoint', 'WEB_DIRECTORY']