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()