Files
T

168 lines
5.6 KiB
Python

"""
VoxCPM User Settings Management
Handles persistence of user preferences including:
- Model path preference (default/custom)
- Custom model path
- First-run flag
Uses ComfyUI's folder_paths for proper integration.
"""
import os
import json
import logging
from typing import Optional, Dict, Any
logger = logging.getLogger(__name__)
SETTINGS_FILE = "voxcpm_settings.json"
class VoxCPMSettings:
"""Manages user settings for VoxCPM using ComfyUI's user directory."""
def __init__(self):
self._settings: Dict[str, Any] = {}
self._settings_path: Optional[str] = None
self._load_settings()
def _get_settings_path(self) -> str:
"""Get the path to the settings file in ComfyUI's user directory."""
try:
import folder_paths
user_dir = folder_paths.get_user_directory()
if user_dir:
# Store in user/default/VoxCPM/voxcpm_settings.json
voxcpm_user_dir = os.path.join(user_dir, "default", "VoxCPM")
os.makedirs(voxcpm_user_dir, exist_ok=True)
return os.path.join(voxcpm_user_dir, SETTINGS_FILE)
except ImportError:
pass
# Fallback to custom node directory
return SETTINGS_FILE
def _load_settings(self) -> None:
"""Load settings from disk."""
try:
self._settings_path = self._get_settings_path()
if os.path.exists(self._settings_path):
with open(self._settings_path, "r", encoding="utf-8") as f:
self._settings = json.load(f)
logger.debug(f"Loaded settings from {self._settings_path}")
except Exception as e:
logger.warning(f"Failed to load settings: {e}")
self._settings = {}
def _save_settings(self) -> bool:
"""Save settings to disk."""
try:
if self._settings_path:
with open(self._settings_path, "w", encoding="utf-8") as f:
json.dump(self._settings, f, indent=2)
logger.debug(f"Saved settings to {self._settings_path}")
return True
except Exception as e:
logger.error(f"Failed to save settings: {e}")
return False
@property
def is_first_run(self) -> bool:
"""Check if this is the first run."""
return self._settings.get("first_run", True)
def mark_first_run_complete(self) -> None:
"""Mark first run as complete."""
self._settings["first_run"] = False
self._save_settings()
@property
def use_custom_path(self) -> bool:
"""Check if user wants to use custom path."""
return self._settings.get("use_custom_path", False)
def set_use_custom_path(self, value: bool) -> None:
"""Set custom path preference."""
self._settings["use_custom_path"] = value
self._save_settings()
@property
def custom_model_path(self) -> Optional[str]:
"""Get custom model path."""
return self._settings.get("custom_model_path")
def set_custom_model_path(self, path: str) -> bool:
"""Set custom model path. Returns True if valid."""
if self._validate_model_path(path):
self._settings["custom_model_path"] = path
self._save_settings()
return True
return False
def _validate_model_path(self, path: str) -> bool:
"""Validate that path exists and contains valid model structure."""
if not os.path.isdir(path):
return False
# Check for at least one valid model
try:
for item in os.listdir(path):
item_path = os.path.join(path, item)
if os.path.isdir(item_path):
has_config = os.path.exists(
os.path.join(item_path, "config.json")
)
has_weights = (
os.path.exists(
os.path.join(item_path, "pytorch_model.bin")
)
or os.path.exists(
os.path.join(item_path, "model.safetensors")
)
or os.path.exists(
os.path.join(item_path, "model-00001-of-00001.safetensors")
)
)
if has_config and has_weights:
return True
except OSError:
return False
return False
def get_effective_model_path(self) -> str:
"""Get the effective model path based on settings."""
if self.use_custom_path and self.custom_model_path:
return self.custom_model_path
# Default path
try:
import folder_paths
tts_paths = folder_paths.get_folder_paths("tts")
if tts_paths:
return os.path.join(tts_paths[0], "VoxCPM")
return os.path.join(folder_paths.models_dir, "tts", "VoxCPM")
except ImportError:
return os.path.join("models", "tts", "VoxCPM")
def to_dict(self) -> Dict[str, Any]:
"""Export settings for frontend."""
return {
"first_run": self.is_first_run,
"use_custom_path": self.use_custom_path,
"custom_model_path": self.custom_model_path,
"effective_path": self.get_effective_model_path(),
}
# Global settings instance
_settings_instance: Optional[VoxCPMSettings] = None
def get_settings() -> VoxCPMSettings:
"""Get the global settings instance."""
global _settings_instance
if _settings_instance is None:
_settings_instance = VoxCPMSettings()
return _settings_instance