← Практика · 29 августа 2026
Собираем пакетную озвучку русского текста через Яндекс SpeechKit: несколько голосов, оплата по символам, один скрипт вместо ручного труда.
7 минут чтенияинструменты

Игре нужны голоса персонажей. Ролику — дикторский текст. Аудиокниге — сто глав. Живой диктор за такое просит десятки тысяч рублей и неделю времени, а любая правка текста означает новую запись.
Синтез решает задачу иначе: озвучка становится частью сборки проекта. Поменяли реплику — перегенерировали файл. Ниже рабочий рецепт, которым я озвучиваю аудио для своего справочного сайта.
Яндекс SpeechKit — качество русской речи у него сегодня одно из лучших, а платить можно по факту, за символы. Нужен API-ключ сервисного аккаунта с ролью на синтез, дальше всё делается тремя десятками строк.
Ключ берётся в консоли Яндекс.Облака: каталог → «Сервисные аккаунты» → создать аккаунт → назначить роль ai.speechkit-tts.user → в карточке аккаунта «Создать новый ключ» → API-ключ (не IAM-токен и не статический ключ доступа). Значение показывается один раз.
Кладём его так, чтобы не попал в репозиторий:
mkdir -p ~/.config/yandex
printf 'AQVN…ваш-ключ\n' > ~/.config/yandex/tts.key
chmod 600 ~/.config/yandex/tts.key
Сразу проверяем — это экономит полчаса недоумения:
curl -s -o /tmp/проба.ogg -w 'HTTP %{http_code}\n' \
-H "Authorization: Api-Key $(cat ~/.config/yandex/tts.key)" \
--data-urlencode "text=проверка связи" -d "lang=ru-RU" -d "voice=jane" -d "format=oggopus" \
https://tts.api.cloud.yandex.net/speech/v1/tts:synthesize
200 — годится. 401 — у ключа нет роли на синтез: типичная ошибка, когда берут ключ от распознавания речи, они очень похожи.
Идея простая: таблица «кто говорит — что говорит — как называется файл», и по ней генерируются mp3. Реплики персонажей лежат в обычном CSV, который можно править хоть в таблице.
файл,голос,текст
дед-01,zahar,Ну что, внучка, посмотрим, кто тут хозяин.
внучка-01,jane,Я тебя предупреждала, дедушка.
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""Пакетная озвучка: реплики.csv → mp3 в папке звук/."""
import csv, os, pathlib, subprocess, sys, time, urllib.parse, urllib.request
КЛЮЧ = open(os.path.expanduser('~/.config/yandex/tts.key')).read().strip()
URL = 'https://tts.api.cloud.yandex.net/speech/v1/tts:synthesize'
ПАПКА = pathlib.Path('звук'); ПАПКА.mkdir(exist_ok=True)
def синтез(текст, голос, скорость='1.0'):
данные = urllib.parse.urlencode({
'text': текст, 'lang': 'ru-RU', 'voice': голос,
'speed': скорость, 'format': 'oggopus'}).encode()
запрос = urllib.request.Request(URL, data=данные,
headers={'Authorization': f'Api-Key {КЛЮЧ}'})
for попытка in range(4): # 429 и 5xx лечатся паузой
try:
return urllib.request.urlopen(запрос, timeout=120).read()
except urllib.error.HTTPError as e:
if e.code in (429, 500, 502, 503) and попытка < 3:
time.sleep(2 * (попытка + 1)); continue
raise
всего_знаков = 0
for строка in csv.DictReader(open('реплики.csv', encoding='utf-8')):
цель = ПАПКА / f"{строка['файл']}.mp3"
if цель.exists(): # уже озвучено — не платим второй раз
continue
текст = строка['текст'].strip()
всего_знаков += len(текст)
ogg = ПАПКА / f"{строка['файл']}.ogg"
ogg.write_bytes(синтез(текст, строка['голос']))
subprocess.run(['ffmpeg', '-v', 'error', '-y', '-i', str(ogg),
'-b:a', '96k', str(цель)], check=True)
ogg.unlink()
print('готово:', цель)
print(f'символов синтезировано: {всего_знаков}')
python3 озвучка.py
У SpeechKit их полтора десятка. Женские — jane, alena, omazh, мужские — filipp, zahar, ermil. Для персонажей игры этого достаточно: два-три голоса плюс изменение скорости дают ощутимо разных героев.
Скорость задаётся параметром speed от 0,1 до 3,0. Замедление до 0,9 делает голос солиднее, ускорение до 1,15 — суетливее. Приём дешёвый и работает: старик и подросток могут быть одним голосом на разной скорости.
У части голосов есть амплуа — neutral, good, evil: параметр emotion. Поддерживается не всеми, поэтому проверяйте на своём голосе, прежде чем закладывать в пайплайн.
Синтез тарифицируется по числу символов — вы платите ровно за то, что озвучили, без абонентской платы. Поэтому оценка бюджета делается заранее, обычным подсчётом:
python3 - <<'PY'
import csv
n = sum(len(с['текст']) for с in csv.DictReader(open('реплики.csv', encoding='utf-8')))
print('символов:', n, '≈ страниц текста:', round(n / 1800, 1))
PY
Дальше умножаете на текущий тариф из прайса Яндекса — он меняется, и я намеренно не привожу здесь цифру, которая через полгода станет враньём. Порядок такой: сценарий игры на сто реплик — это несколько десятков тысяч символов, то есть сумма, сравнимая с чашкой кофе, а не с гонораром диктора.
Две привычки, которые экономят деньги. Не переозвучивать уже готовое — в скрипте выше это одна строка с проверкой существования файла. И не гонять черновики: пока текст правится, слушайте одну-две ключевые реплики, а не весь сценарий.
Лимит длины запроса — около пяти тысяч символов. Длинную главу надо резать: по абзацам, а если абзац огромный — по предложениям. Склеивать потом через ffmpeg.
Ответ приходит не в mp3. Версия v1 отдаёт oggopus или сырой lpcm, mp3 делает уже ffmpeg. Если mp3 не нужен — оставляйте ogg, он легче и его понимают браузеры.
429 — это не поломка, а темп. При пакетной генерации сотен файлов вы упрётесь в ограничение частоты. Ретраи с паузой в скрипте выше решают вопрос; ставить их «потом» не получится — заметите на середине большого прогона.
Ударения. Синтез иногда ошибается в омографах: «замок», «стоит», «дорога». Лечится расстановкой ударения через + перед гласной: зам+ок. Прогоните готовые файлы ушами — правки обычно нужны в двух-трёх местах из ста.
Числа и сокращения. «2026 г.» и «т. д.» читаются непредсказуемо. Проще написать словами в исходнике, чем спорить с движком.
Если из реплик нужна цельная дорожка — например, глава аудиокниги:
ls звук/глава-*.mp3 | sed "s/^/file '/;s/$/'/" > /tmp/список.txt
ffmpeg -v error -f concat -safe 0 -i /tmp/список.txt -c copy глава.mp3
ffprobe -v error -show_entries format=duration -of csv=p=0 глава.mp3
Последняя строка печатает длительность — она пригодится, если делаете оглавление с таймкодами: время начала каждой части равно сумме длительностей предыдущих.