lenML/ChatTTS-Forge
301
1import logging2 3from fastapi import Depends, HTTPException, Query4from fastapi.responses import FileResponse, StreamingResponse5from pydantic import BaseModel6 7from modules.api import utils as api_utils8from modules.api.Api import APIManager9from modules.api.impl.handler.TTSHandler import TTSHandler10from modules.api.impl.model.audio_model import AdjustConfig, AudioFormat11from modules.api.impl.model.chattts_model import ChatTTSConfig, InferConfig12from modules.api.impl.model.enhancer_model import EnhancerConfig13from modules.speaker import Speaker14 15logger = logging.getLogger(__name__)16 17 18class TTSParams(BaseModel):19 text: str = Query(..., description="Text to synthesize")20 spk: str = Query(21 "female2", description="Specific speaker by speaker name or speaker seed"22 )23 style: str = Query("chat", description="Specific style by style name")24 temperature: float = Query(25 0.3, description="Temperature for sampling (may be overridden by style or spk)"26 )27 top_p: float = Query(28 0.5, description="Top P for sampling (may be overridden by style or spk)"29 )30 top_k: int = Query(31 20, description="Top K for sampling (may be overridden by style or spk)"32 )33 seed: int = Query(34 42, description="Seed for generate (may be overridden by style or spk)"35 )36 format: str = Query("mp3", description="Response audio format: [mp3,wav]")37 prompt1: str = Query("", description="Text prompt for inference")38 prompt2: str = Query("", description="Text prompt for inference")39 prefix: str = Query("", description="Text prefix for inference")40 bs: str = Query("8", description="Batch size for inference")41 thr: str = Query("100", description="Threshold for sentence spliter")42 eos: str = Query("[uv_break]", description="End of sentence str")43 44 enhance: bool = Query(False, description="Enable enhancer")45 denoise: bool = Query(False, description="Enable denoiser")46 47 speed: float = Query(1.0, description="Speed of the audio")48 pitch: float = Query(0, description="Pitch of the audio")49 volume_gain: float = Query(0, description="Volume gain of the audio")50 51 stream: bool = Query(False, description="Stream the audio")52 53 54async def synthesize_tts(params: TTSParams = Depends()):55 try:56 # Validate text57 if not params.text.strip():58 raise HTTPException(59 status_code=422, detail="Text parameter cannot be empty"60 )61 62 # Validate temperature63 if not (0 <= params.temperature <= 1):64 raise HTTPException(65 status_code=422, detail="Temperature must be between 0 and 1"66 )67 68 # Validate top_p69 if not (0 <= params.top_p <= 1):70 raise HTTPException(status_code=422, detail="top_p must be between 0 and 1")71 72 # Validate top_k73 if params.top_k <= 0:74 raise HTTPException(75 status_code=422, detail="top_k must be a positive integer"76 )77 if params.top_k > 100:78 raise HTTPException(79 status_code=422, detail="top_k must be less than or equal to 100"80 )81 82 # Validate format83 if params.format not in ["mp3", "wav"]:84 raise HTTPException(85 status_code=422,86 detail="Invalid format. Supported formats are mp3 and wav",87 )88 89 calc_params = api_utils.calc_spk_style(spk=params.spk, style=params.style)90 91 spk = calc_params.get("spk", params.spk)92 if not isinstance(spk, Speaker):93 raise HTTPException(status_code=422, detail="Invalid speaker")94 95 style = calc_params.get("style", params.style)96 seed = params.seed or calc_params.get("seed", params.seed)97 temperature = params.temperature or calc_params.get(98 "temperature", params.temperature99 )100 prefix = params.prefix or calc_params.get("prefix", params.prefix)101 prompt1 = params.prompt1 or calc_params.get("prompt1", params.prompt1)102 prompt2 = params.prompt2 or calc_params.get("prompt2", params.prompt2)103 eos = params.eos or ""104 105 batch_size = int(params.bs)106 threshold = int(params.thr)107 108 tts_config = ChatTTSConfig(109 style=style,110 temperature=temperature,111 top_k=params.top_k,112 top_p=params.top_p,113 prefix=prefix,114 prompt1=prompt1,115 prompt2=prompt2,116 )117 infer_config = InferConfig(118 batch_size=batch_size,119 spliter_threshold=threshold,120 eos=eos,121 seed=seed,122 )123 adjust_config = AdjustConfig(124 pitch=params.pitch,125 speed_rate=params.speed,126 volume_gain_db=params.volume_gain,127 )128 enhancer_config = EnhancerConfig(129 enabled=params.enhance or params.denoise or False,130 lambd=0.9 if params.denoise else 0.1,131 )132 133 handler = TTSHandler(134 text_content=params.text,135 spk=spk,136 tts_config=tts_config,137 infer_config=infer_config,138 adjust_config=adjust_config,139 enhancer_config=enhancer_config,140 )141 media_type = f"audio/{params.format}"142 if params.format == "mp3":143 media_type = "audio/mpeg"144 145 if params.stream:146 if infer_config.batch_size != 1:147 # 流式生成下仅支持 batch size 为 1,当前请求参数将被忽略148 logger.warning(149 f"Batch size {infer_config.batch_size} is not supported in streaming mode, will set to 1"150 )151 152 buffer_gen = handler.enqueue_to_stream(format=AudioFormat(params.format))153 return StreamingResponse(buffer_gen, media_type=media_type)154 else:155 buffer = handler.enqueue_to_buffer(format=AudioFormat(params.format))156 return StreamingResponse(buffer, media_type=media_type)157 except Exception as e:158 import logging159 160 logging.exception(e)161 162 if isinstance(e, HTTPException):163 raise e164 else:165 raise HTTPException(status_code=500, detail=str(e))166 167 168def setup(api_manager: APIManager):169 api_manager.get("/v1/tts", response_class=FileResponse)(synthesize_tts)170 