tts-studio / app.py
anbds98's picture
Create app.py
bd0197d verified
Raw
History Blame Contribute Delete
4.85 kB
import os
import yaml
import torch
import numpy as np
import soundfile as sf
from fastapi import FastAPI, HTTPException
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import StreamingResponse
from pydantic import BaseModel
from typing import Optional
import io
# --- Tận dụng mã nguồn gốc VieNeu-TTS ---
# Đảm bảo các thư mục vieneu_tts và utils nằm ở thư mục gốc để import thành công
from vieneu_tts import VieNeuTTS
from utils.core_utils import split_text_into_chunks
app = FastAPI(title="VieNeu-TTS API Server")
# Kích hoạt CORS để index.html trên máy bạn có thể gọi API này
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# --- Khởi tạo Model & Cấu hình ---
CONFIG_PATH = "config.yaml"
if not os.path.exists(CONFIG_PATH):
raise FileNotFoundError("❌ Không tìm thấy file config.yaml ở thư mục gốc!")
with open(CONFIG_PATH, "r", encoding="utf-8") as f:
config = yaml.safe_load(f)
# Lấy đường dẫn model GGUF từ config
# Lưu ý: config.yaml của bạn phải có: backbone_configs -> VieNeu-TTS-GGUF -> repo
try:
model_path = config['backbone_configs']['VieNeu-TTS-GGUF']['repo']
except KeyError:
raise KeyError("❌ config.yaml thiếu cấu hình 'backbone_configs/VieNeu-TTS-GGUF/repo'")
print(f"🔄 Đang nạp model từ: {model_path} (Chế độ CPU)...")
# Khởi tạo động cơ VieNeu-TTS
# Ép device về "cpu" vì Hugging Face Space miễn phí không có GPU
tts = VieNeuTTS(backbone_repo=model_path, backbone_device="cpu")
print("✅ VieNeu-TTS đã sẵn sàng phục vụ!")
# --- Định nghĩa Schema cho API ---
class TTSRequest(BaseModel):
input: str
voice: Optional[str] = "Vĩnh (nam miền Nam)"
speed: Optional[float] = 1.0
# --- Các cổng kết nối (Endpoints) ---
@app.get("/")
def health_check():
"""Kiểm tra xem API có đang sống không"""
return {
"status": "online",
"model": model_path,
"device": "cpu",
"message": "VieNeu-TTS API is running on Hugging Face Docker"
}
@app.get("/v1/voices")
def get_voices():
"""Trả về danh sách giọng đọc từ config.yaml cho index.html chọn"""
voices = list(config.get('voice_samples', {}).keys())
return {"data": [{"id": v} for v in voices]}
@app.post("/v1/audio/speech")
async def text_to_speech(request: TTSRequest):
"""Cổng chính: Nhận text -> Trả về file âm thanh .wav"""
try:
# 1. Lấy thông tin giọng đọc mẫu
voice_info = config['voice_samples'].get(request.voice)
if not voice_info:
raise HTTPException(status_code=400, detail=f"Giọng '{request.voice}' không tồn tại trong config")
ref_audio_path = voice_info['audio']
ref_text_path = voice_info['text']
if not os.path.exists(ref_audio_path):
raise HTTPException(status_code=404, detail=f"Không tìm thấy file audio mẫu: {ref_audio_path}")
# Đọc nội dung text của giọng mẫu
with open(ref_text_path, "r", encoding="utf-8") as f:
ref_text_content = f.read().strip()
# 2. Xử lý văn bản đầu vào (Chia nhỏ đoạn văn dài bằng hàm của VieNeu-TTS)
chunks = split_text_into_chunks(request.input, max_chars=256)
# 3. Mã hóa giọng đọc mẫu (Encoding reference)
ref_codes = tts.encode_reference(ref_audio_path)
# 4. Thực hiện chuyển đổi (Inference) cho từng đoạn và nối lại
final_audio_segments = []
for chunk in chunks:
if not chunk.strip(): continue
# tts.infer trả về numpy array
wav = tts.infer(chunk, ref_codes, ref_text_content)
if wav is not None:
final_audio_segments.append(wav)
if not final_audio_segments:
raise HTTPException(status_code=500, detail="Không thể tạo ra âm thanh từ văn bản này")
# Nối tất cả các đoạn audio lại thành một
combined_wav = np.concatenate(final_audio_segments)
# 5. Đóng gói vào file WAV và gửi về trình duyệt
buffer = io.BytesIO()
sf.write(buffer, combined_wav, 24000, format='WAV')
buffer.seek(0)
return StreamingResponse(buffer, media_type="audio/wav")
except Exception as e:
print(f"❌ Lỗi xử lý TTS: {str(e)}")
raise HTTPException(status_code=500, detail=f"Internal Server Error: {str(e)}")
# --- Chạy Server ---
if __name__ == "__main__":
import uvicorn
# Hugging Face Docker BẮT BUỘC dùng port 7860
uvicorn.run(app, host="0.0.0.0", port=7860)