180 lines
5.7 KiB
Python
180 lines
5.7 KiB
Python
import os
|
|
import soundfile as sf
|
|
from fastapi import FastAPI, HTTPException
|
|
from fastapi.middleware.cors import CORSMiddleware
|
|
from fastapi.responses import FileResponse
|
|
from pydantic import BaseModel, Field
|
|
import uvicorn
|
|
import redis
|
|
import hashlib
|
|
import json
|
|
from kafka import KafkaProducer, KafkaConsumer
|
|
import threading
|
|
import time
|
|
from tools.i18n.i18n import I18nAuto
|
|
from GPT_SoVITS.inference_webui import change_gpt_weights, change_sovits_weights, get_tts_wav
|
|
from dotenv import load_dotenv
|
|
import os
|
|
import torch
|
|
|
|
# 加载 .env 文件
|
|
load_dotenv()
|
|
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
|
|
print(f"Using device: {device}")
|
|
|
|
# FastAPI configuration
|
|
app = FastAPI()
|
|
i18n = I18nAuto()
|
|
|
|
# CORS configuration
|
|
ALLOWED_ORIGINS = os.getenv('ALLOWED_ORIGINS').split(',')
|
|
|
|
# Redis configuration
|
|
REDIS_HOST = os.getenv('REDIS_HOST')
|
|
REDIS_PORT = int(os.getenv('REDIS_PORT'))
|
|
REDIS_DB = int(os.getenv('REDIS_TTS_DB'))
|
|
REDIS_PASSWORD = os.getenv('REDIS_PASSWORD')
|
|
|
|
# Kafka configuration
|
|
KAFKA_BROKER = os.getenv('KAFKA_BROKER')
|
|
KAFKA_TOPIC = os.getenv('KAFKA_TTS_TOPIC')
|
|
# KAFKA_GROUP_ID = 'tts_group'
|
|
KAFKA_CONSUMER_THREADS = 1
|
|
|
|
# TTS configuration
|
|
GPT_MODEL_PATH = os.getenv('GPT_MODEL_PATH')
|
|
SOVITS_MODEL_PATH = os.getenv('SOVITS_MODEL_PATH')
|
|
REF_AUDIO_PATH = os.getenv('REF_AUDIO_KO_PATH')
|
|
REF_TEXT_PATH = os.getenv('REF_TEXT_KO_PATH')
|
|
REF_LANGUAGE = os.getenv('REF_KO_LANGUAGE')
|
|
TARGET_LANGUAGE = os.getenv('TARGET_LANGUAGE')
|
|
OUTPUT_PATH = os.getenv('OUTPUT_PATH')
|
|
|
|
# Initialize FastAPI CORS
|
|
app.add_middleware(
|
|
CORSMiddleware,
|
|
allow_origins=ALLOWED_ORIGINS,
|
|
allow_credentials=True,
|
|
allow_methods=["*"],
|
|
allow_headers=["*"],
|
|
)
|
|
|
|
# Initialize Redis client
|
|
redis_client = redis.Redis(
|
|
host=REDIS_HOST,
|
|
port=REDIS_PORT,
|
|
db=REDIS_DB,
|
|
password=REDIS_PASSWORD
|
|
)
|
|
|
|
# Initialize Kafka producer
|
|
kafka_producer = KafkaProducer(bootstrap_servers=KAFKA_BROKER)
|
|
|
|
class TTSRequest(BaseModel):
|
|
text: str = Field(..., alias="text")
|
|
|
|
def get_audio_hash(text):
|
|
return hashlib.md5(text.encode()).hexdigest()
|
|
|
|
# Initialize models at startup
|
|
print("Initializing models...")
|
|
change_gpt_weights(gpt_path=GPT_MODEL_PATH)
|
|
change_sovits_weights(sovits_path=SOVITS_MODEL_PATH)
|
|
|
|
# Read reference text
|
|
with open(REF_TEXT_PATH, 'r', encoding='utf-8') as file:
|
|
ref_text = file.read()
|
|
|
|
print("Models initialized successfully.")
|
|
|
|
def synthesize(target_text, output_path):
|
|
# Synthesize audio
|
|
with torch.cuda.device(device):
|
|
synthesis_result = get_tts_wav(ref_wav_path=REF_AUDIO_PATH,
|
|
prompt_text=ref_text,
|
|
prompt_language=i18n(REF_LANGUAGE),
|
|
text=target_text,
|
|
text_language=i18n(TARGET_LANGUAGE), top_p=1, temperature=1)
|
|
|
|
result_list = list(synthesis_result)
|
|
|
|
if result_list:
|
|
last_sampling_rate, last_audio_data = result_list[-1]
|
|
audio_hash = get_audio_hash(target_text)
|
|
output_wav_path = os.path.join(output_path, f"{audio_hash}.wav")
|
|
sf.write(output_wav_path, last_audio_data, last_sampling_rate)
|
|
return output_wav_path
|
|
else:
|
|
return None
|
|
|
|
@app.post("/tts_ko")
|
|
async def synthesize_audio(request: TTSRequest):
|
|
try:
|
|
print(f"Received TTS request: {request.dict()}")
|
|
target_text = request.text
|
|
audio_hash = get_audio_hash(target_text)
|
|
|
|
# Check Redis cache
|
|
cached_audio = redis_client.get(audio_hash)
|
|
if cached_audio:
|
|
audio_info = json.loads(cached_audio)
|
|
return FileResponse(audio_info['path'], media_type="audio/wav")
|
|
|
|
# Check file system
|
|
file_path = os.path.join(OUTPUT_PATH, f"{audio_hash}.wav")
|
|
if os.path.exists(file_path):
|
|
# Cache the file path in Redis
|
|
redis_client.set(audio_hash, json.dumps({"path": file_path}))
|
|
return FileResponse(file_path, media_type="audio/wav")
|
|
|
|
# Send message to Kafka
|
|
kafka_producer.send(KAFKA_TOPIC, json.dumps({
|
|
'text': target_text,
|
|
'audio_hash': audio_hash
|
|
}).encode('utf-8'))
|
|
|
|
# Wait for the audio to be generated (you might want to implement a more sophisticated waiting mechanism)
|
|
for _ in range(60): # Wait for up to 30 seconds
|
|
if os.path.exists(file_path):
|
|
return FileResponse(file_path, media_type="audio/wav")
|
|
time.sleep(1)
|
|
|
|
# If audio is not generated within the timeout
|
|
raise HTTPException(status_code=504, detail="Audio generation timed out")
|
|
except Exception as e:
|
|
print(f"Error processing TTS request: {str(e)}")
|
|
raise HTTPException(status_code=500, detail=str(e))
|
|
|
|
@app.get("/")
|
|
async def root():
|
|
return {"message": "TTS API is running"}
|
|
|
|
def kafka_consumer_thread():
|
|
consumer = KafkaConsumer(
|
|
KAFKA_TOPIC,
|
|
bootstrap_servers=KAFKA_BROKER,
|
|
# group_id=KAFKA_GROUP_ID,
|
|
auto_offset_reset='latest',
|
|
value_deserializer=lambda m: json.loads(m.decode('utf-8'))
|
|
)
|
|
|
|
for message in consumer:
|
|
target_text = message.value['text']
|
|
audio_hash = message.value['audio_hash']
|
|
|
|
output_path = synthesize(target_text, OUTPUT_PATH)
|
|
|
|
if output_path:
|
|
redis_client.set(audio_hash, json.dumps({"path": output_path}))
|
|
print(f"Audio synthesized successfully: {output_path}")
|
|
else:
|
|
print("Failed to synthesize audio")
|
|
|
|
if __name__ == "__main__":
|
|
# Start Kafka consumer threads
|
|
torch.cuda.set_device(device)
|
|
for _ in range(KAFKA_CONSUMER_THREADS):
|
|
consumer_thread = threading.Thread(target=kafka_consumer_thread)
|
|
consumer_thread.start()
|
|
|
|
uvicorn.run(app, host="0.0.0.0", port=6003) |