Перейти к содержанию

TTS Provider

Text-to-Speech через Edge-TTS с извлечением таймингов слов.

Возможности

  • Бесплатный TTS от Microsoft Edge
  • Множество голосов и языков
  • Word-level тайминги для синхронизации
  • SSML поддержка
  • Контроль скорости и pitch

Использование

Базовый синтез

from kit.providers.tts import EdgeTTSProvider

tts = EdgeTTSProvider(voice="ru-RU-DmitryNeural")

result = await tts.synthesize(
    text="Привет! Как дела?",
    output_path="output.mp3"
)

print(f"Audio: {result.audio_path}")
print(f"Duration: {result.duration_ms}ms")

Тайминги слов

result = await tts.synthesize(
    text="Это пример текста для синтеза речи",
    output_path="output.mp3"
)

# Тайминг каждого слова
for timing in result.word_timings:
    print(f"{timing.word}: {timing.start_ms}-{timing.end_ms}ms")

# Пример вывода:
# Это: 0-250ms
# пример: 250-520ms
# текста: 520-890ms
# для: 890-1050ms
# синтеза: 1050-1450ms
# речи: 1450-1720ms

Доступные голоса

# Русские голоса
voices = [
    "ru-RU-DmitryNeural",    # Мужской
    "ru-RU-SvetlanaNeural",  # Женский
]

# Казахские голоса
voices = [
    "kk-KZ-AigulNeural",     # Женский
    "kk-KZ-DauletNeural",    # Мужской
]

# Английские голоса
voices = [
    "en-US-GuyNeural",
    "en-US-JennyNeural",
    "en-GB-RyanNeural",
]

# Получить все голоса
all_voices = await EdgeTTSProvider.list_voices()

Параметры синтеза

result = await tts.synthesize(
    text="Текст для озвучки",
    output_path="output.mp3",
    rate="+10%",    # Скорость: -50% до +100%
    pitch="+5Hz",   # Высота тона
    volume="+0%"    # Громкость
)

SSML поддержка

ssml = """
<speak>
    <prosody rate="slow">Медленная речь.</prosody>
    <break time="500ms"/>
    <prosody pitch="high">Высокий голос!</prosody>
    <emphasis level="strong">Важно!</emphasis>
</speak>
"""

result = await tts.synthesize_ssml(ssml, "output.mp3")

Batch синтез

texts = [
    "Первое предложение",
    "Второе предложение",
    "Третье предложение"
]

results = await tts.synthesize_batch(
    texts,
    output_dir="./audio"
)

for result in results:
    print(f"{result.audio_path}: {result.duration_ms}ms")

API Reference

EdgeTTSProvider

class EdgeTTSProvider:
    def __init__(
        self,
        voice: str = "ru-RU-DmitryNeural",
        rate: str = "+0%",
        pitch: str = "+0Hz",
        volume: str = "+0%"
    )

    async def synthesize(
        self,
        text: str,
        output_path: str,
        rate: str = None,
        pitch: str = None,
        volume: str = None
    ) -> TTSResult

    async def synthesize_ssml(
        self,
        ssml: str,
        output_path: str
    ) -> TTSResult

    async def synthesize_batch(
        self,
        texts: List[str],
        output_dir: str
    ) -> List[TTSResult]

    @staticmethod
    async def list_voices(language: str = None) -> List[Voice]

TTSResult

@dataclass
class TTSResult:
    audio_path: str
    duration_ms: int
    word_timings: List[WordTiming]
    voice: str

@dataclass
class WordTiming:
    word: str
    start_ms: int
    end_ms: int

    @property
    def duration_ms(self) -> int:
        return self.end_ms - self.start_ms

Примеры из production

Autoshorts — озвучка с субтитрами

class VoiceoverGenerator:
    def __init__(self):
        self.tts = EdgeTTSProvider(voice="ru-RU-DmitryNeural")

    async def generate_with_subtitles(
        self,
        script: str,
        output_dir: str
    ) -> dict:
        """Генерация озвучки и файла субтитров."""

        result = await self.tts.synthesize(
            text=script,
            output_path=f"{output_dir}/voiceover.mp3"
        )

        # Генерация SRT субтитров
        srt_content = self.create_srt(result.word_timings)
        srt_path = f"{output_dir}/subtitles.srt"

        with open(srt_path, "w", encoding="utf-8") as f:
            f.write(srt_content)

        return {
            "audio": result.audio_path,
            "subtitles": srt_path,
            "duration": result.duration_ms
        }

    def create_srt(self, timings: List[WordTiming]) -> str:
        """Создание SRT файла из таймингов."""
        srt_lines = []

        # Группируем слова по 5-7 для субтитров
        chunks = self.chunk_words(timings, max_words=6)

        for i, chunk in enumerate(chunks, 1):
            start = self.ms_to_srt_time(chunk[0].start_ms)
            end = self.ms_to_srt_time(chunk[-1].end_ms)
            text = " ".join(w.word for w in chunk)

            srt_lines.append(f"{i}")
            srt_lines.append(f"{start} --> {end}")
            srt_lines.append(text)
            srt_lines.append("")

        return "\n".join(srt_lines)

    def ms_to_srt_time(self, ms: int) -> str:
        """Конвертация миллисекунд в SRT формат."""
        hours = ms // 3600000
        minutes = (ms % 3600000) // 60000
        seconds = (ms % 60000) // 1000
        millis = ms % 1000
        return f"{hours:02d}:{minutes:02d}:{seconds:02d},{millis:03d}"

Karaoke-style субтитры

class KaraokeGenerator:
    def __init__(self):
        self.tts = EdgeTTSProvider()

    async def generate(self, text: str, output_path: str) -> dict:
        result = await self.tts.synthesize(text, f"{output_path}/audio.mp3")

        # ASS субтитры с karaoke эффектом
        ass_content = self.create_karaoke_ass(result.word_timings)

        with open(f"{output_path}/karaoke.ass", "w") as f:
            f.write(ass_content)

        return {
            "audio": result.audio_path,
            "subtitles": f"{output_path}/karaoke.ass"
        }

    def create_karaoke_ass(self, timings: List[WordTiming]) -> str:
        # ASS формат с подсветкой текущего слова
        ...

DedMoroz.ai — персонализированная озвучка

class GreetingVoiceover:
    def __init__(self):
        self.tts = EdgeTTSProvider(
            voice="ru-RU-DmitryNeural",
            rate="-5%",  # Чуть медленнее для торжественности
            pitch="-2Hz"  # Чуть ниже для "дедморозовости"
        )

    async def generate(self, greeting: str, name: str) -> str:
        output_path = f"./greetings/{name}_{uuid4()}.mp3"

        await self.tts.synthesize(
            text=greeting,
            output_path=output_path
        )

        return output_path