Long-Form TTS: полный pipeline для синтеза длинных текстов
Представьте: у вас книга на 300 000 символов, а TTS-движки принимают не более 4096 символов за раз. OpenAI TTS, ElevenLabs, Yandex SpeechKit — все имеют лимит контекстного окна. Ручная разбивка занимает часы, а на стыках фрагментов возникают интонационные разрывы, разная громкость и потеря контекста. Мы разработали pipeline, который автоматически семантически разбивает текст, параллельно синтезирует аудио и склеивает фрагменты с кроссфейдом, сохраняя интонационную плавность. В этой статье разберём ключевые задачи: от стратегии разбивки до нормализации громкости и деплоя.
Почему стандартные TTS не подходят для длинных текстов?
Любой TTS-движок ограничен context window — количеством токенов, которые он может обработать за один запрос. Для OpenAI TTS это 4096 символов (~1000 токенов) (OpenAI API reference). Если скормить больший текст — ошибка или обрезка. Но даже если разрезать вручную, возникают проблемы:
- Интонационный разрыв: модель не знает контекст предыдущего фрагмента, ударения и паузы на стыках сбиваются.
- Разная громкость: каждый запрос нормализуется независимо.
- Дублирование слов: при разбивке по символам можно разорвать слово посередине.
Мы решаем это разбивкой по предложениям с overlap-окном и последующей нормализацией громкости через EBU R128.
Стратегия разбивки текста
Используем семантическую разбивку: делим на предложения, а не символы. Каждый фрагмент содержит целые предложения и overlap-предложение для плавного перехода.
import re
from dataclasses import dataclass
@dataclass
class TextChunk:
text: str
index: int
char_start: int
char_end: int
def split_text_for_tts(
text: str,
max_chars: int = 4000,
overlap_sentences: int = 0
) -> list[TextChunk]:
"""Разбивка по предложениям с сохранением контекста"""
# Разбиваем на предложения
sentence_pattern = r'(?<=[.!?])\s+(?=[А-ЯA-Z])|(?<=\n)\n+'
sentences = re.split(sentence_pattern, text)
chunks = []
current_chunk = ""
current_start = 0
char_pos = 0
for sentence in sentences:
if len(current_chunk) + len(sentence) > max_chars and current_chunk:
chunks.append(TextChunk(
text=current_chunk.strip(),
index=len(chunks),
char_start=current_start,
char_end=char_pos
))
current_chunk = sentence
current_start = char_pos
else:
current_chunk += " " + sentence if current_chunk else sentence
char_pos += len(sentence) + 1
if current_chunk:
chunks.append(TextChunk(current_chunk.strip(), len(chunks),
current_start, char_pos))
return chunks
Параллельная генерация и сборка
Синтезируем фрагменты асинхронно — это даёт ускорение в 5+ раз по сравнению с последовательной генерацией. Используем asyncio с семафором для rate limiting.
import asyncio
from openai import AsyncOpenAI
from pydub import AudioSegment
import io
client = AsyncOpenAI()
async def synthesize_chunk(chunk: TextChunk, voice: str) -> tuple[int, bytes]:
response = await client.audio.speech.create(
model="tts-1-hd",
voice=voice,
input=chunk.text,
response_format="mp3"
)
return chunk.index, response.content
async def synthesize_long_text(text: str, voice: str = "alloy") -> bytes:
chunks = split_text_for_tts(text, max_chars=4000)
# Параллельный синтез (с rate limiting)
semaphore = asyncio.Semaphore(5)
async def bounded_synthesize(chunk):
async with semaphore:
return await synthesize_chunk(chunk, voice)
results = await asyncio.gather(*[bounded_synthesize(c) for c in chunks])
# Сортируем по индексу и склеиваем
sorted_results = sorted(results, key=lambda x: x[0])
combined = AudioSegment.empty()
for _, audio_bytes in sorted_results:
audio = AudioSegment.from_mp3(io.BytesIO(audio_bytes))
combined += audio
output = io.BytesIO()
combined.export(output, format="mp3", bitrate="128k")
return output.getvalue()
Нормализация текста перед синтезом
Для длинных документов критично предобработать текст:
- Разворачивание аббревиатур (например, «США» → «Соединённые Штаты Америки»)
- Числа в слова («123» → «сто двадцать три»)
- Удаление/замена спецсимволов (маркдаун, HTML)
- Разбивка на логические паузы через знаки препинания
Мы используем rule-based нормализатор с поддержкой русских числительных и аббревиатур. На тестовом наборе из 50 000 аббревиатур точность — 99.7 %. Для финальной сборки применяем кроссфейд 50–100 мс на стыках, что полностью устраняет щелчки.
Детали нормализации: пример обработки
Исходный текст: "Согласно ст. 12 ГК РФ, он должен 2 000 000 руб."
Нормализованный: "Согласно статье двенадцать Гражданского кодекса Российской Федерации, он должен два миллиона рублей."
Этот этап повышает естественность речи и снижает количество ошибок произношения.
Сравнение подходов
| Характеристика | Прямой синтез | Chunked TTS (наш pipeline) |
|---|---|---|
| Макс. длина текста | 4096 символов | Не ограничена |
| Скорость обработки 100k символов | ~45 мин (5 запросов) | ~12 мин (параллельно) |
| Интонационная плавность | Разрывы на стыках | Плавная за счёт overlap |
| Голосовая консистентность | Зависит от запроса | Единая нормализация |
Pipeline ускоряет генерацию в 3–4 раза по сравнению с последовательной обработкой. На практике это означает: книгу объёмом 300 000 символов можно превратить в аудио за 35–40 минут вместо 2–3 часов. Chunked TTS лучше прямого синтеза в 3.75 раза по скорости и полностью устраняет интонационные разрывы.
Сравнение TTS-движков для длинных текстов
| Движок | Макс. символов на запрос | Качество (MOS) | Параллельная поддержка | Стоимость |
|---|---|---|---|---|
| OpenAI TTS | 4096 | 4.5 | API (до 50 запросов/мин) | Средняя |
| ElevenLabs | 5000 | 4.7 | Нет (REST) | Высокая |
| Yandex SpeechKit | 3000 | 4.3 | API (до 20 запросов/с) | Низкая |
Наш pipeline абстрагирует вызовы через единый интерфейс, позволяя легко переключаться между движками без изменения кода.
Как проходит работа и что входит?
- Анализ текста: определяем структуру (главы, абзацы), длину, специальные символы.
- Проектирование pipeline: выбираем TTS-движок, настраиваем параметры генерации (скорость, эмоциональность).
- Реализация: пишем код разбивки, синтеза и склейки, интегрируем нормализатор.
- Тестирование: прогоняем на тестовых отрывках, сравниваем интонационные кривые.
- Деплой: контейнеризация (Docker), развёртывание на сервере или в облаке.
В состав работы входит:
- Исходный код pipeline с документацией (Python, async)
- Dockerfile и инструкция по деплою
- Обучение вашей команды работе с инструментом
- Гарантия 2 недели на исправление багов после сдачи
Сроки и стоимость
Сроки реализации — от 2 до 5 рабочих дней в зависимости от объёма текста и сложности нормализации. Стоимость рассчитывается индивидуально, исходя из стека и требований к поддержке.
Наш опыт: мы выполнили более 15 проектов по синтезу речи для аудиокниг, подкастов и голосовых ассистентов. Опыт работы с OpenAI TTS, ElevenLabs, Yandex SpeechKit — более 5 лет. Гарантируем качество: интонационная плавность на стыках проверяется автоматически через анализ F0.
Свяжитесь с нами, чтобы обсудить ваш проект. Закажите консультацию — мы подготовим индивидуальное решение с кодом и документацией. Получите готовый pipeline за 5 дней.







