mirror of
https://github.com/denizsafak/abogen.git
synced 2026-07-18 05:40:26 +02:00
163 lines
5.5 KiB
Python
163 lines
5.5 KiB
Python
from __future__ import annotations
|
|
|
|
import json
|
|
import os
|
|
from typing import Any, Dict, List, Optional
|
|
|
|
from abogen.constants import LANGUAGE_DESCRIPTIONS
|
|
from abogen.utils import get_user_config_path
|
|
|
|
_CONFIG_WRAPPER_KEY = "abogen_speaker_configs"
|
|
|
|
|
|
def _config_path() -> str:
|
|
config_path = get_user_config_path()
|
|
config_dir = os.path.dirname(config_path)
|
|
os.makedirs(config_dir, exist_ok=True)
|
|
return os.path.join(config_dir, "speaker_configs.json")
|
|
|
|
|
|
def load_configs() -> Dict[str, Dict[str, Any]]:
|
|
path = _config_path()
|
|
if not os.path.exists(path):
|
|
return {}
|
|
try:
|
|
with open(path, "r", encoding="utf-8") as handle:
|
|
payload = json.load(handle)
|
|
except Exception:
|
|
return {}
|
|
if isinstance(payload, dict) and _CONFIG_WRAPPER_KEY in payload:
|
|
payload = payload[_CONFIG_WRAPPER_KEY]
|
|
if not isinstance(payload, dict):
|
|
return {}
|
|
sanitized: Dict[str, Dict[str, Any]] = {}
|
|
for name, entry in payload.items():
|
|
if not isinstance(name, str) or not isinstance(entry, dict):
|
|
continue
|
|
sanitized[name] = _sanitize_config(entry)
|
|
return sanitized
|
|
|
|
|
|
def save_configs(configs: Dict[str, Dict[str, Any]]) -> None:
|
|
path = _config_path()
|
|
sanitized: Dict[str, Dict[str, Any]] = {}
|
|
for name, entry in configs.items():
|
|
if not isinstance(name, str) or not name.strip():
|
|
continue
|
|
sanitized[name] = _sanitize_config(entry)
|
|
with open(path, "w", encoding="utf-8") as handle:
|
|
json.dump({_CONFIG_WRAPPER_KEY: sanitized}, handle, indent=2, sort_keys=True)
|
|
|
|
|
|
def get_config(name: str) -> Optional[Dict[str, Any]]:
|
|
name = (name or "").strip()
|
|
if not name:
|
|
return None
|
|
configs = load_configs()
|
|
data = configs.get(name)
|
|
return dict(data) if isinstance(data, dict) else None
|
|
|
|
|
|
def upsert_config(name: str, payload: Dict[str, Any]) -> Dict[str, Any]:
|
|
name = (name or "").strip()
|
|
if not name:
|
|
raise ValueError("Configuration name is required")
|
|
configs = load_configs()
|
|
configs[name] = _sanitize_config(payload or {})
|
|
save_configs(configs)
|
|
return configs[name]
|
|
|
|
|
|
def delete_config(name: str) -> None:
|
|
name = (name or "").strip()
|
|
if not name:
|
|
return
|
|
configs = load_configs()
|
|
if name in configs:
|
|
del configs[name]
|
|
save_configs(configs)
|
|
|
|
|
|
def _sanitize_config(entry: Dict[str, Any]) -> Dict[str, Any]:
|
|
language = str(entry.get("language") or "a").strip() or "a"
|
|
speakers_raw = entry.get("speakers")
|
|
if not isinstance(speakers_raw, dict):
|
|
speakers_raw = {}
|
|
speakers: Dict[str, Any] = {}
|
|
for speaker_id, payload in speakers_raw.items():
|
|
if not isinstance(speaker_id, str) or not isinstance(payload, dict):
|
|
continue
|
|
record = _sanitize_speaker({"id": speaker_id, **payload})
|
|
speakers[record["id"]] = record
|
|
allowed_languages = entry.get("languages") or entry.get("allowed_languages") or []
|
|
if not isinstance(allowed_languages, list):
|
|
allowed_languages = []
|
|
normalized_langs = []
|
|
for code in allowed_languages:
|
|
if isinstance(code, str) and code:
|
|
normalized_langs.append(code.lower())
|
|
default_voice = entry.get("default_voice")
|
|
if not isinstance(default_voice, str):
|
|
default_voice = ""
|
|
return {
|
|
"language": language.lower(),
|
|
"languages": normalized_langs,
|
|
"default_voice": default_voice,
|
|
"speakers": speakers,
|
|
"version": int(entry.get("version", 1)),
|
|
"notes": entry.get("notes") if isinstance(entry.get("notes"), str) else "",
|
|
}
|
|
|
|
|
|
def slugify_label(label: str) -> str:
|
|
normalized = (label or "").strip().lower()
|
|
if not normalized:
|
|
return "speaker"
|
|
slug = "".join(ch if ch.isalnum() else "_" for ch in normalized)
|
|
slug = "_".join(filter(None, slug.split("_")))
|
|
return slug or "speaker"
|
|
|
|
|
|
def _sanitize_speaker(entry: Dict[str, Any]) -> Dict[str, Any]:
|
|
label = (entry.get("label") or entry.get("name") or "").strip()
|
|
gender = (entry.get("gender") or "unknown").strip().lower()
|
|
if gender not in {"male", "female", "unknown"}:
|
|
gender = "unknown"
|
|
voice = entry.get("voice")
|
|
voice_profile = entry.get("voice_profile")
|
|
voice_formula = entry.get("voice_formula")
|
|
voice_languages = entry.get("languages") or []
|
|
if not isinstance(voice_languages, list):
|
|
voice_languages = []
|
|
normalized_langs = []
|
|
for code in voice_languages:
|
|
if isinstance(code, str) and code:
|
|
normalized_langs.append(code.lower())
|
|
resolved_voice = entry.get("resolved_voice") or voice_formula or voice
|
|
resolved_label = label or entry.get("id") or ""
|
|
slug = entry.get("id") if isinstance(entry.get("id"), str) else slugify_label(resolved_label)
|
|
return {
|
|
"id": slug,
|
|
"label": resolved_label,
|
|
"gender": gender,
|
|
"voice": voice if isinstance(voice, str) else "",
|
|
"voice_profile": voice_profile if isinstance(voice_profile, str) else "",
|
|
"voice_formula": voice_formula if isinstance(voice_formula, str) else "",
|
|
"resolved_voice": resolved_voice if isinstance(resolved_voice, str) else "",
|
|
"languages": normalized_langs,
|
|
}
|
|
|
|
|
|
def list_configs() -> List[Dict[str, Any]]:
|
|
configs = load_configs()
|
|
ordered = []
|
|
for name in sorted(configs):
|
|
entry = configs[name]
|
|
ordered.append({"name": name, **entry})
|
|
return ordered
|
|
|
|
|
|
def describe_language(code: str) -> str:
|
|
code = (code or "a").lower()
|
|
return LANGUAGE_DESCRIPTIONS.get(code, code.upper())
|