feat: integrate Silero TTS as default TTS engine
- Add SileroTTS service with GPU/CPU support - Update TTS API to support configurable engines (silero/edge) - Add TTS_ENGINE and SILERO_SPEAKER config options - Set Silero as default TTS engine - Update requirements.txt with silero dependency
This commit is contained in:
@@ -9,9 +9,15 @@ OUTPUT_DIR=./output
|
|||||||
SEED_VC_MODEL=seed-uvit-whisper-small-wavenet
|
SEED_VC_MODEL=seed-uvit-whisper-small-wavenet
|
||||||
SEED_VC_REPO_PATH=./Seed-VC
|
SEED_VC_REPO_PATH=./Seed-VC
|
||||||
|
|
||||||
|
# TTS Engine Configuration
|
||||||
|
TTS_ENGINE=silero # Options: silero, edge
|
||||||
|
|
||||||
# Edge-TTS Configuration
|
# Edge-TTS Configuration
|
||||||
EDGE_TTS_VOICE=ru-RU-SvetlanaNeural
|
EDGE_TTS_VOICE=ru-RU-SvetlanaNeural
|
||||||
|
|
||||||
|
# Silero TTS Configuration
|
||||||
|
SILERO_SPEAKER=v5_ru
|
||||||
|
|
||||||
# Flask Configuration
|
# Flask Configuration
|
||||||
FLASK_PORT=5000
|
FLASK_PORT=5000
|
||||||
FLASK_DEBUG=false
|
FLASK_DEBUG=false
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ from flask import current_app, request, send_file
|
|||||||
from flask_restx import Namespace, Resource, fields
|
from flask_restx import Namespace, Resource, fields
|
||||||
from app.models.voice import Voice
|
from app.models.voice import Voice
|
||||||
from app.services.edge_tts_service import EdgeTTSService
|
from app.services.edge_tts_service import EdgeTTSService
|
||||||
|
from app.services.silero_tts_service import SileroTTSService
|
||||||
from app.services.seed_vc_service import SeedVCService
|
from app.services.seed_vc_service import SeedVCService
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@@ -110,10 +111,17 @@ class TTS(Resource):
|
|||||||
final_output = os.path.join(output_dir, f"{output_id}_converted.wav")
|
final_output = os.path.join(output_dir, f"{output_id}_converted.wav")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Step 1: Generate TTS with Edge-TTS
|
# Step 1: Generate TTS
|
||||||
logger.info("Step 1: Generating TTS with Edge-TTS")
|
tts_engine = current_app.config.get('TTS_ENGINE', 'silero')
|
||||||
|
logger.info(f"Step 1: Generating TTS with {tts_engine}")
|
||||||
|
|
||||||
|
if tts_engine == 'silero':
|
||||||
|
silero_speaker = current_app.config.get('SILERO_SPEAKER', 'v5_ru')
|
||||||
|
tts_service = SileroTTSService(speaker=silero_speaker)
|
||||||
|
else:
|
||||||
edge_tts_voice = current_app.config['EDGE_TTS_VOICE']
|
edge_tts_voice = current_app.config['EDGE_TTS_VOICE']
|
||||||
tts_service = EdgeTTSService(voice=edge_tts_voice)
|
tts_service = EdgeTTSService(voice=edge_tts_voice)
|
||||||
|
|
||||||
tts_service.text_to_speech(text, tts_output, speed=speed)
|
tts_service.text_to_speech(text, tts_output, speed=speed)
|
||||||
|
|
||||||
# Step 2: Convert voice with Seed-VC
|
# Step 2: Convert voice with Seed-VC
|
||||||
|
|||||||
@@ -18,9 +18,15 @@ class Config:
|
|||||||
SEED_VC_MODEL = os.getenv('SEED_VC_MODEL', 'seed-uvit-whisper-small-wavenet')
|
SEED_VC_MODEL = os.getenv('SEED_VC_MODEL', 'seed-uvit-whisper-small-wavenet')
|
||||||
SEED_VC_REPO_PATH = os.getenv('SEED_VC_REPO_PATH', './Seed-VC')
|
SEED_VC_REPO_PATH = os.getenv('SEED_VC_REPO_PATH', './Seed-VC')
|
||||||
|
|
||||||
|
# TTS Engine
|
||||||
|
TTS_ENGINE = os.getenv('TTS_ENGINE', 'silero') # Default to Silero
|
||||||
|
|
||||||
# Edge-TTS
|
# Edge-TTS
|
||||||
EDGE_TTS_VOICE = os.getenv('EDGE_TTS_VOICE', 'ru-RU-SvetlanaNeural')
|
EDGE_TTS_VOICE = os.getenv('EDGE_TTS_VOICE', 'ru-RU-SvetlanaNeural')
|
||||||
|
|
||||||
|
# Silero TTS
|
||||||
|
SILERO_SPEAKER = os.getenv('SILERO_SPEAKER', 'v5_ru')
|
||||||
|
|
||||||
# Flask
|
# Flask
|
||||||
FLASK_PORT = int(os.getenv('FLASK_PORT', '5000'))
|
FLASK_PORT = int(os.getenv('FLASK_PORT', '5000'))
|
||||||
FLASK_DEBUG = os.getenv('FLASK_DEBUG', 'false').lower() == 'true'
|
FLASK_DEBUG = os.getenv('FLASK_DEBUG', 'false').lower() == 'true'
|
||||||
|
|||||||
87
app/services/silero_tts_service.py
Normal file
87
app/services/silero_tts_service.py
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
"""Silero TTS service for text-to-speech conversion."""
|
||||||
|
import os
|
||||||
|
import logging
|
||||||
|
from typing import Optional
|
||||||
|
import torch
|
||||||
|
import numpy as np
|
||||||
|
import soundfile as sf
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class SileroTTSService:
|
||||||
|
"""Service for converting text to speech using Silero TTS."""
|
||||||
|
|
||||||
|
def __init__(self, device: str = 'cuda', speaker: str = 'v5_ru'):
|
||||||
|
"""
|
||||||
|
Initialize Silero TTS service.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
device: Device to use ('cuda' or 'cpu')
|
||||||
|
speaker: Speaker model to use (e.g., 'v5_ru')
|
||||||
|
"""
|
||||||
|
self.device = device if torch.cuda.is_available() else 'cpu'
|
||||||
|
self.speaker = speaker
|
||||||
|
self.model = None
|
||||||
|
self._load_model()
|
||||||
|
logger.info(f"Silero TTS Service initialized with speaker: {speaker}, device: {self.device}")
|
||||||
|
|
||||||
|
def _load_model(self):
|
||||||
|
"""Load Silero TTS model."""
|
||||||
|
try:
|
||||||
|
# Import silero here to avoid loading at module level
|
||||||
|
from silero import silero_tts
|
||||||
|
self.model, _ = silero_tts(
|
||||||
|
language='ru',
|
||||||
|
speaker=self.speaker,
|
||||||
|
device=self.device
|
||||||
|
)
|
||||||
|
logger.info(f"Silero TTS model loaded successfully on {self.device}")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Failed to load Silero TTS model: {e}")
|
||||||
|
raise
|
||||||
|
|
||||||
|
def text_to_speech(self, text: str, output_path: str, speed: float = 1.0) -> str:
|
||||||
|
"""
|
||||||
|
Convert text to speech and save to file.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
text: Text to convert
|
||||||
|
output_path: Path to save audio file
|
||||||
|
speed: Speech speed multiplier (not supported by Silero, kept for API compatibility)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Path to generated audio file
|
||||||
|
"""
|
||||||
|
logger.info(f"Generating TTS with Silero for text: {text[:50]}...")
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Generate audio
|
||||||
|
audio = self.model.apply_tts(text=text)
|
||||||
|
|
||||||
|
# Convert to numpy array
|
||||||
|
audio_np = audio.numpy() if torch.is_tensor(audio) else np.array(audio)
|
||||||
|
|
||||||
|
# Ensure float32 format
|
||||||
|
audio_np = audio_np.astype(np.float32)
|
||||||
|
|
||||||
|
# Save as WAV (Silero uses 16000 Hz by default)
|
||||||
|
sf.write(output_path, audio_np, 16000)
|
||||||
|
|
||||||
|
logger.info(f"Silero TTS generated: {output_path}")
|
||||||
|
return output_path
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Silero TTS generation failed: {e}")
|
||||||
|
raise
|
||||||
|
|
||||||
|
def get_supported_voices(self) -> list:
|
||||||
|
"""Get list of supported speakers."""
|
||||||
|
# Silero Russian models
|
||||||
|
voices = [
|
||||||
|
"v5_ru", # Default Russian voice (best quality)
|
||||||
|
"v4_ru",
|
||||||
|
"v3_1_ru",
|
||||||
|
"v3_ru",
|
||||||
|
]
|
||||||
|
return voices
|
||||||
@@ -2,6 +2,7 @@ flask>=2.3.0
|
|||||||
flask-restx>=1.3.0
|
flask-restx>=1.3.0
|
||||||
python-dotenv>=1.0.0
|
python-dotenv>=1.0.0
|
||||||
edge-tts>=6.1.0
|
edge-tts>=6.1.0
|
||||||
|
silero>=0.5.0
|
||||||
torch>=2.0.0
|
torch>=2.0.0
|
||||||
torchaudio>=2.0.0
|
torchaudio>=2.0.0
|
||||||
numpy>=1.24.0
|
numpy>=1.24.0
|
||||||
|
|||||||
Reference in New Issue
Block a user