Shaman 82c306ff69 init
2026-10-01 07:38:01 +03:00
2026-10-01 07:38:01 +03:00
2026-10-01 07:38:01 +03:00
2026-10-01 07:38:01 +03:00
2026-10-01 07:38:01 +03:00
2026-10-01 07:38:01 +03:00
2026-10-01 07:38:01 +03:00
2026-10-01 07:38:01 +03:00
2026-10-01 07:38:01 +03:00
2026-10-01 07:38:01 +03:00
2026-10-01 07:38:01 +03:00
2026-10-01 07:38:01 +03:00
2026-10-01 07:38:01 +03:00
2026-10-01 07:38:01 +03:00

Silero TTS Server

Локальный сервер синтеза речи на базе Silero TTS с OpenAI-совместимым API.

Принимает текст на русском и английском, определяет язык запроса по наличию кириллицы, нормализует числа/аббревиатуры (rutextnorm), транслитерирует латинские слова в кириллицу (RU) и возвращает готовый аудиопоток. Работает полностью офлайн — модели грузятся из локальных файлов .pt, сеть не требуется.


Возможности

  • 🔌 OpenAI-совместимые эндпоинты: /v1/audio/speech, /v1/models, /v1/audio/voices
  • 🌍 Один язык и один голос на запрос — если в тексте есть хоть одна кириллическая буква, весь текст читает русская модель; иначе — английская. Голос не меняется внутри ответа.
  • 🔢 Нормализация текста — цифры, даты, единицы измерения читаются словами (только RU)
  • 🎙️ 14 голосов (имена из набора OpenAI) с отдельными спикерами для RU и EN
  • 🎵 6 форматов вывода: mp3, wav, pcm, opus, aac, flac
  • 📡 Потоковая передача (stream: true)
  • 🖥️ Веб-интерфейс для тестирования на /webui (включается в config.json)
  • ⚙️ Полностью CPU-синтез, без обязательной GPU

Быстрый старт

Windows

run.bat

Linux / macOS

./run.sh

Скрипты автоматически создают .venv, устанавливают зависимости и запускают сервер на http://0.0.0.0:5215.

Обновление из репозитория

update.bat
./update.sh

Останавливают сервис, подтягивают изменения из origin (ветка main), переустанавливают зависимости и перезапускают сервер.

Зависимости от системы

  • Python 3.12
  • ffmpeg — нужен для mp3/opus/aac/flac (для wav/pcm не требуется). Ставится автоматически вместе с imageio-ffmpeg (входит в зависимости), отдельных действий не нужно. Свой бинарь можно указать в config.json → runtime.ffmpeg_path.

Ручной запуск

python -m venv .venv
.venv/Scripts/pip install -e ".[dev]"   # Windows
.venv/bin/pip install -e ".[dev]"        # Linux/macOS
.venv/bin/python -m uvicorn app.main:app --host 0.0.0.0 --port 5215

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

Базовый запрос

curl http://localhost:5215/v1/audio/speech \
  -H "Content-Type: application/json" \
  -d '{"voice": "alloy", "input": "Привет, мир! This is a test.", "response_format": "mp3"}' \
  --output speech.mp3

Параметры запроса

Параметр Тип По умолчанию Описание
model string UniosTTS Игнорируется (совместимость с OpenAI)
voice string — Имя голоса (см. ниже), при неизвестном — default
input string — Текст для синтеза (не пустой)
response_format string mp3 mp3 / wav / pcm / opus / aac / flac
speed float 1.0 Пока поддерживается только 1.0
stream bool false Потоковая отдача аудио
stream_format string — audio включает стрим; sse не поддерживается
rate string — Темп просодии: x-slow/slow/medium/fast/x-fast (иное значение → 422)
pitch string — Тон просодии: x-low/low/medium/high/x-high (иное значение → 422)

Голоса

Имена голосов соответствуют встроенному набору OpenAI. RU-сторона ограничена 5 спикерами модели v5_5_ru, EN-сторона — спикерами v3_en.

Имя RU-спикер EN-спикер
alloy eugene en_2
ash aidar en_1
ballad baya en_3
coral kseniya en_4
echo aidar en_8
fable baya en_5
nova xenia en_6
onyx eugene en_8
sage xenia en_7
shimmer kseniya en_9
verse eugene en_10
marin aidar en_11
cedar kseniya en_12
default baya en_0

Проверка работоспособности

curl http://localhost:5215/health
# {"status":"ok"}

Эндпоинты

Метод Путь Назначение
POST /v1/audio/speech Синтез речи (OpenAI-совместимый)
GET /v1/models Список моделей (бренд-модель UniosTTS)
GET /v1/audio/voices Список голосов из config.json
GET /health Проверка работоспособности
GET /webui Веб-интерфейс (если webui: true)
GET /webui/models Список моделей для UI (UniosTTS)
GET /webui/voices Список голосов для UI
POST /webui/speech Синтез + метаданные (JSON: base64, язык, нормализация)

Веб-интерфейс

Для интерактивного тестирования. Включается флагом "webui": true в config.json; по умолчанию выключен (эндпоинты /webui/* отдают 404).

Откройте http://localhost:5215/webui: вводите текст, выбирайте голос и формат, слушайте результат. Поведение идентично API — модель выбирается автоматически по языку текста (model в UI — это UniosTTS, фактически игнорируется), голос берётся из стандартного справочника.

Отличие от API — /webui/speech возвращает JSON с base64-аудио и метаданными вместо бинарного потока, чтобы показать диагностику:

Поле Описание
audio Аудио в base64
language Определённый язык (ru / en)
model / voice Модель и имя голоса из справочника
speaker Реальный спикер модели
normalized_text Текст после нормализации
segments Число сегментов
synthesis_time_ms Время синтеза, мс

Интерфейс также хранит историю запросов текущей сессии и даёт скачать файл.


Конфигурация

Настройки в config.json:

{
  "webui": true,                 // включить веб-интерфейс на /webui
  "use_ssml": true,              // SSML-просодия/паузы; false = откат на легаси text= путь
  "models": [                    // список моделей, порядок = приоритет при выборе по языку
    { "file": "v5_5_ru", "languages": ["ru"] },   // models/v5_5_ru.pt
    { "file": "v3_en",   "languages": ["en"] }    // models/v3_en.pt
  ],
  "model_voices": {              // сопоставление стандартного имени голоса со спикером каждой модели
    "v5_5_ru": { "default": "baya", "alloy": "eugene" /* ... */ },
    "v3_en":   { "default": "en_0", "alloy": "en_2"   /* ... */ }
  },
  "runtime": {
    "sample_rate": 24000,
    "ffmpeg_path": "ffmpeg",
    "translit_dict": "translit_dict.json",
    "ssml_max_chars": 500        // предел длины одного <speak>-документа (сверх — режется по абзацам)
  }
}

Модели описаны в массиве models и грузятся через torch.package при старте (см. app/synthesis.py). Стандартные имена голосов (alloy, ash, …) одинаковы для всех моделей, а во model_voices задано, какой реальный спикер соответствует имени в каждой конкретной модели. Язык текста определяет, какая модель используется, — из её languages.


Структура проекта

.
├── app/                  # FastAPI-приложение
│   ├── main.py           # инициализация приложения, /health, /v1/models
│   ├── speech.py         # эндпоинты /v1/audio/speech и /v1/audio/voices
│   ├── webui.py          # веб-интерфейс /webui и /webui/* (опционально)
│   ├── webui.html        # одностраничный UI (inline CSS/JS)
│   ├── synthesis.py      # движок синтеза (набор моделей Silero)
│   ├── segmentation.py   # определение языка запроса и подготовка сегментов
│   ├── normalize.py      # нормализация RU-текста (rutextnorm)
│   ├── ssml.py           # SSML-билдер: <s>/<p>/<break>/<prosody>, sentinel-паузы
│   ├── translit.py       # транслитерация латинских слов в кириллицу (RU)
│   ├── audio.py          # кодирование PCM → mp3/wav/opus/… (ffmpeg)
│   ├── config.py         # загрузка config.json
│   └── errors.py         # OpenAI-формат ошибок
├── silero_tts/           # локальный шим-обёртка над моделями Silero
├── models/               # файлы моделей .pt (грузятся офлайн)
├── config.json           # модели, голоса и рантайм-настройки
├── translit_dict.json    # словари транслитерации (exact + abbreviations)
├── tests/                # pytest-тесты (транслит, сегментация, синтез)
├── pyproject.toml        # зависимости и сборка
├── run.bat / run.sh      # скрипты запуска
├── update.bat / update.sh # скрипты обновления из git

Как это работает

текст → определение языка запроса (есть кириллица → RU, иначе EN)
      → извлечение явных пауз `|||` / `|||мс` (→ sentinel)
      → допостановка точек на концах строк
      → разметка паузных границ (разделители удаляются, вставляется sentinel)
      → разбиение на абзацы и предложения (razdel)
      → транслитерация латинских слов в кириллицу (только RU)
      → нормализация RU (rutextnorm)  — см. «Нормализация текста»
      → сборка SSML (<speak>/<p>/<s>/<break>/<prosody>), разбиение по ssml_max_chars
      → синтез одним голосом через ssml_text (автоударения + ручные «+»)
      → кодирование в нужный формат (ffmpeg)

Нормализация текста

Перед синтезом текст нормализуется в два слоя:

  1. Общий слой — языконезависимые правила, применяются ко всему тексту целиком, независимо от языка.
  2. Региональный слой — правила конкретного языка, применяются только к сегментам распознанного языка (сейчас реализован только для русского).
сырой текст
  → detect_language()                       # один язык на весь запрос
  → extract_explicit_pauses()               # `|||`/`|||мс` → sentinel-пауза (без цифр в тексте)
  → normalize_common()                       # общий слой (+ разметка паузных границ sentinel'ом)
  → разбиение на абзацы (пустые строки) и предложения (razdel.sentenize)
  → transliterate_latin()                   # только для RU: латынь → кириллица
  → normalize_language(sent, lang)          # региональный слой по языку
  → build_ssml() → <speak>/<p>/<s>/<break>/<prosody>
  → синтез через ssml_text (put_accent=True, put_yo)

Логика сосредоточена в app/normalize.py, app/translit.py и app/segmentation.py.

Общий слой — normalize_common() (любой язык)

Вид Метод Что делает
Точка на переносе строки ensure_line_punctuation Если строка заканчивается на букву/цифру без знака препинания, перед переводом строки добавляется .. Нужно для заголовков и списков: иначе razdel склеивает строку со следующей, и синтез читает их слитно без паузы. Строки, уже заканчивающиеся на пунктуацию (. ! ? , : …) или на закрывающие » ) ", не изменяются. Каждый перевод строки трактуется как граница — пауза ставится намеренно.
Пробелы (в normalize_common) Схлопывание повторяющихся пробелов и табуляций.

Региональный слой — normalize_language() (по языку)

Региональные нормализаторы перечислены в словаре _REGIONAL_NORMALIZERS = {"ru": normalize_russian} (app/normalize.py). Функция вызывается для каждого сегмента с его языком; языки без собственных правил (например, английский) проходят без изменений — числа и даты в них произносит сама модель.

Как добавить язык. Положите функцию-нормализатор в _REGIONAL_NORMALIZERS под нужным кодом языка — общий слой останется прежним.

Русский — normalize_russian() (rutextnorm)

Применяется к сегментам языка ru. Методы идут в таком порядке:

Группа Методы
Типографика и пробелы normalize_typography, схлопывание повторных пробелов
Веб-текст normalize_web (URL, e-mail, домены, символы @ / . #)
Сокращения и разделы normalize_abbreviations, expand_abbreviations, normalize_sections (§, ст., п.), normalize_case_context
Числа normalize_number_groups (разряды), normalize_ranges (диапазоны), normalize_ordinals + normalize_ordinal_triggers (порядковые), normalize_decimals (десятичные), normalize_negatives (отрицания), normalize_text_with_numbers (цифры → слова)
Даты и время normalize_dates, normalize_time, normalize_scores (счёт N:M)
Дроби и проценты normalize_fractions, normalize_percent, normalize_multipliers (×, кратное)
Деньги и величины normalize_symbol_currency, currency_normalization, normalize_measurements (кг, м, …), normalize_symbols
Прочее normalize_math (+ − = %), normalize_versions, normalize_text_with_phone_numbers
Латиница в RU-тексте обрабатывается до вызова normalize_russian — транслитерацией из app/translit.py (см. «Смешанный RU/EN текст»), поэтому внутренние latin-правила rutextnorm (cyrrilize) фактически получают уже кириллический текст
Буква Ё restore_yo

Смешанный RU/EN текст

Язык выбирается на весь запрос: хотя бы одна кириллическая буква (U+0400–U+04FF) — русская модель и RU-спикер, иначе — английская модель и EN-спикер. Голос не меняется внутри ответа. Чтобы английские слова не выпадали из аудиодорожки (русская модель Silero их не произносит), перед нормализацией они транслитерируются в кириллицу (app/translit.py):

  1. Словарь аббревиатур (abbreviations) — для токенов только в верхнем регистре: IT → «айти», DVD → «ди-вэ-ди».
  2. Словарь точных соответствий (exact) — бренды и термины: Ozon → «Озон», Python → «Пайтон» (регистр выхода повторяет вход).
  3. Фонетические правила — для слов вне словаря: shipping → «шиппинг».

Словарь лежит в translit_dict.json (путь настраивается в config.json → runtime.translit_dict) и пополняется без изменения кода — достаточно добавить строку "spotify": "спотифай" в раздел exact.

Транслитерация применяется только к RU-запросам; чисто английский текст идёт в EN-модель без изменений.

Автоударения (на стороне модели)

При синтезе включены put_accent=True и put_yo=True (app/synthesis.py) — расстановка ударений и разрешение омографов средствами модели v5_5_ru (это функция Silero, а не нормализатора).


SSML-разметка (просодия, паузы, ручные ударения)

По умолчанию ("use_ssml": true) синтез идёт через SSML: подготовленные сегменты собираются в <speak>-документы (app/ssml.py) и подаются в модель параметром ssml_text. Структура: предложение → <s>, абзац (пустая строка во входе) → <p>, всё содержимое при заданном rate/pitch оборачивается в <prosody>.

Метка во входе Эффект
rate / pitch (API/UI) Темп и тон речи (<prosody>); невалидное значение → 422
`
`
+ перед гласной Ручное ударение, сохраняется до модели (рядом с автоударением)
Скобки/кавычки/markdown Удаляются как символы; на их границе вставляется пауза 150 мс

Паузные границы кодируются внутренним sentinel-символом из PUA, который не виден в модели и в отображаемом тексте (normalized_text в /webui/speech показывается уже без него). Длинные тексты режутся по границам абзацев на документы не длиннее runtime.ssml_max_chars (по умолчанию 500 символов — экспериментальный предел модели).

Откат. Если SSML-путь нужно полностью отключить, поставьте "use_ssml": false в config.json — синтез вернётся на легаси-ветку text= по одному сегменту за вызов (без <break>/<prosody>, ручные + и явные паузы в этом режиме не действуют). Поле rate/pitch при этом игнорируется.


Ограничения

  • Скорость воспроизведения (speed) отличная от 1.0 не поддерживается.
  • stream_format: "sse" возвращает ошибку.
  • Синтез выполняется на CPU (num_threads=4); для реального времени при больших нагрузках рекомендуется GPU.

Лицензия

Модели Silero TTS (включая используемые здесь v5_5_ru и v3_en) распространяются под лицензией CC BY-NC 4.0 — только для некоммерческого использования. Убедитесь, что ваше использование соответствует её условиям.

S
Description
OpenAI совместимый TTS сервис на базе Silero
Readme 276 MiB
Languages
Python 83.1%
HTML 13.6%
Shell 2.1%
Batchfile 1.2%