Spaces:
Sleeping
Sleeping
| import io | |
| import logging | |
| import os | |
| import secrets | |
| import string | |
| import sys | |
| from json import loads | |
| import torch | |
| import yaml | |
| from typing import List, Union, Optional, Dict, Type | |
| from pydantic import BaseModel, Field, ValidationError, field_validator | |
| from contants import ModelType | |
| JSON_AS_ASCII = False | |
| MAX_CONTENT_LENGTH = 5242880 | |
| # Absolute path of vits-simple-api (current program root path) | |
| BASE_DIR: str = os.path.dirname(os.path.realpath(__file__)) | |
| # Configuration file path | |
| CONFIG_PATH: str = os.path.join(BASE_DIR, "config.yaml") | |
| # WTForms CSRF | |
| SECRET_KEY: str = secrets.token_hex(16) | |
| def update_nested_dict(original, updates): | |
| for key, value in updates.items(): | |
| if isinstance(value, dict) and key in original: | |
| update_nested_dict(original[key], value) | |
| else: | |
| original[key] = value | |
| class VitsConfig(BaseModel): | |
| id: int = 0 | |
| format: str = "wav" | |
| lang: str = "auto" | |
| length: float = 1.0 | |
| noise: float = 0.33 | |
| noisew: float = 0.4 | |
| segment_size: int = 50 | |
| use_streaming: bool = False | |
| class W2V2VitsConfig(BaseModel): | |
| id: int = 0 | |
| format: str = "wav" | |
| lang: str = "auto" | |
| length: float = 1.0 | |
| noise: float = 0.33 | |
| noisew: float = 0.4 | |
| segment_size: int = 50 | |
| emotion: int = 0 | |
| class HuBertVitsConfig(BaseModel): | |
| id: int = 0 | |
| format: str = "wav" | |
| length: float = 1.0 | |
| noise: float = 0.33 | |
| noisew: float = 0.4 | |
| class BertVits2Config(BaseModel): | |
| id: int = 0 | |
| speaker: Optional[str] = None | |
| format: str = "wav" | |
| lang: str = "auto" | |
| length: float = 1.0 | |
| noise: float = 0.33 | |
| noisew: float = 0.4 | |
| segment_size: int = 50 | |
| sdp_ratio: float = 0.2 | |
| emotion: int = 0 | |
| text_prompt: str = "Happy" | |
| style_text: Optional[str] = None | |
| style_weight: float = 0.7 | |
| use_streaming: bool = False | |
| torch_data_type: Optional[str] = None | |
| class GPTSoVitsPreset(BaseModel): | |
| refer_wav_path: Optional[str] = None | |
| prompt_text: Optional[str] = None | |
| prompt_lang: str = "auto" | |
| class GPTSoVitsConfig(BaseModel): | |
| hz: int = 50 | |
| is_half: bool = False | |
| id: int = 0 | |
| lang: str = "auto" | |
| format: str = "wav" | |
| segment_size: int = 30 | |
| top_k: int = 5 | |
| top_p: float = 1.0 | |
| temperature: float = 1.0 | |
| use_streaming: bool = False | |
| batch_size: int = 5 | |
| speed: float = 1.0 | |
| seed: int = -1 | |
| presets: Dict[str, GPTSoVitsPreset] = Field(default_factory=lambda: {"default": GPTSoVitsPreset(), | |
| "default2": GPTSoVitsPreset()}) | |
| class Reader(BaseModel): | |
| model_type: str = "VITS" | |
| id: int = 0 | |
| preset: str = "default" | |
| class ReadingConfig(BaseModel): | |
| interlocutor: Reader = Reader() | |
| narrator: Reader = Reader() | |
| class ResourcePathsConfig(BaseModel): | |
| chinese_roberta_wwm_ext_large: str = "bert/chinese-roberta-wwm-ext-large" | |
| bert_base_japanese_v3: str = "bert/bert-base-japanese-v3" | |
| bert_large_japanese_v2: str = "bert/bert-large-japanese-v2" | |
| deberta_v2_large_japanese: str = "bert/deberta-v2-large-japanese" | |
| deberta_v3_large: str = "bert/deberta-v3-large" | |
| deberta_v2_large_japanese_char_wwm: str = "bert/deberta-v2-large-japanese-char-wwm" | |
| wav2vec2_large_robust_12_ft_emotion_msp_dim: str = "emotional/wav2vec2-large-robust-12-ft-emotion-msp-dim" | |
| clap_htsat_fused: str = "emotional/clap-htsat-fused" | |
| erlangshen_MegatronBert_1_3B_Chinese: str = "bert/Erlangshen-MegatronBert-1.3B-Chinese" | |
| vits_chinese_bert: str = "bert/vits_chinese_bert" | |
| hubert_soft_0d54a1f4: str = "hubert/hubert_soft/hubert-soft-0d54a1f4.pt" | |
| dimensional_emotion_npy: Union[str, List[str]] = "emotional/dimensional_emotion_npy" | |
| dimensional_emotion_model: str = "emotional/dimensional_emotion_model/models.yaml" | |
| g2pw_model: str = "G2PWModel" | |
| chinese_hubert_base: str = "hubert/chinese_hubert_base" | |
| class BaseModelConfig(BaseModel): | |
| model_type: Optional[str] | |
| class Config: | |
| protected_namespaces = () | |
| class VITSModelConfig(BaseModelConfig): | |
| vits_path: str = None | |
| config_path: str = None | |
| dynamic_loading: Optional[bool] = False | |
| class W2V2VITSModelConfig(BaseModelConfig): | |
| vits_path: str = None | |
| config_path: str = None | |
| class HuBertVITSModelConfig(BaseModelConfig): | |
| vits_path: str = None | |
| config_path: str = None | |
| class BertVITS2ModelConfig(BaseModelConfig): | |
| vits_path: str = None | |
| config_path: str = None | |
| class GPTSoVITSModelConfig(BaseModelConfig): | |
| vits_path: str = None | |
| t2s_path: str = None | |
| MODEL_TYPE_MAP: Dict[str, Type[BaseModelConfig]] = { | |
| ModelType.VITS: VITSModelConfig, | |
| ModelType.W2V2_VITS: W2V2VITSModelConfig, | |
| ModelType.HUBERT_VITS: HuBertVITSModelConfig, | |
| ModelType.BERT_VITS2: BertVITS2ModelConfig, | |
| ModelType.GPT_SOVITS: GPTSoVITSModelConfig | |
| } | |
| class TTSModelConfig(BaseModel): | |
| models_dir: str = "models" | |
| auto_load: bool = True | |
| tts_models: List[Union[ | |
| VITSModelConfig, | |
| W2V2VITSModelConfig, | |
| HuBertVITSModelConfig, | |
| BertVITS2ModelConfig, | |
| GPTSoVITSModelConfig, | |
| ]] = Field(default_factory=list) | |
| def recognition_model_type_by_config(self, config: dict) -> str: | |
| symbols = config.get("symbols", None) | |
| emotion_embedding = config["data"].get("emotion_embedding", False) | |
| if "use_spk_conditioned_encoder" in config["model"]: | |
| model_type = ModelType.BERT_VITS2 | |
| return model_type | |
| if symbols != None: | |
| if not emotion_embedding: | |
| mode_type = ModelType.VITS | |
| else: | |
| mode_type = ModelType.W2V2_VITS | |
| else: | |
| mode_type = ModelType.HUBERT_VITS | |
| return mode_type | |
| def infer_model_type(cls, v): | |
| result = [] | |
| for model in v: | |
| if 'model_type' not in model: | |
| if 'vits_path' in model and 'config_path' in model: | |
| with open(model["config_path"], 'r', encoding='utf-8') as f: | |
| data = loads(f.read()) | |
| model['model_type'] = cls.recognition_model_type_by_config(data) | |
| elif 'vits_path' in model and 't2s_path' in model: | |
| model['model_type'] = ModelType.GPT_SOVITS | |
| model_class = MODEL_TYPE_MAP[model['model_type']] | |
| result.append(model_class(**model)) | |
| return result | |
| def add_model(self, model_config: BaseModelConfig): | |
| if not isinstance(model_config, BaseModelConfig): | |
| raise TypeError("model_config must be an instance of BaseModelConfig") | |
| model_class = MODEL_TYPE_MAP.get(model_config.model_type) | |
| if model_class is None: | |
| raise ValueError(f"Unknown model_type: {model_config.model_type}") | |
| self.tts_models.append(model_class(**model_config.model_dump())) | |
| def update_tts_models(self, tts_models: list): | |
| self.tts_models = [] | |
| for item in tts_models: | |
| tts_model = item["tts_model"] | |
| model_type = tts_model.get("model_type") | |
| if model_type: | |
| model_type = model_type.upper().replace("_", "-") | |
| else: | |
| if tts_model.get("t2s_path"): | |
| model_type = ModelType.GPT_SOVITS | |
| else: | |
| with open(tts_model["config_path"], 'r', encoding='utf-8') as f: | |
| data = f.read() | |
| model_type = self.recognition_model_type(loads(data)) | |
| model_class = MODEL_TYPE_MAP.get(ModelType(model_type)) | |
| if model_class is not None: | |
| try: | |
| model_instance = model_class.model_validate(tts_model) | |
| self.tts_models.append(model_instance) | |
| except ValidationError as e: | |
| logging.error(f"Validation error for item {tts_model}: {e}") | |
| else: | |
| logging.error(f"Unknown model_type in data: {model_type}") | |
| class HttpService(BaseModel): | |
| host: str = "0.0.0.0" | |
| port: int = 7860 | |
| debug: bool = False | |
| origins: str = "*" | |
| class LogConfig(BaseModel): | |
| logs_path: str = "logs" | |
| logs_backup_count: int = 30 | |
| logging_level: str = "DEBUG" | |
| class APIKey(BaseModel): | |
| key: str = Field( | |
| default_factory=lambda: ''.join(secrets.choice(string.ascii_letters + string.digits) for _ in range(24))) | |
| enabled: bool = True | |
| class System(BaseModel): | |
| device: str = Field(default_factory=lambda: str( | |
| "cuda" if torch.cuda.is_available() else "mps" if torch.backends.mps.is_available() else "cpu")) | |
| upload_folder: str = "upload" | |
| cache_path: str = "cache" | |
| clean_interval_seconds: int = 3600 | |
| cache_audio: bool = False | |
| api_key_enabled: bool = False | |
| api_keys: List[APIKey] = Field(default_factory=lambda: [APIKey() for _ in range(5)]) | |
| is_admin_enabled: bool = True | |
| admin_route: str = '/admin' | |
| data_path: str = "data" | |
| class LanguageIdentification(BaseModel): | |
| language_identification_library: str = "langid" | |
| espeak_library: Optional[str] = r"C:/Program Files/eSpeak NG/libespeak-ng.dll" if "win" in sys.platform else "" | |
| language_automatic_detect: List[str] = Field(default_factory=list) | |
| split_pattern: str = r'[\!\"\#\$\%\&\'\(\)\*\+\,\-\.\/\:\;\<\>\=\?\@\[\]\{\}\\\\\^\_\`' \ | |
| r'\!?。"#$%&'()*+,-/:;<=>@[\]^_`{|}~⦅⦆「」、、〃》「」' \ | |
| r'『』【】〔〕〖〗〘〙〚〛〜〝〞〟〰〾〿–—‘\'\‛\“\”\„\‟…‧﹏.]+' | |
| class Polyphonic(BaseModel): | |
| dict_path: str = "polyphonic.yaml" | |
| class User(BaseModel): | |
| id: int = 0 | |
| username: str = Field( | |
| default_factory=lambda: ''.join(secrets.choice(string.ascii_letters + string.digits) for _ in range(8))) | |
| password: str = Field( | |
| default_factory=lambda: ''.join(secrets.choice(string.ascii_letters + string.digits) for _ in range(16))) | |
| def is_authenticated(self): | |
| return True | |
| def is_active(self): | |
| return True | |
| def is_anonymous(self): | |
| return False | |
| def get_id(self): | |
| return str(self.id) | |
| class NgrokConfig(BaseModel): | |
| auth_token: Optional[str] = None | |
| class Config(BaseModel): | |
| http_service: HttpService = HttpService() | |
| ngrok_config: NgrokConfig = NgrokConfig() | |
| resource_paths_config: ResourcePathsConfig = ResourcePathsConfig() | |
| tts_model_config: TTSModelConfig = TTSModelConfig() | |
| admin: User = User() | |
| system: System = System() | |
| log_config: LogConfig = LogConfig() | |
| language_identification: LanguageIdentification = LanguageIdentification() | |
| polyphonic: Polyphonic = Polyphonic() | |
| reading_config: ReadingConfig = ReadingConfig() | |
| vits_config: VitsConfig = VitsConfig() | |
| w2v2_vits_config: W2V2VitsConfig = W2V2VitsConfig() | |
| hubert_vits_config: HuBertVitsConfig = HuBertVitsConfig() | |
| bert_vits2_config: BertVits2Config = BertVits2Config() | |
| gpt_sovits_config: GPTSoVitsConfig = GPTSoVitsConfig() | |
| def load_config(file_path: str): | |
| if not os.path.exists(file_path): | |
| config = Config() | |
| save_config_to_yaml(config) | |
| return config | |
| with open(file_path, 'r', encoding='utf-8') as file: | |
| config_data = yaml.safe_load(file) | |
| if config_data: | |
| try: | |
| config = Config(**config_data) | |
| except ValidationError as e: | |
| logging.error(f"Config validation error: {e}") | |
| config = Config() # Load defaults | |
| for error in e.errors(): | |
| field = error['loc'][0] | |
| if field in config.__annotations__: | |
| default_value = getattr(Config, field, None) | |
| if default_value is not None: | |
| # Apply default value | |
| setattr(config, field, default_value) | |
| else: | |
| config = Config() | |
| save_config_to_yaml(config) | |
| return config | |
| def update_config(self, update_data: Dict): | |
| try: | |
| new_config_data = self.model_dump() | |
| update_nested_dict(new_config_data, update_data) | |
| updated_config = Config(**new_config_data) | |
| save_config_to_yaml(updated_config) | |
| self.__dict__.update(updated_config.__dict__) | |
| return self | |
| except ValidationError as e: | |
| logging.error(f"Config validation error: {e}") | |
| return self | |
| def save_config_to_yaml(config: Config): | |
| temp_file = io.StringIO() | |
| yaml.safe_dump(config.model_dump(), temp_file, allow_unicode=True, sort_keys=False) | |
| data = temp_file.getvalue() | |
| temp_file.close() | |
| with open(CONFIG_PATH, 'w', encoding='utf-8') as file: | |
| file.write(data) | |
| config = Config.load_config(CONFIG_PATH) | |