168 lines
5.6 KiB
Python
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
|