File size: 10,877 Bytes
025ca3f 851495c a28f7e3 025ca3f a28f7e3 025ca3f a28f7e3 025ca3f a28f7e3 efefd3d a28f7e3 efefd3d a28f7e3 025ca3f |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 |
import typing
from abc import ABC, abstractmethod
from enum import Enum
from pathlib import Path
from pydantic import BaseModel
from voice_dialogue.utils.logger import logger
class TTSConfigType(Enum):
"""TTS引擎类型枚举"""
MOYOYO = 'moyoyo'
KOKORO = 'kokoro'
class VoiceModelStatus(Enum):
"""声音模型状态枚举"""
NOT_DOWNLOADED = 'not_downloaded'
DOWNLOADING = 'downloading'
DOWNLOADED = 'downloaded'
FAILED = 'failed'
class BaseTTSConfig(BaseModel, ABC):
"""TTS配置基类"""
tts_type: TTSConfigType
character_name: str
cover_image: str
description: str
file_size: str
is_chinese_voice: bool
@abstractmethod
def get_model_storage_path(self) -> Path:
"""获取模型存储路径"""
pass
@abstractmethod
def is_model_complete(self) -> bool:
"""检查模型文件是否完整"""
pass
@abstractmethod
def download_model(self, progress_callback: typing.Callable = None):
"""下载模型"""
pass
@abstractmethod
def delete_model(self):
"""删除模型"""
pass
class TTSConfigRegistry:
"""TTS注册表,管理所有TTS引擎和配置"""
def __init__(self):
self._configs: dict[str, BaseTTSConfig] = {}
self._priority_order = {
TTSConfigType.KOKORO: 1,
TTSConfigType.MOYOYO: 2,
}
def register_config(self, config: BaseTTSConfig):
"""注册TTS配置"""
key = f"{config.tts_type.value}:{config.character_name}"
self._configs[key] = config
def get_config(self, tts_type: TTSConfigType, character_name: str) -> BaseTTSConfig:
"""获取指定配置"""
key = f"{tts_type.value}:{character_name}"
return self._configs.get(key)
def get_configs_by_type(self, tts_type: TTSConfigType) -> list[BaseTTSConfig]:
"""获取指定类型的所有配置"""
return [config for config in self._configs.values()
if config.tts_type == tts_type]
def get_all_configs(self) -> list[BaseTTSConfig]:
"""获取所有配置"""
return list(self._configs.values())
def get_default_config(self, user_language: typing.Optional[typing.Literal['zh', 'en']] = None) -> typing.Optional[
BaseTTSConfig]:
"""
获取默认的TTS配置
选择逻辑:
1. 根据用户语言偏好选择对应的语音类型(中文/非中文)
2. 优先选择已下载完整的模型
3. 按照预定义的优先级顺序选择TTS类型
4. 在同类型中优先选择匹配语言的语音
5. 如果都没有完整模型,返回优先级最高且语言匹配的配置
Args:
user_language: 用户语言偏好,'zh'为中文,'en'为英文,None则自动检测系统语言
Returns:
BaseTTSConfig: 默认配置,如果没有任何配置则返回None
"""
try:
# 如果没有指定用户语言,则自动检测系统语言
if user_language is None:
try:
from utils.system import get_system_language
user_language = get_system_language()
logger.info(f"自动检测到系统语言: {user_language}")
except ImportError:
logger.warning("无法导入系统语言检测模块,使用默认语言 'zh'")
user_language = 'zh'
except Exception as e:
logger.warning(f"系统语言检测失败: {e},使用默认语言 'zh'")
user_language = 'zh'
all_configs = self.get_all_configs()
if not all_configs:
logger.warning("没有找到任何TTS配置")
return None
# 确定语音偏好:中文系统偏好中文语音,非中文系统偏好非中文语音
prefer_chinese_voice = (user_language == 'zh')
logger.info(f"用户语言: {user_language}, 语音偏好: {'中文语音' if prefer_chinese_voice else '非中文语音'}")
# 首先尝试找到已完整下载且语言匹配的配置
complete_configs = [config for config in all_configs if config.is_model_complete()]
if complete_configs:
# 按语言偏好和优先级排序已完整的配置
selected_config = self._select_config_by_priority_and_language(complete_configs, prefer_chinese_voice)
logger.info(
f"选择已完整的默认TTS配置: {selected_config.tts_type.value}:{selected_config.character_name} "
f"(语音类型: {'中文' if selected_config.is_chinese_voice else '非中文'})")
return selected_config
# 如果没有完整的配置,选择优先级最高且语言匹配的配置
logger.warning("没有找到完整下载的TTS模型,选择优先级最高且语言匹配的配置")
fallback_config = self._select_config_by_priority_and_language(all_configs, prefer_chinese_voice)
logger.info(f"使用备选默认TTS配置: {fallback_config.tts_type.value}:{fallback_config.character_name} "
f"(语音类型: {'中文' if fallback_config.is_chinese_voice else '非中文'})")
return fallback_config
except Exception as e:
logger.error(f"获取默认TTS配置时发生错误: {e}", exc_info=True)
return None
def _select_config_by_priority_and_language(
self,
configs: list[BaseTTSConfig],
prefer_chinese_voice: bool
) -> BaseTTSConfig:
"""
按优先级和语言偏好选择配置
Args:
configs: 配置列表
prefer_chinese_voice: 是否偏好中文语音
Returns:
BaseTTSConfig: 选中的配置
"""
if not configs:
raise ValueError("配置列表不能为空")
# 按优先级和语言偏好排序
def sort_key(config: BaseTTSConfig):
# 优先级权重(数字越小优先级越高)
tts_priority = self._priority_order.get(config.tts_type, 999)
character_priority = -config.priority
# 语言匹配加分
# 如果偏好中文语音且配置是中文语音,或者偏好非中文语音且配置是非中文语音,则加分
language_match = (prefer_chinese_voice == config.is_chinese_voice)
language_bonus = 0 if language_match else 1
# 角色名称作为最后的排序条件
return (language_bonus, tts_priority, character_priority, config.character_name)
sorted_configs = sorted(configs, key=sort_key)
return sorted_configs[0]
def get_recommended_configs(self, max_count: int = 3,
user_language: typing.Optional[typing.Literal['zh', 'en']] = None) -> list[
BaseTTSConfig]:
"""
获取推荐的TTS配置列表
Args:
max_count: 最大返回数量
user_language: 用户语言偏好,'zh'为中文,'en'为英文,None则自动检测系统语言
Returns:
list[BaseTTSConfig]: 推荐配置列表
"""
try:
# 如果没有指定用户语言,则自动检测系统语言
if user_language is None:
try:
from utils.system import get_system_language
user_language = get_system_language()
except (ImportError, Exception):
user_language = 'zh'
all_configs = self.get_all_configs()
if not all_configs:
return []
prefer_chinese_voice = (user_language == 'zh')
# 优先返回已完整下载的配置
complete_configs = [config for config in all_configs if config.is_model_complete()]
if complete_configs:
sorted_configs = sorted(complete_configs,
key=lambda c: (self._priority_order.get(c.tts_type, 999),
0 if (prefer_chinese_voice == c.is_chinese_voice) else 1,
c.character_name))
return sorted_configs[:max_count]
# 如果没有完整配置,返回按优先级和语言偏好排序的配置
sorted_configs = sorted(all_configs,
key=lambda c: (self._priority_order.get(c.tts_type, 999),
0 if (prefer_chinese_voice == c.is_chinese_voice) else 1,
c.character_name))
return sorted_configs[:max_count]
except Exception as e:
logger.error(f"获取推荐TTS配置时发生错误: {e}", exc_info=True)
return []
def get_default_config_for_system(self) -> typing.Optional[BaseTTSConfig]:
"""
为系统首次启动获取默认TTS配置
专门用于系统首次启动时的场景,会自动检测系统语言并选择最合适的默认配置
Returns:
BaseTTSConfig: 系统默认配置
"""
try:
from utils.system import get_system_language
system_language = get_system_language()
logger.info(f"系统首次启动,检测到系统语言: {system_language}")
default_config = self.get_default_config(user_language=system_language)
if default_config:
logger.info(
f"为系统首次启动选择默认TTS配置: {default_config.tts_type.value}:{default_config.character_name}")
# 记录配置详情,方便调试
logger.debug(f"默认配置详情: 语音类型={'中文' if default_config.is_chinese_voice else '非中文'}, "
f"模型完整性={'完整' if default_config.is_model_complete() else '未完整'}")
else:
logger.error("无法为系统首次启动选择默认TTS配置")
return default_config
except ImportError:
logger.warning("无法导入系统语言检测模块,使用中文作为默认语言")
return self.get_default_config(user_language='zh')
except Exception as e:
logger.error(f"为系统首次启动获取默认配置时发生错误: {e}", exc_info=True)
return self.get_default_config(user_language='zh')
# 全局TTS注册表实例
tts_config_registry = TTSConfigRegistry()
|