import os, json, sys from uuid import uuid4 from typing import Literal import urllib.parse import hashlib from pydantic import BaseModel, Field, model_validator from typing import Literal, List, Optional, Dict, Any, Union from uuid import uuid4 import hashlib def convert_value_type(value: Any, type_: str): if value is None: return None if isinstance(value, str): value = urllib.parse.unquote(value) if type(value).__name__ == type_: # 如果值的类型和参数的类型一致,直接返回值 return value if type_ == "int": return int(value) elif type_ == "float": if isinstance(value, str) and value[-1] == "%": return float(value[:-1]) / 100 else: return float(value) elif type_ == "bool": if isinstance(value, bool): return value return str(value).lower() in ("true", "1", "t", "y", "yes", "allow", "allowed") else: # 默认为字符串 return str(value) class ParamItem(BaseModel): """ Represents a parameter item for a TTS task. Attributes: type (str): The data type of the parameter. default (Any): The default value of the parameter. alias (List[str]): The list of aliases for the parameter. label (Optional[str]): The label for the parameter. name (Optional[str]): The name of the parameter. description (Optional[str]): The description of the parameter. min_value (Optional[float]): The minimum value of the parameter. max_value (Optional[float]): The maximum value of the parameter. step (Optional[float]): The step value for the parameter. choices (Optional[List[str]]): The list of choices for the parameter. """ type: str component_type: Optional[str] = None default: Any alias: List[str] label: Optional[str] = None name: Optional[str] description: Optional[str] = None min_value: Optional[float] = None max_value: Optional[float] = None step: Optional[float] = None choices: Optional[List[str]] = None def __init__(self, **data): if not data.get("type"): data.update({"type": "str"}) super().__init__(**data) self.default = convert_value_type(self.default, self.type) def init_params_config(res: dict): result = {} for key, value in res.items(): if value.get("label") is None: value.update({"label": key}) value.update({"name": key}) result[key] = ParamItem(**value) return result class Base_TTS_Task(BaseModel): """ Base class for TTS (Text-to-Speech) tasks. Attributes: uuid (str): Unique identifier for the task. params_config (Dict[str, ParamItem]): Configuration parameters for the task. task_type (Literal["text", "ssml", "audio"]): Type of the task. Can be "text", "ssml", or "audio". audio_path (Optional[str]): Path to the audio file. src (Optional[str]): Source url. ssml (Optional[str]): SSML (Speech Synthesis Markup Language) text. text (Optional[str]): Text content. format (Optional[str]): Audio format. sample_rate (Optional[int]): Sample rate of the audio. loudness (Optional[float]): Loudness of the audio. speed (Optional[float]): Speed of the audio. stream (Optional[bool]): Flag indicating if the audio should be streamed. save_temp (Optional[bool]): Flag indicating if the temporary files should be saved. disabled_features (Optional[List[str]]): List of disabled features. """ uuid: str = None params_config: Dict[str, ParamItem] task_type: Literal["text", "ssml", "audio"] = Field(default="text") audio_path: Optional[str] = None src: Optional[str] = None ssml: Optional[str] = None text: Optional[str] = None character: Optional[str] = None emotion: Optional[str] = None format: Optional[str] = None sample_rate: Optional[int] = None loudness: Optional[float] = None speed: Optional[float] = None stream: Optional[bool] = None save_temp: Optional[bool] = False disabled_features: Optional[List[str]] = None class Config: populate_by_name = True extra = "ignore" def __init__(self, other_task: Union[BaseModel, dict, None] = None, **data): if isinstance(other_task, BaseModel): # 如果 task 是 Base_TTS_Task 实例,从该实例复制属性 data = other_task.model_dump() super().__init__(**data) else: # 如果 task 是字典,直接使用这个字典 if isinstance(other_task, dict): data = other_task assert "params_config" in data, "params_config is not defined." super().__init__(params_config=data.get("params_config")) self.set_default_values() self.set_values(**data) self.uuid = str(uuid4()) def update_value(self, key: str, value: Any, allow_none: bool = False): if not allow_none and value is None: return assert self.params_config is not None, "params_config is not defined." for param_key, param_value in self.params_config.items(): if key in param_value.alias: if hasattr(self, param_key): value = convert_value_type(value, param_value.type) setattr(self, param_key, value) else: pass # raise ValueError(f"Attribute {param_key} not found. Something went wrong in params_config.json.") def set_values(self, **data): assert self.params_config is not None, "params_config is not defined." for key, value in data.items(): if hasattr(self, key): value = convert_value_type(value, type(getattr(self, key)).__name__) setattr(self, key, value) else: self.update_value(key, value) def set_default_values(self): assert self.params_config is not None, "params_config is not defined." for key, value in self.params_config.items(): if ( hasattr(self, key) and getattr(self, key) is None and value.default is not None ): setattr(self, key, value.default) @property def md5(self): m = hashlib.md5() if self.task_type == "text": m.update(self.text.encode()) elif self.task_type == "ssml": m.update(self.ssml.encode()) elif self.task_type == "audio": m.update(self.src.encode()) return m.hexdigest() def __str__(self): dict_content: dict = self.model_dump(exclude={"params_config"}) # 收集所有值为None的键 keys_to_remove = [key for key, value in dict_content.items() if value is None] # 弹出这些键 for key in keys_to_remove: dict_content.pop(key) return json.dumps(dict_content, indent=4, ensure_ascii=False) def copy(self, update: Dict[str, Any] = {}, deep: bool = False): update["uuid"] = str(uuid4()) return super().model_copy(update=update, deep=deep)