File size: 4,158 Bytes
9bc318e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
"""
Genie TTS API 主应用
整合所有模块,提供完整的API服务
"""

import asyncio
import logging
from datetime import datetime

from fastapi import FastAPI, HTTPException
from fastapi.middleware.cors import CORSMiddleware

# 导入本地模块
from tts_engine import GenieTTSInterface
from voice_cloning import VoiceCloner
from api_routes import (
    basic_router, tts_router, voice_clone_router, character_router, set_engines
)
from models import create_error_response

logger = logging.getLogger(__name__)

# 创建FastAPI应用
api_app = FastAPI(
    title="Genie TTS API",
    description="""
    # 🎵 Genie TTS API
    
    高质量日语文本转语音API服务,基于 GPT-SoVITS V2 架构
    
    ## ✨ 主要功能
    - **🎯 文本转语音**: 使用预训练角色进行语音合成
    - **🎭 语音克隆**: 基于参考音频快速克隆声音
    - **📊 音频分析**: 分析音频质量和特征
    - **🔄 批量处理**: 支持批量文本转语音
    
    ## 🌟 特色
    - 零样本语音合成
    - 轻量级ONNX推理
    - CPU友好设计
    - 支持日语语音合成
    
    ## 📚 使用指南
    1. 查看 `/health` 确认服务状态
    2. 使用 `/tts/synthesize` 进行基础语音合成
    3. 通过 `/voice-clone/create` 创建个性化声音
    4. 使用 `/voice-clone/synthesize` 进行克隆声音合成
    """,
    version="1.0.0",
    docs_url="/docs",
    redoc_url="/redoc"
)

# 添加CORS中间件
api_app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],  # 生产环境中应该限制具体域名
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

# 全局引擎实例
tts_engine = None
voice_cloner = None


@api_app.on_event("startup")
async def startup_event():
    """应用启动时初始化所有服务"""
    global tts_engine, voice_cloner
    
    logger.info("🚀 正在初始化Genie TTS API服务...")
    
    try:
        # 1. 初始化TTS引擎
        logger.info("📝 初始化TTS引擎...")
        tts_engine = GenieTTSInterface()
        await asyncio.get_event_loop().run_in_executor(None, tts_engine.initialize_engine)
        
        # 2. 初始化语音克隆器
        logger.info("🎭 初始化语音克隆器...")
        voice_cloner = VoiceCloner(tts_engine)
        
        # 3. 设置路由引擎
        set_engines(tts_engine, voice_cloner)
        
        logger.info("✅ Genie TTS API服务初始化成功")
        logger.info("🌐 API文档地址: /docs")
        logger.info("📖 ReDoc文档地址: /redoc")
        
    except Exception as e:
        logger.error(f"❌ 初始化失败: {e}")
        tts_engine = None
        voice_cloner = None


@api_app.on_event("shutdown")
async def shutdown_event():
    """应用关闭时清理资源"""
    global tts_engine, voice_cloner
    
    logger.info("🧹 正在清理资源...")
    
    try:
        # 这里可以添加清理逻辑
        # 例如:关闭数据库连接,清理临时文件等
        logger.info("✅ 资源清理完成")
    except Exception as e:
        logger.error(f"❌ 资源清理失败: {e}")


# 注册所有路由
api_app.include_router(basic_router)
api_app.include_router(tts_router)
api_app.include_router(voice_clone_router)
api_app.include_router(character_router)


# 全局异常处理器
@api_app.exception_handler(HTTPException)
async def http_exception_handler(request, exc):
    """HTTP异常处理器"""
    return create_error_response(exc.detail, exc.status_code)


@api_app.exception_handler(Exception)
async def general_exception_handler(request, exc):
    """通用异常处理器"""
    logger.error(f"未处理的异常: {exc}")
    return create_error_response("内部服务器错误", 500)


def create_api_interface():
    """创建API接口,用于与Gradio应用集成"""
    return api_app


if __name__ == "__main__":
    # 直接运行API服务(仅用于开发测试)
    import uvicorn
    
    logger.info("🔧 开发模式启动")
    uvicorn.run(
        api_app, 
        host="0.0.0.0", 
        port=8000,
        log_level="info",
        reload=True
    )