from __future__ import annotations from functools import lru_cache from pathlib import Path import yaml from pydantic import Field from pydantic_settings import BaseSettings, SettingsConfigDict ROOT = Path(__file__).resolve().parents[2] class AppSettings(BaseSettings): model_config = SettingsConfigDict( env_file=str(ROOT / ".env"), env_file_encoding="utf-8", extra="ignore", ) tts_model: str = Field(default="cosyvoice", validation_alias="TTS_MODEL") host: str = Field(default="0.0.0.0", validation_alias="TTS_HOST") port: int = Field(default=8000, validation_alias="TTS_PORT") samples_dir: Path = Field(default=ROOT / "samples") outputs_dir: Path = Field(default=ROOT / "outputs" / "api") uploads_dir: Path = Field(default=ROOT / "backend" / "data" / "uploads") default_ref_audio: str | None = Field(default=None, validation_alias="TTS_REF_AUDIO") default_ref_text: str | None = Field(default=None, validation_alias="TTS_REF_TEXT") cosyvoice_model_dir: Path = Field(default=ROOT / "models" / "Fun-CosyVoice3-0.5B") cosyvoice_prompt_prefix: str = ( "You are a helpful assistant.<|endofprompt|>" ) chunk_max_chars: int = 120 @lru_cache def get_settings() -> AppSettings: yaml_path = ROOT / "config" / "settings.yaml" data: dict = {} if yaml_path.is_file(): with open(yaml_path, encoding="utf-8") as f: raw = yaml.safe_load(f) or {} data["tts_model"] = raw.get("default_model", "cosyvoice") gen = raw.get("generation") or {} data["chunk_max_chars"] = gen.get("chunk_max_chars", 120) cv = raw.get("cosyvoice") or {} if cv.get("model_dir"): data["cosyvoice_model_dir"] = ROOT / cv["model_dir"] if cv.get("prompt_prefix"): data["cosyvoice_prompt_prefix"] = cv["prompt_prefix"] srv = raw.get("server") or {} data["host"] = srv.get("host", "0.0.0.0") data["port"] = srv.get("port", 8000) paths = raw.get("paths") or {} if paths.get("samples_dir"): data["samples_dir"] = ROOT / paths["samples_dir"] if paths.get("outputs_dir"): data["outputs_dir"] = ROOT / paths["outputs_dir"] / "api" if paths.get("uploads_dir"): data["uploads_dir"] = ROOT / paths["uploads_dir"] return AppSettings(**{k: v for k, v in data.items() if v is not None}) def project_root() -> Path: return ROOT