├── .gitignore ├── Dockerfile ├── README.md ├── api_examples.md ├── config ├── config.yaml └── config.yaml.example ├── docker-compose.yml ├── docker ├── .env.prod └── .env.prod.example ├── main.py ├── requirements.txt └── src ├── api.py ├── core └── tts_manager.py ├── main.py ├── platforms ├── acgn_ttson.py ├── fish_audio.py └── ttson.py └── utils ├── config.py └── http_client.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Python 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | *.so 6 | .Python 7 | build/ 8 | develop-eggs/ 9 | dist/ 10 | downloads/ 11 | eggs/ 12 | .eggs/ 13 | lib/ 14 | lib64/ 15 | parts/ 16 | sdist/ 17 | var/ 18 | wheels/ 19 | *.egg-info/ 20 | .installed.cfg 21 | *.egg 22 | 23 | # Virtual Environment 24 | venv/ 25 | env/ 26 | ENV/ 27 | .env 28 | .venv 29 | 30 | # IDE 31 | .idea/ 32 | .vscode/ 33 | *.swp 34 | *.swo 35 | .project 36 | .pydevproject 37 | .settings/ 38 | 39 | # Logs 40 | *.log 41 | logs/ 42 | log/ 43 | 44 | # Chrome Driver 45 | *.exe 46 | chromedriver* 47 | 48 | # Local development 49 | .DS_Store 50 | Thumbs.db 51 | .env.local 52 | .env.development.local 53 | .env.test.local 54 | .env.production.local 55 | 56 | # Project specific 57 | config.local.py 58 | debug/ 59 | temp/ -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.10.13-slim 2 | 3 | WORKDIR /app 4 | 5 | COPY . . 6 | 7 | RUN apt update \ 8 | && python3 -m pip install -r ./requirements.txt \ 9 | && touch /.dockerenv 10 | 11 | # 设置环境变量文件路径 12 | ENV ENV_FILE=/app/docker/.env.prod 13 | 14 | # 使用环境变量文件启动应用 15 | CMD ["sh", "-c", "set -a && . ${ENV_FILE} && set +a && python3 main.py"] 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # One-TTS 2 | 3 | 一个用于将多个TTS平台聚合管理调用的后端Python项目。 4 | 5 | ## 安装 6 | 7 | 1. 克隆项目: 8 | ```bash 9 | git clone https://github.com/the-lazy-me/One-TTS.git 10 | cd One-TTS 11 | ``` 12 | 13 | 2. 安装依赖: 14 | ```bash 15 | pip install -r requirements.txt 16 | ``` 17 | 18 | ## 使用方法 19 | 20 | 1. 配置文件`config/config.yaml` 21 | 22 | ```yaml 23 | # API服务配置 24 | api_server: 25 | host: "0.0.0.0" 26 | port: 5555 27 | base_url: "http://localhost:5555" 28 | debug: false # 默认关闭debug模式 29 | 30 | # TTS平台配置 31 | platforms: 32 | # 海豚Ai配音,地址:https://www.ttson.cn/ 33 | ttson: 34 | base_url: "https://ht.ttson.cn:37284" 35 | token: "your_token_here" 36 | network_proxies: 37 | http: null 38 | https: null 39 | 40 | # TTS-Online,实际也是海豚Ai的,地址:https://acgn.ttson.cn/ 41 | acgn_ttson: 42 | base_url: "https://ht.ttson.cn:37284" 43 | token: "your_token_here" 44 | network_proxies: 45 | http: null 46 | https: null 47 | 48 | # Fish Audio,地址:https://fish.audio/ 49 | fish_audio: 50 | base_url: "https://api.fish.audio" 51 | token: "your_token_here" 52 | network_proxies: 53 | http: "http://127.0.0.1:7890" 54 | https: "http://127.0.0.1:7890" 55 | ``` 56 | 57 | 2. 运行示例程序: 58 | ```bash 59 | python main.py 60 | ``` 61 | 62 | 63 | ## 支持的平台 64 | 65 | - [x] 支持[海豚配音 TTS Online 二次元音色](https://www.ttson.cn/?source=thelazy) 66 | - [x] 支持[海豚Ai配音](https://www.ttson.cn/?source=thelazy) 67 | - [x] 支持[Fish Audio](https://fish.audio/zh-CN/discovery/) 68 | - [ ] 支持[ChatTTS](https://github.com/2noise/ChatTTS) 69 | - [ ] 支持[GPT-SoVITS](https://github.com/RVC-Boss/GPT-SoVITS) 70 | - [ ] 支持[Kokoro TTS](https://kokorotts.net/zh) 71 | 72 | ## 配置说明 73 | 74 | 1. 复制示例配置文件: 75 | -------------------------------------------------------------------------------- /api_examples.md: -------------------------------------------------------------------------------- 1 | ### 获取平台列表 2 | 3 | ```bash 4 | curl -X GET "{BASE_URL}/platforms" 5 | ``` 6 | 7 | 此调用将返回支持的平台列表。 8 | 9 | ### 获取指定平台的语音角色 10 | 11 | ```bash 12 | curl -X POST "{BASE_URL}/characters" \ 13 | -H "Content-Type: application/json" \ 14 | -d '{ 15 | "platform": "{platform}", 16 | "token": "{token}" 17 | }' 18 | ``` 19 | 20 | 此调用将返回指定平台的可用角色。请将 `{platform}` 和 `{token}` 替换为实际值。 21 | 22 | ### 获取情感列表(仅TTSON平台支持) 23 | 24 | ```bash 25 | curl -X GET "{BASE_URL}/emotions" \ 26 | -G --data-urlencode "platform={platform}" 27 | ``` 28 | 29 | 此调用将返回指定平台的情感列表。请将 `{platform}` 替换为实际值。 30 | 31 | ### 文本转语音 32 | 33 | ```bash 34 | curl -X POST "{BASE_URL}/tts" \ 35 | -H "Content-Type: application/json" \ 36 | -d '{ 37 | "platform": "{platform}", 38 | "text": "这是一个测试文本", 39 | "character": "{character}", 40 | "token": "{token}", 41 | "options": {options} 42 | }' 43 | ``` -------------------------------------------------------------------------------- /config/config.yaml: -------------------------------------------------------------------------------- 1 | # API服务配置 2 | api_server: 3 | host: "0.0.0.0" 4 | port: 5555 5 | base_url: "http://localhost:5555" 6 | debug: false # 默认关闭debug模式 7 | 8 | # TTS平台配置 9 | platforms: 10 | # 海豚Ai配音,地址:https://www.ttson.cn/ 11 | ttson: 12 | base_url: "https://u95167-bd74-2aef8085.westx.seetacloud.com:8443" 13 | token: "your_token_here" 14 | network_proxies: 15 | http: null 16 | https: null 17 | 18 | # TTS-Online,实际也是海豚Ai的,地址:https://acgn.ttson.cn/ 19 | acgn_ttson: 20 | base_url: "https://u95167-bd74-2aef8085.westx.seetacloud.com:8443" 21 | token: "your_token_here" 22 | network_proxies: 23 | http: null 24 | https: null 25 | 26 | # Fish Audio,地址:https://fish.audio/ 27 | fish_audio: 28 | base_url: "https://api.fish.audio" 29 | token: "your_token_here" 30 | network_proxies: 31 | http: "http://127.0.0.1:7890" 32 | https: "http://127.0.0.1:7890" 33 | -------------------------------------------------------------------------------- /config/config.yaml.example: -------------------------------------------------------------------------------- 1 | # API服务配置 2 | api_server: 3 | host: "0.0.0.0" 4 | port: 5555 5 | base_url: "http://localhost:5555" 6 | debug: false # 默认关闭debug模式 7 | 8 | # TTS平台配置 9 | platforms: 10 | # 海豚Ai配音,地址:https://www.ttson.cn/ 11 | ttson: 12 | base_url: "https://u95167-bd74-2aef8085.westx.seetacloud.com:8443" 13 | token: "your_token_here" # 在实际配置文件中替换为你的token 14 | network_proxies: 15 | http: null 16 | https: null 17 | 18 | # TTS-Online配置 19 | acgn_ttson: 20 | base_url: "https://u95167-bd74-2aef8085.westx.seetacloud.com:8443" 21 | token: "your_token_here" # 在实际配置文件中替换为你的token 22 | network_proxies: 23 | http: null 24 | https: null 25 | 26 | # Fish Audio配置 27 | fish_audio: 28 | base_url: "https://api.fish.audio" 29 | token: "your_token_here" # 在实际配置文件中替换为你的token 30 | network_proxies: 31 | http: "http://127.0.0.1:7890" 32 | https: "http://127.0.0.1:7890" -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | 3 | services: 4 | one-tts-api: 5 | build: . 6 | ports: 7 | - "${API_PORT:-5555}:5555" 8 | volumes: 9 | - ./docker/.env.prod:/app/docker/.env.prod 10 | restart: unless-stopped -------------------------------------------------------------------------------- /docker/.env.prod: -------------------------------------------------------------------------------- 1 | # API服务配置 2 | API_HOST=0.0.0.0 3 | API_PORT=5555 4 | API_BASE_URL=http://localhost:5555 5 | API_DEBUG=false 6 | 7 | # 海豚Ai配音配置 8 | TTSON_BASE_URL=https://u95167-bd74-2aef8085.westx.seetacloud.com:8443 9 | TTSON_TOKEN=your_token_here 10 | TTSON_HTTP_PROXY= 11 | TTSON_HTTPS_PROXY= 12 | 13 | # TTS-Online配置 14 | ACGN_TTSON_BASE_URL=https://u95167-bd74-2aef8085.westx.seetacloud.com:8443 15 | ACGN_TTSON_TOKEN=your_token_here 16 | ACGN_TTSON_HTTP_PROXY= 17 | ACGN_TTSON_HTTPS_PROXY= 18 | 19 | # Fish Audio配置 20 | FISH_AUDIO_BASE_URL=https://api.fish.audio 21 | FISH_AUDIO_TOKEN=your_token_here 22 | FISH_AUDIO_HTTP_PROXY=http://127.0.0.1:7890 23 | FISH_AUDIO_HTTPS_PROXY=http://127.0.0.1:7890 -------------------------------------------------------------------------------- /docker/.env.prod.example: -------------------------------------------------------------------------------- 1 | # API服务配置 2 | API_HOST=0.0.0.0 3 | API_PORT=5555 4 | API_BASE_URL=http://localhost:5555 5 | API_DEBUG=false 6 | 7 | # 海豚Ai配音配置 8 | TTSON_BASE_URL=https://u95167-bd74-2aef8085.westx.seetacloud.com:8443 9 | TTSON_TOKEN=your_token_here 10 | TTSON_HTTP_PROXY= 11 | TTSON_HTTPS_PROXY= 12 | 13 | # TTS-Online配置 14 | ACGN_TTSON_BASE_URL=https://u95167-bd74-2aef8085.westx.seetacloud.com:8443 15 | ACGN_TTSON_TOKEN=your_token_here 16 | ACGN_TTSON_HTTP_PROXY= 17 | ACGN_TTSON_HTTPS_PROXY= 18 | 19 | # Fish Audio配置 20 | FISH_AUDIO_BASE_URL=https://api.fish.audio 21 | FISH_AUDIO_TOKEN=your_token_here 22 | FISH_AUDIO_HTTP_PROXY=http://127.0.0.1:7890 23 | FISH_AUDIO_HTTPS_PROXY=http://127.0.0.1:7890 -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | import uvicorn 2 | from src.utils.config import config 3 | import argparse 4 | import os 5 | 6 | if __name__ == "__main__": 7 | # 添加命令行参数解析 8 | parser = argparse.ArgumentParser(description='One-TTS API Server') 9 | parser.add_argument('--debug', action='store_true', help='启用debug模式') 10 | args = parser.parse_args() 11 | 12 | # 优先使用环境变量,如果没有则使用配置文件 13 | host = os.getenv('API_HOST', config["api_server"]["host"]) 14 | port = int(os.getenv('API_PORT', config["api_server"]["port"])) 15 | debug_mode = args.debug or \ 16 | os.getenv('API_DEBUG', '').lower() == 'true' or \ 17 | config["api_server"].get("debug", False) 18 | 19 | # 运行服务器 20 | uvicorn.run( 21 | "src.api:app", 22 | host=host, 23 | port=port, 24 | reload=debug_mode, 25 | log_level="debug" if debug_mode else "info" 26 | ) -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/the-lazy-me/One-TTS/35bc60d9aadca54046f0f1b68df955e33dc963f8/requirements.txt -------------------------------------------------------------------------------- /src/api.py: -------------------------------------------------------------------------------- 1 | from fastapi import FastAPI, HTTPException, Query 2 | from fastapi.responses import StreamingResponse 3 | from src.core.tts_manager import TTSManager, TTSPlatform 4 | from src.platforms.acgn_ttson import ACGNTtson 5 | from src.platforms.ttson import Ttson 6 | from src.platforms.fish_audio import FishAudio 7 | from typing import Dict, Any 8 | from pydantic import BaseModel 9 | import io 10 | import time 11 | from src.utils.config import config 12 | 13 | # 平台映射字典 14 | PLATFORM_MAP = { 15 | "ttson": Ttson, 16 | "acgn_ttson": ACGNTtson, 17 | "fish_audio": FishAudio 18 | } 19 | 20 | app = FastAPI(title="One-TTS API") 21 | 22 | # 初始化TTS管理器 23 | tts_manager = TTSManager() 24 | 25 | # 从配置文件获取base_url并注册平台 26 | acgn_platform = ACGNTtson(config["platforms"]["acgn_ttson"]["base_url"]) 27 | tts_manager.register_platform("acgn_ttson", acgn_platform) 28 | 29 | ttson_platform = Ttson(config["platforms"]["ttson"]["base_url"]) 30 | tts_manager.register_platform("ttson", ttson_platform) 31 | 32 | fish_audio_platform = FishAudio( 33 | base_url=config["platforms"]["fish_audio"]["base_url"], 34 | token=config["platforms"]["fish_audio"]["token"], 35 | network_proxies=config["platforms"]["fish_audio"]["network_proxies"] 36 | ) 37 | tts_manager.register_platform("fish_audio", fish_audio_platform) 38 | 39 | # 请求模型定义 40 | class CharacterRequest(BaseModel): 41 | platform: str 42 | token: str = None # token变为可选参数 43 | 44 | class TTSRequest(BaseModel): 45 | platform: str 46 | text: str 47 | character: str 48 | token: str = None # token变为可选参数 49 | options: Dict[str, Any] = {} 50 | 51 | @app.get("/platforms") 52 | def list_platforms(): 53 | """获取所有支持的平台列表""" 54 | return {"platforms": tts_manager.list_platforms()} 55 | 56 | @app.post("/characters") 57 | def get_characters(request: CharacterRequest): 58 | """获取指定平台的所有可用语音角色""" 59 | platform = tts_manager.get_platform(request.platform) 60 | if not platform: 61 | raise HTTPException(status_code=404, detail="Platform not found") 62 | 63 | try: 64 | # 优先使用请求中的token,否则使用配置文件中的token 65 | token = request.token or config["platforms"][request.platform]["token"] 66 | platform.set_token(token) 67 | return {"characters": platform.get_characters()} 68 | except Exception as e: 69 | raise HTTPException(status_code=500, detail=str(e)) 70 | 71 | @app.get("/emotions") 72 | def get_emotions(platform: str = Query(..., description="Platform name")): 73 | """获取指定平台的所有可用情感(仅TTSON平台支持)""" 74 | if platform != "ttson": 75 | raise HTTPException(status_code=400, detail="Only TTSON platform supports emotions") 76 | 77 | platform_instance = tts_manager.get_platform(platform) 78 | if not platform_instance: 79 | raise HTTPException(status_code=404, detail="Platform not found") 80 | 81 | return {"emotions": platform_instance.get_emotions()} 82 | 83 | @app.post("/tts") 84 | async def text_to_speech(request: TTSRequest): 85 | """统一的文本转语音接口""" 86 | platform = tts_manager.get_platform(request.platform) 87 | if not platform: 88 | raise HTTPException(status_code=404, detail="Platform not found") 89 | 90 | try: 91 | # 优先使用请求中的token,否则使用配置文件中的token 92 | token = request.token or config["platforms"][request.platform]["token"] 93 | platform.set_token(token) 94 | 95 | # 根据不同平台处理character参数 96 | voice_id = request.character 97 | if request.platform in ["ttson", "acgn_ttson"]: 98 | voice_id = int(request.character) 99 | 100 | audio_data = platform.generate( 101 | text=request.text, 102 | voice_id=voice_id, 103 | **request.options 104 | ) 105 | 106 | filename = f"{request.platform}_tts_{int(time.time())}.mp3" 107 | return StreamingResponse( 108 | io.BytesIO(audio_data), 109 | media_type="audio/mpeg", 110 | headers={"Content-Disposition": f"attachment; filename={filename}"} 111 | ) 112 | except ValueError as e: 113 | raise HTTPException(status_code=400, detail=str(e)) 114 | except Exception as e: 115 | raise HTTPException(status_code=500, detail=str(e)) 116 | 117 | def init_platform(name: str, config: Dict) -> TTSPlatform: 118 | """初始化指定的TTS平台""" 119 | platform_class = PLATFORM_MAP.get(name) 120 | if not platform_class: 121 | raise ValueError(f"Unsupported platform: {name}") 122 | 123 | return platform_class( 124 | base_url=config['base_url'], 125 | token=config.get('token'), 126 | network_proxies=config.get('network_proxies') 127 | ) -------------------------------------------------------------------------------- /src/core/tts_manager.py: -------------------------------------------------------------------------------- 1 | from abc import ABC, abstractmethod 2 | from typing import List, Dict, Optional, BinaryIO 3 | 4 | class TTSPlatform(ABC): 5 | """TTS平台的基础抽象类""" 6 | 7 | @abstractmethod 8 | def get_characters(self) -> List[Dict]: 9 | """获取所有可用的语音角色""" 10 | pass 11 | 12 | @abstractmethod 13 | def generate(self, text: str, voice_id: int, **kwargs) -> bytes: 14 | """生成语音文件""" 15 | pass 16 | 17 | class TTSManager: 18 | """TTS平台管理器""" 19 | _instance = None 20 | 21 | def __new__(cls): 22 | if cls._instance is None: 23 | cls._instance = super(TTSManager, cls).__new__(cls) 24 | cls._instance._platforms = {} 25 | return cls._instance 26 | 27 | def register_platform(self, name: str, platform: TTSPlatform): 28 | """注册TTS平台""" 29 | self._platforms[name] = platform 30 | 31 | def get_platform(self, name: str) -> Optional[TTSPlatform]: 32 | """获取指定的TTS平台""" 33 | return self._platforms.get(name) 34 | 35 | def list_platforms(self) -> List[str]: 36 | """列出所有已注册的平台""" 37 | return list(self._platforms.keys()) -------------------------------------------------------------------------------- /src/main.py: -------------------------------------------------------------------------------- 1 | import uvicorn 2 | import argparse 3 | 4 | if __name__ == "__main__": 5 | # 添加命令行参数解析 6 | parser = argparse.ArgumentParser(description='One-TTS API Server') 7 | parser.add_argument('--debug', action='store_true', help='启用debug模式') 8 | args = parser.parse_args() 9 | 10 | # 运行服务器 11 | uvicorn.run( 12 | "api:app", # 使用导入字符串 13 | host="0.0.0.0", 14 | port=8000, 15 | reload=args.debug, # debug模式下启用热重载 16 | log_level="debug" if args.debug else "info" 17 | ) -------------------------------------------------------------------------------- /src/platforms/acgn_ttson.py: -------------------------------------------------------------------------------- 1 | from typing import List, Dict, BinaryIO 2 | from src.core.tts_manager import TTSPlatform 3 | from src.utils.http_client import HTTPClient 4 | 5 | class ACGNTtson(TTSPlatform): 6 | """ACGN TTSON平台实现""" 7 | 8 | def __init__(self, base_url: str, token: str = None): 9 | self.base_url = base_url.rstrip('/') 10 | self.token = token 11 | self.http_client = HTTPClient() 12 | 13 | def set_token(self, token: str): 14 | """设置token""" 15 | self.token = token 16 | 17 | def get_characters(self) -> List[Dict]: 18 | """获取所有可用的语音角色""" 19 | url = f"{self.base_url}/flashsummary/voices" 20 | params = { 21 | 'language': 'zh-CN', 22 | 'tag_id': 1 23 | } 24 | headers = { 25 | 'Pragma': 'no-cache' 26 | } 27 | 28 | response = self.http_client.get(url, params=params, headers=headers) 29 | return response.json() 30 | 31 | def generate(self, text: str, voice_id: int, **kwargs) -> bytes: 32 | """生成语音文件 33 | 34 | Args: 35 | text: 要转换的文本 36 | voice_id: 语音角色ID 37 | **kwargs: 其他参数,包括: 38 | - to_lang: 目标语言,默认'ZH' 39 | - auto_translate: 是否自动翻译,默认0 40 | - voice_speed: 语音速度,默认'0%' 41 | - speed_factor: 速度因子,默认1 42 | - pitch_factor: 音调因子,默认0 43 | - rate: 语速率,默认'1.0' 44 | - emotion: 情感,默认1 45 | 46 | Returns: 47 | bytes: 音频文件的二进制数据 48 | 49 | Raises: 50 | ValueError: 当token未设置时抛出 51 | """ 52 | # 首先生成语音 53 | result = self.generate_speech(text, voice_id, **kwargs) 54 | 55 | # 然后下载音频 56 | return self.download_audio(result) 57 | 58 | def generate_speech(self, text: str, voice_id: int, **kwargs) -> Dict: 59 | """生成语音(内部使用)""" 60 | if not self.token: 61 | raise ValueError("Token is required for generating speech") 62 | 63 | url = f"{self.base_url}/flashsummary/tts" 64 | 65 | payload = { 66 | "voice_id": voice_id, 67 | "text": text, 68 | "format": "wav", 69 | "to_lang": kwargs.get('to_lang', 'auto'), 70 | "auto_translate": kwargs.get('auto_translate', 0), 71 | "voice_speed": kwargs.get('voice_speed', '0%'), 72 | "speed_factor": kwargs.get('speed_factor', 1), 73 | "pitch_factor": kwargs.get('pitch_factor', 0), 74 | "rate": kwargs.get('rate', '1.0'), 75 | "client_ip": "ACGN", 76 | "emotion": kwargs.get('emotion', 1) 77 | } 78 | 79 | headers = { 80 | 'Content-Type': 'application/json' 81 | } 82 | 83 | params = {'token': self.token} 84 | 85 | response = self.http_client.post( 86 | url=url, 87 | json=payload, 88 | headers=headers, 89 | params=params 90 | ) 91 | 92 | return response.json() 93 | 94 | def download_audio(self, result: Dict) -> bytes: 95 | """下载生成的音频文件(内部使用)""" 96 | if not self.token: 97 | raise ValueError("Token is required for downloading audio") 98 | 99 | # 从结果中构建完整的URL 100 | base_url = f"{result['url']}:{result['port']}" 101 | url = f"{base_url}/flashsummary/retrieveFileData" 102 | 103 | # 设置请求参数 104 | params = { 105 | 'stream': True, 106 | 'token': self.token, 107 | 'voice_audio_path': result['voice_path'] 108 | } 109 | 110 | # 设置请求头 111 | headers = { 112 | 'Pragma': 'no-cache', 113 | 'Range': 'bytes=0-' 114 | } 115 | 116 | # 发送请求并返回响应内容 117 | response = self.http_client.get(url, params=params, headers=headers) 118 | return response.content # 直接返回二进制内容 -------------------------------------------------------------------------------- /src/platforms/fish_audio.py: -------------------------------------------------------------------------------- 1 | from typing import List, Dict 2 | from src.core.tts_manager import TTSPlatform 3 | from src.utils.http_client import HTTPClient 4 | import ormsgpack # 需要安装:pip install ormsgpack 5 | 6 | class FishAudio(TTSPlatform): 7 | """Fish Audio平台实现""" 8 | 9 | def __init__(self, base_url: str = "https://api.fish.audio", token: str = None, network_proxies: Dict = None): 10 | self.base_url = base_url.rstrip('/') 11 | self.token = token 12 | self.http_client = HTTPClient(proxies=network_proxies) 13 | 14 | def set_token(self, token: str): 15 | """设置token""" 16 | self.token = token 17 | 18 | def get_characters(self) -> List[Dict]: 19 | """获取所有可用的语音角色""" 20 | if not self.token: 21 | raise ValueError("Token is required for getting characters") 22 | 23 | url = f"{self.base_url}/v1/voices" 24 | headers = { 25 | 'Authorization': f'Bearer {self.token}' 26 | } 27 | 28 | response = self.http_client.get(url, headers=headers) 29 | return response.json()['data'] 30 | 31 | def generate(self, text: str, voice_id: str, **kwargs) -> bytes: 32 | """生成语音文件""" 33 | if not self.token: 34 | raise ValueError("Token is required for generating speech") 35 | 36 | url = f"{self.base_url}/v1/tts" 37 | 38 | # 构建请求数据 39 | request_data = { 40 | "text": text, 41 | "reference_id": voice_id, 42 | "chunk_length": kwargs.get("chunk_length", 200), 43 | "format": kwargs.get("format", "mp3"), 44 | "mp3_bitrate": kwargs.get("mp3_bitrate", 128), 45 | "normalize": kwargs.get("normalize", True), 46 | "latency": kwargs.get("latency", "normal"), 47 | "references": [] # 暂不支持参考音频 48 | } 49 | 50 | headers = { 51 | 'Authorization': f'Bearer {self.token}', 52 | 'Content-Type': 'application/msgpack' 53 | } 54 | 55 | # 使用 msgpack 序列化请求数据 56 | payload = ormsgpack.packb( 57 | request_data, 58 | option=ormsgpack.OPT_SERIALIZE_PYDANTIC 59 | ) 60 | 61 | # 发送请求并获取音频数据 62 | response = self.http_client.post( 63 | url=url, 64 | data=payload, # 使用 data 而不是 json 65 | headers=headers 66 | ) 67 | 68 | # 收集所有音频数据 69 | audio_data = b'' 70 | for chunk in response.iter_content(chunk_size=8192): 71 | if chunk: 72 | audio_data += chunk 73 | 74 | return audio_data -------------------------------------------------------------------------------- /src/platforms/ttson.py: -------------------------------------------------------------------------------- 1 | from typing import List, Dict, BinaryIO 2 | from src.core.tts_manager import TTSPlatform 3 | from src.utils.http_client import HTTPClient 4 | 5 | 6 | class Ttson(TTSPlatform): 7 | """TTSON平台实现""" 8 | 9 | def __init__(self, base_url: str, token: str = None, network_proxies: Dict = None): 10 | self.base_url = base_url.rstrip('/') 11 | self.token = token 12 | self.http_client = HTTPClient(proxies=network_proxies) 13 | 14 | def set_token(self, token: str): 15 | """设置token""" 16 | self.token = token 17 | 18 | def get_characters(self) -> List[Dict]: 19 | """获取所有可用的语音角色""" 20 | if not self.token: 21 | raise ValueError("Token is required for getting characters") 22 | 23 | url = f"{self.base_url}/flashsummary/voices" 24 | params = { 25 | 'token': self.token 26 | } 27 | headers = { 28 | 'Pragma': 'no-cache' 29 | } 30 | 31 | response = self.http_client.get(url, params=params, headers=headers) 32 | return response.json() 33 | 34 | def get_emotions(self) -> List[Dict]: 35 | """获取所有可用的情感""" 36 | url = f"{self.base_url}/flashsummary/emotions" 37 | headers = { 38 | 'Pragma': 'no-cache' 39 | } 40 | 41 | response = self.http_client.get(url, headers=headers) 42 | return response.json() 43 | 44 | def generate_speech(self, text: str, voice_id: int, **kwargs) -> Dict: 45 | """生成语音""" 46 | if not self.token: 47 | raise ValueError("Token is required for generating speech") 48 | 49 | url = f"{self.base_url}/flashsummary/tts" 50 | 51 | payload = { 52 | "voice_id": voice_id, 53 | "text": text, 54 | "to_lang": kwargs.get('to_lang', 'auto'), 55 | "format": "wav", 56 | "speed_factor": kwargs.get('speed_factor', 1), 57 | "pitch_factor": kwargs.get('pitch_factor', 0), 58 | "volume_change_dB": kwargs.get('volume_change_dB', 0), 59 | "emotion": kwargs.get('emotion', 1), 60 | "code": kwargs.get('code', '') 61 | } 62 | 63 | headers = { 64 | 'Content-Type': 'application/json' 65 | } 66 | 67 | params = {'token': self.token} 68 | 69 | response = self.http_client.post( 70 | url=url, 71 | json=payload, 72 | headers=headers, 73 | params=params 74 | ) 75 | return response.json() 76 | 77 | def download_audio(self, result: Dict) -> bytes: 78 | """下载生成的音频文件 79 | result: 生成语音返回的结果,包含 url, port, voice_path 80 | """ 81 | if not self.token: 82 | raise ValueError("Token is required for downloading audio") 83 | 84 | # 从结果中构建完整的URL 85 | base_url = f"{result['url']}:{result['port']}" 86 | url = f"{base_url}/flashsummary/retrieveFileData" 87 | 88 | # 设置请求参数 89 | params = { 90 | 'stream': True, 91 | 'token': self.token, 92 | 'voice_audio_path': result['voice_path'] 93 | } 94 | 95 | # 设置请求头 96 | headers = { 97 | 'Pragma': 'no-cache', 98 | 'Range': 'bytes=0-' 99 | } 100 | 101 | # 发送请求并返回响应内容 102 | response = self.http_client.get(url, params=params, headers=headers) 103 | return response.content 104 | 105 | def generate(self, text: str, voice_id: int, **kwargs) -> bytes: 106 | """生成语音文件 107 | 108 | Args: 109 | text: 要转换的文本 110 | voice_id: 语音角色ID 111 | **kwargs: 其他参数,包括: 112 | - to_lang: 目标语言,默认'auto' 113 | - speed_factor: 速度因子,默认1 114 | - pitch_factor: 音调因子,默认0 115 | - volume_change_dB: 音量变化,默认0 116 | - emotion: 情感,默认1 117 | - code: 代码,默认'' 118 | 119 | Returns: 120 | bytes: 音频文件的二进制数据 121 | 122 | Raises: 123 | ValueError: 当token未设置时抛出 124 | """ 125 | # 首先生成语音 126 | result = self.generate_speech(text, voice_id, **kwargs) 127 | 128 | # 然后下载音频 129 | return self.download_audio(result) 130 | -------------------------------------------------------------------------------- /src/utils/config.py: -------------------------------------------------------------------------------- 1 | import yaml 2 | from pathlib import Path 3 | import os 4 | 5 | def load_config(): 6 | """加载配置文件,并用环境变量覆盖配置""" 7 | # 加载基础配置 8 | config_path = Path(__file__).parent.parent.parent / "config" / "config.yaml" 9 | with open(config_path, "r", encoding="utf-8") as f: 10 | config = yaml.safe_load(f) 11 | 12 | # 从环境变量更新配置 13 | # TTSON配置 14 | if os.getenv('TTSON_BASE_URL'): 15 | config['platforms']['ttson']['base_url'] = os.getenv('TTSON_BASE_URL') 16 | if os.getenv('TTSON_TOKEN'): 17 | config['platforms']['ttson']['token'] = os.getenv('TTSON_TOKEN') 18 | if os.getenv('TTSON_HTTP_PROXY'): 19 | config['platforms']['ttson']['network_proxies']['http'] = os.getenv('TTSON_HTTP_PROXY') 20 | if os.getenv('TTSON_HTTPS_PROXY'): 21 | config['platforms']['ttson']['network_proxies']['https'] = os.getenv('TTSON_HTTPS_PROXY') 22 | 23 | # ACGN TTSON配置 24 | if os.getenv('ACGN_TTSON_BASE_URL'): 25 | config['platforms']['acgn_ttson']['base_url'] = os.getenv('ACGN_TTSON_BASE_URL') 26 | if os.getenv('ACGN_TTSON_TOKEN'): 27 | config['platforms']['acgn_ttson']['token'] = os.getenv('ACGN_TTSON_TOKEN') 28 | if os.getenv('ACGN_TTSON_HTTP_PROXY'): 29 | config['platforms']['acgn_ttson']['network_proxies']['http'] = os.getenv('ACGN_TTSON_HTTP_PROXY') 30 | if os.getenv('ACGN_TTSON_HTTPS_PROXY'): 31 | config['platforms']['acgn_ttson']['network_proxies']['https'] = os.getenv('ACGN_TTSON_HTTPS_PROXY') 32 | 33 | # Fish Audio配置 34 | if os.getenv('FISH_AUDIO_BASE_URL'): 35 | config['platforms']['fish_audio']['base_url'] = os.getenv('FISH_AUDIO_BASE_URL') 36 | if os.getenv('FISH_AUDIO_TOKEN'): 37 | config['platforms']['fish_audio']['token'] = os.getenv('FISH_AUDIO_TOKEN') 38 | if os.getenv('FISH_AUDIO_HTTP_PROXY'): 39 | config['platforms']['fish_audio']['network_proxies']['http'] = os.getenv('FISH_AUDIO_HTTP_PROXY') 40 | if os.getenv('FISH_AUDIO_HTTPS_PROXY'): 41 | config['platforms']['fish_audio']['network_proxies']['https'] = os.getenv('FISH_AUDIO_HTTPS_PROXY') 42 | 43 | return config 44 | 45 | config = load_config() -------------------------------------------------------------------------------- /src/utils/http_client.py: -------------------------------------------------------------------------------- 1 | import requests 2 | from typing import Dict, Any, Optional 3 | 4 | class HTTPClient: 5 | """HTTP客户端工具类""" 6 | 7 | def __init__(self, proxies=None): 8 | self.proxies = proxies 9 | 10 | def get(self, url: str, params: Optional[Dict] = None, headers: Optional[Dict] = None) -> Any: 11 | """发送GET请求""" 12 | if self.proxies: 13 | kwargs = {'proxies': self.proxies} 14 | else: 15 | kwargs = {} 16 | response = requests.get(url, params=params, headers=headers, stream=True, **kwargs) 17 | response.raise_for_status() 18 | return response 19 | 20 | def post(self, url: str, data: Optional[Dict] = None, json: Optional[Dict] = None, 21 | headers: Optional[Dict] = None, params: Optional[Dict] = None) -> Any: 22 | """发送POST请求""" 23 | if self.proxies: 24 | kwargs = {'proxies': self.proxies} 25 | else: 26 | kwargs = {} 27 | response = requests.post(url, data=data, json=json, headers=headers, params=params, **kwargs) 28 | response.raise_for_status() 29 | return response --------------------------------------------------------------------------------