AIdea-Server / src /speech /speech.py
β€œbotayla”
add Speech
8285ecf
Raw
History Blame Contribute Delete
4.89 kB
import re
from typing import Optional
import edge_tts
from langdetect import detect
from src.utils.logger import setup_logger
logger = setup_logger(__name__)
class TextToSpeechService:
"""
Service responsible for converting generated summaries
or notes into speech using Microsoft Edge TTS.
Features:
1. Text cleaning
2. Automatic language detection
3. Voice selection based on language
4. Audio generation
"""
VOICE_MAP = {
"ar": "ar-EG-SalmaNeural",
"en": "en-US-AriaNeural",
"fr": "fr-FR-DeniseNeural",
"ja": "ja-JP-NanamiNeural",
"de": "de-DE-KatjaNeural",
"es": "es-ES-ElviraNeural",
"it": "it-IT-ElsaNeural",
"pt": "pt-BR-FranciscaNeural",
"ru": "ru-RU-SvetlanaNeural",
"zh-cn": "zh-CN-XiaoxiaoNeural",
"zh-tw": "zh-TW-HsiaoChenNeural",
"ko": "ko-KR-SunHiNeural",
"tr": "tr-TR-EmelNeural",
"pl": "pl-PL-ZofiaNeural",
"sv": "sv-SE-SofieNeural",
"hi": "hi-IN-SwaraNeural",
"id": "id-ID-GadisNeural",
}
DEFAULT_VOICE = "en-US-AriaNeural"
# ──────────────────────────────────────────────
# Text Cleaning
# ──────────────────────────────────────────────
def _clean_text(self, text: str) -> str:
"""
Remove URLs, markdown symbols, YouTube metadata,
and unnecessary characters before TTS generation.
"""
# 1. Remove YouTube header block first (before anything else)
text = re.sub(
r'#\s*YouTube Video.*?---',
'',
text,
flags=re.DOTALL
)
text = re.sub(
r'^.*?(Ψ§Ω„Ω…Ψ΅Ψ―Ψ±|Ψ§Ω„Ω…Ψ―Ψ©|Source|Duration|YouTube|youtu\.be|youtube\.com).*$',
'',
text,
flags=re.MULTILINE | re.IGNORECASE
)
# 2. Remove URLs
text = re.sub(
r'https?://\S+|www\.\S+',
' ',
text
)
# 3. Remove standalone numbers on their own line
text = re.sub(
r'^\s*\d+\s*$',
'',
text,
flags=re.MULTILINE
)
# 4. Remove Markdown headers
text = re.sub(r'#+', ' ', text)
# 5. Remove Markdown symbols
text = re.sub(r'[*_~`>|]', ' ', text)
# 6. Remove dashes
text = re.sub(r'-+', ' ', text)
# 7. Keep only useful characters
text = re.sub(
r'[^\w\s\u0600-\u06FF.,!?ΨŸΨ›:\n]',
' ',
text
)
# 8. Collapse extra spaces
text = re.sub(r'\s+', ' ', text)
return text.strip()
# ──────────────────────────────────────────────
# Language Detection
# ──────────────────────────────────────────────
def _detect_voice(self, text: str) -> str:
"""
Detect language and return matching voice.
"""
try:
language = detect(text)
logger.info(
f"Detected language: {language}"
)
return self.VOICE_MAP.get(
language,
self.DEFAULT_VOICE
)
except Exception as e:
logger.warning(
f"Language detection failed: {e}"
)
return self.DEFAULT_VOICE
# ──────────────────────────────────────────────
# Generate Speech
# ──────────────────────────────────────────────
async def generate_audio(
self,
text: str,
output_file: str = "summary.mp3",
rate: str = "-10%"
) -> str:
"""
Convert text into speech and save it as MP3.
Returns generated audio path.
"""
cleaned_text = self._clean_text(text)
if not cleaned_text:
raise ValueError(
"No valid text available for speech generation."
)
voice = self._detect_voice(cleaned_text)
logger.info(
f"Generating speech using voice: {voice}"
)
communicate = edge_tts.Communicate(
text=cleaned_text,
voice=voice,
rate=rate
)
await communicate.save(output_file)
logger.info(
f"Audio saved successfully: {output_file}"
)
return output_file