Голос диктора (references/voice.md)

Источник: ds-internal/presentations/component-video-skill/references/voice.md — справочник скилла astra-component-video

Справочник к объясняющим роликам. Голос синтезируется локально, без внешних сервисов: тексты не уходят из компании, у ролика нет подписки и лимитов.

Какой движок

Как звучитживой голос, без акцентабыстро, но слышно «машинность» и акцент
Голоспо описанию: VoiceDesign создаёт эталон, Base читает им все фразыпять готовых: baya, kseniya, xenia, aidar, eugene
Латиницачитает: React, Flutter, YAML — как естьне читает: только кириллица, «Реакт», «Фигма»
Железовидеокарта NVIDIA, 4,4 ГБ памятипроцессор
Скоростьминута голоса ≈ 5 минут25 фраз ≈ 10 секунд
ЛицензияApache 2.0 — можно публиковатьCC BY-NC 4.0 — только внутренние ролики

Автор роликов Silero забраковал: «ломанный, слышен акцент». Брать Qwen3. Silero — когда нет видеокарты и нужен черновик, чтобы проверить хронометраж.

Установка — один раз

Голосу нужно отдельное окружение Python с torch: в основное его не ставить. Путь к его интерпретатору — в переменной ASTRA_TTS_PYTHON, по ней build.mjs запускает озвучку.

Qwen3-TTS:

python -m venv <папка>/qwen-tts-venv
<venv>/python -m pip install torch torchaudio --index-url https://download.pytorch.org/whl/cu128
<venv>/python -m pip install qwen-tts soundfile numpy scipy
huggingface-cli download Qwen/Qwen3-TTS-12Hz-1.7B-VoiceDesign --local-dir <модели>/Qwen3-TTS-12Hz-1.7B-VoiceDesign
huggingface-cli download Qwen/Qwen3-TTS-12Hz-1.7B-Base --local-dir <модели>/Qwen3-TTS-12Hz-1.7B-Base
huggingface-cli download Qwen/Qwen3-TTS-Tokenizer-12Hz --local-dir <модели>/Qwen3-TTS-Tokenizer-12Hz

Переменные: ASTRA_TTS_PYTHON=<venv>/python (в Windows — <venv>\Scripts\python.exe), ASTRA_QWEN_DIR=<модели>. Около 9 ГБ моделей.

  • Видеокарты RTX 50xx (Blackwell, sm_120) видит только torch со сборкой CUDA 12.8 (cu128): старые сборки молча работают на процессоре или падают.
  • flash-attn под Windows не ставится — voice.py включает sdpa, это нормально.
  • Предупреждение SoX при импорте qwen_tts — не мешает.
  • Проверено: torch 2.11 cu128, qwen-tts 0.1.1, transformers 4.57, RTX 5060 Ti.

Silero:

python -m venv <папка>/tts-venv
<venv>/python -m pip install torch numpy scipy
curl -sSL -o <модели>/v5_6_ru.pt https://models.silero.ai/models/tts/ru/v5_6_ru.pt

Переменные: ASTRA_TTS_PYTHON, ASTRA_TTS_MODEL=<модели>/v5_6_ru.pt (148 МБ).

Проверка Whisper'ом: pip install faster-whisper в любое из этих окружений. Модель small (около 500 МБ) скачается при первом запуске.

Без окружения голоса ролик с уже озвученными фразами всё равно собирается: build.mjs видит, что тексты не менялись, и модель не запускает. Так собирается образец design-contract-short.

Настройки в данных ролика

narrator: {
  engine: "qwen3",
  design: "A calm, confident male narrator in his mid-thirties. Warm, clear voice, native Russian speaker with natural pronunciation and no accent. Moderate pace, friendly explainer tone, like a professional documentary voice-over. Studio-quality recording, no background noise.",
  sample: "Привет! Сегодня я расскажу, как дизайн и разработка договариваются о компоненте. Это займёт пару минут.",
  tempo: 1.3,
},
voice: "Фраза на обложке.",          // звучит на обложке
outroVoice: "Фраза на финале.",       // звучит на финале, необязательно
slides: [{ …, voice: ["Фраза 1.", "Фраза 2."] }],
engineqwen3 или silero (по умолчанию silero)
designописание голоса по-английски: пол, возраст, тембр, манера, «native Russian speaker … no accent», качество записи. Сверху — голос роликов Astra DS: брать его, пока автор не попросил другой
sampleрусская фраза-эталон, 15–20 слов в манере ролика: ею VoiceDesign задаёт голос
tempoво сколько раз быстрее синтеза: 1,3 — утверждённый темп. Ускоряет фильтр atempo ffmpeg, высота голоса не меняется; новый темп не требует нового синтеза
speakerголос Silero, по умолчанию baya (ноль ошибок распознавания на пробной фразе; у kseniya тоже)

Смена design или sample — новый эталон и переозвучка всего ролика. Пробы двух голосов (мужской и женский) перед выбором — короткий ролик на 3–4 фразы с тем же текстом.

Как голос ведёт картинку

  • Фразы части встают подряд: 0,3 с до первой, 0,2 между, 0,5 после последней.
  • Сцена идёт за голосом: кончается с последней фразой, а не досиживает анимацию. Последнее действие на экране видно ещё секунду; hold: 2.5 у сцены — дольше, например для финальной мысли.
  • beat: k у элемента сцены — элемент встаёт на k-й фразе (с нуля). Несколько элементов с одним beat встают равномерно по первым 70 % фразы. Без beat — по своему ритму.
  • focus.delay у chain — сдвиг выделения от начала фразы, если на ней же появляется последний шаг.
  • Обложка длится не меньше своей фразы; финал — не меньше outroVoice.

Порядок сборки — голос раньше музыки: озвучка → метки и длина → музыка по меткам → сведение. build.mjs проходит его сам. Вручную:

<ASTRA_TTS_PYTHON> -I -X utf8 tools/voice.py say videos/<ролик>.js
node tools/cues.mjs
python -I -X utf8 tools/synth_music.py videos/<ролик>.cues.json assets/audio/<ролик>-music.wav
python -I -X utf8 tools/voice.py mix videos/<ролик>.cues.json assets/audio/<ролик>-music.wav assets/audio/<ролик>.wav
node tools/cues.mjs
  • say пишет фразы в assets/<ролик>/voice/ (синтез — в raw/, с темпом — рядом), длительности — в videos/<ролик>.voice.js. Этот файл создаёт только voice.py: руками не править. Повторный say переозвучивает только изменённые фразы.
  • В index.html у ролика с голосом строка <script src="videos/<ролик>.voice.js"> после файла данных; build.mjs ставит и убирает её сам.
  • mix выравнивает фразы по громкости, приглушает музыку под рассказом на 9 дБ целиком и ещё на 7 дБ на самих фразах (в паузах не «качает»), выводит рассказ к −16 dBFS RMS, пик не выше −1 dBFS. Фраза, чья длина разошлась с метками, останавливает свод: метки устарели — снять заново.

Время на ролик в три минуты: озвучка Qwen3 — около 15 минут (только в первый раз; потом — изменённые фразы), музыка, сведение и снимки — около минуты, рендер — около 10 минут. Долгое — в фоне.

Как писать текст для голоса

Общие правила — references/infostyle.md, раздел 8. Для модели:

  • фраза 8–15 слов, одна мысль; «но», «а», «поэтому» в начале — можно;
  • числа — словами: «двести шестьдесят четыре», так вернее склонение (Silero цифры пропускает);
  • «ИИ» модель читает как одно «и» — в голосе «нейросеть»;
  • Silero: только кириллица, Figma — «Фигма», React — «Реакт»; ударение — плюсом перед гласной: +оси стан+овятся. Qwen3 плюсы убирает сам;
  • короткие слова вроде «ось» модель проглатывает — фразу перестроить;
  • имена собственные сливаются («Натан Кёртис» → «на танке Ортис» у Silero) — проверить распознаванием, при нужде перестроить;
  • скорость — около 12 знаков в секунду при темпе 1: минута рассказа ≈ 700 знаков;
  • на экране — то же, что звучит, только короче: голос «Мы берём компонент из Фигмы вместе с его описанием» → карточка «Figma · компонент и его описание».

Проверка без прослушивания

<python с faster-whisper> -I -X utf8 tools/voice_check.py <ролик>

Whisper распознаёт каждую фразу и сравнивает с текстом, около двух секунд на фразу. Выводит:

  • ✗ — слова разошлись: прослушать фразу, явную ошибку (слово пропало, слилось, другое ударение) исправить — перестроить фразу и переозвучить;
  • ~ — разошлись только цифры или латиница: Whisper пишет «264» и «Skill», это не ошибка голоса.

Порог совпадения — 0,8 (--min). На ролике design-contract-v3 (58 фраз, 21.10.2026) отмечено 4 фразы ✗: «из Фигмы» прозвучало как «с фигмы» (дважды), имя «Кристин Валлор», «Скилл».

Лицензии

В отчёте назвать модель и лицензию: Qwen3-TTS — Apache 2.0; Silero v5_6_ru — CC BY-NC 4.0, ролик только для внутреннего пользования. Голос по описанию синтетический — чужой голос не клонируется; модели с клонированием по образцу (ESpeech-TTS, F5-TTS) автор брать не стал.