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 днів.







