video-voiceover
Генерирует voiceover-аудио для роликов через ElevenLabs TTS. Берёт текст, выбирает следующий голос из rotation-пула проекта (least-recently-used), возвращает готовый mp3 + точную длительность. Плюс audio_process.py — постпродакшн (убирает паузы, EQ, нормализация до -16 LUFS).
Что делает простыми словами
Это TTS-машинка с правильной ротацией. У каждого проекта свой пул голосов (например 2-3 ElevenLabs voice IDs). Когда нужно озвучить новый текст:
- Смотрит, какие голоса есть у проекта (через
project_voicesjunction) - Выбирает least-recently-used → next-voice
- Читает settings из
project.yaml.voiceover(model, speed) - Вызывает ElevenLabs API через
tts.py - Сохраняет mp3 в
project/voiceovers/ - Замеряет точную длительность через ffprobe
- Записывает usage в БД (для следующей ротации)
- Постпроцесс через
audio_process.py— короче паузы, нормализация громкости, EQ - Возвращает: путь, длительность, voice, текст
Дальше director берёт эти 4 параметра и собирает под них видео.
Voice rotation: как работает next-voice
В project_voices junction-таблице хранится не только linkage, но и usage stats: times_used, last_used_at. Когда вызываешь next-voice:
SELECT v.id, v.voice_id, v.name FROM voices v JOIN project_voices pv ON pv.voice_id = v.id WHERE pv.project_id = ? ORDER BY pv.last_used_at ASC NULLS FIRST, pv.times_used ASC LIMIT 1
Возвращается голос, который дольше всего не использовался в этом проекте. Если все равны (первый запуск) — берётся первый по порядку.
После генерации:
python ~/.claude/skills/video-voiceover/scripts/db.py record-usage \ <project-dir> <voice-db-id> <audio-path> "<text>" --duration 28.4
Это обновляет last_used_at и инкрементит times_used, плюс линкует запись в voiceover_history с проектом через project_voiceovers.
Workflow по шагам
1. Проверить голоса проекта
python ~/.claude/skills/video-voiceover/scripts/db.py list-voices <project-dir>
Если пусто — попросить юзера добавить:
python ~/.claude/skills/video-voiceover/scripts/db.py add-voice <project-dir> \ --voice-id "21m00Tcm4TlvDq8ikWAM" \ --name "Rachel" \ --language "en" \ --description "Calm, professional female voice"
voice_id юзер берёт с elevenlabs.io/voice-library или через list-available:
python ~/.claude/skills/video-voiceover/scripts/db.py list-available \ --api-key "$ELEVENLABS_API_KEY" \ --language ru
2. Выбрать следующий голос
python ~/.claude/skills/video-voiceover/scripts/db.py next-voice <project-dir>
# → {"id": 3, "voice_id": "21m00Tcm4TlvDq8ikWAM", "name": "Rachel", "language": "ru"}
3. Сгенерировать аудио
python ~/.claude/skills/video-voiceover/scripts/tts.py \ --project-dir <project-dir> \ --text "Полный текст озвучки" \ --voice-id 21m00Tcm4TlvDq8ikWAM
Возвращает путь к mp3 и точную длительность.
4. Postprocess (ОБЯЗАТЕЛЬНО)
python ~/.claude/skills/video-voiceover/scripts/audio_process.py \ --input voiceover.mp3 \ --output voiceover_processed.mp3
Использовать processed-файл для всех следующих шагов (его длительность короче из-за вырезанных пауз).
5. Отрапортовать
Возвращаешь caller'у (или юзеру):
- Audio file path
- Duration в секундах
- Какой голос использован
- Текст, который озвучен
Эти 4 параметра нужны director'у для сборки.
audio_process.py — что делает
Сырое ElevenLabs аудио имеет неприятные особенности: долгие паузы между предложениями, неровную громкость, бубнящие низы и шипящие верха. Postprocess это всё лечит за один проход:
| Этап | Что делает | Эффект |
|---|---|---|
| 1. Silence removal | Укорачивает паузы между фразами | ~5 сек экономии на 30-сек ролике |
| 2. Voice EQ | Highpass 180Hz + lowpass 9kHz + 3kHz presence boost | Убирает rumble снизу + hiss сверху, добавляет «телевизионную» чёткость |
| 3. Adaptive leveler | Динамическая нормализация громкости (как Auphonic) | Тихие места подтягиваются вверх, громкие — вниз |
| 4. Loudness normalization | EBU R128 до -16 LUFS | Broadcast standard. Соответствует тому, что воспроизводят соцсети |
audio_process.py → processed file → director.
Project settings
Настройки голоса хранятся per-project — либо в project.yaml.voiceover, либо в таблице voiceover_settings:
python ~/.claude/skills/video-voiceover/scripts/db.py get-settings <project-dir>
# → {"speed": 1.2, "stability": 0.5, "similarity_boost": 0.75, "model": "eleven_multilingual_v2"}
python ~/.claude/skills/video-voiceover/scripts/db.py set-settings <project-dir> \
--speed 1.2 \
--stability 0.5 \
--similarity-boost 0.75 \
--model "eleven_multilingual_v2"
Defaults
| Параметр | Default | Что это |
|---|---|---|
speed | 1.2 | Скорость произнесения. 1.0 — обычная, 1.2 — рекламная (динамичнее) |
stability | 0.5 | Стабильность голоса. <0.5 — экспрессия (драматичнее), >0.5 — монотоннее |
similarity_boost | 0.75 | Насколько точно следовать оригинальному голосу |
model | eleven_multilingual_v2 | Лучшее качество, 29 языков, emotional range |
ElevenLabs модели
| Модель | Best for |
|---|---|
eleven_multilingual_v2 | Default. Лучшее качество, 29 языков, emotional range |
eleven_turbo_v2_5 | Хорошее качество, ниже latency |
eleven_flash_v2_5 | Самая быстрая, real-time |
Для рекламных роликов почти всегда multilingual_v2 — на 15-60-секундный текст latency не критичен, качество важнее.
Voice management команды
# Добавить голос в пул проекта python ~/.claude/skills/video-voiceover/scripts/db.py add-voice <project-dir> \ --voice-id "ID" --name "Name" --language "ru" --description "Description" # Список голосов с usage-статистикой python ~/.claude/skills/video-voiceover/scripts/db.py list-voices <project-dir> # Удалить голос из пула python ~/.claude/skills/video-voiceover/scripts/db.py remove-voice <project-dir> <voice-db-id> # Просмотреть ElevenLabs voice library python ~/.claude/skills/video-voiceover/scripts/db.py list-available \ --api-key "$ELEVENLABS_API_KEY" --language ru
Внутренности
Скрипты
| Файл | Что делает |
|---|---|
scripts/tts.py | Главный воркхорс: вызывает ElevenLabs API, сохраняет mp3, замеряет длительность через ffprobe |
scripts/audio_process.py | Постпродакшн: silence-removal + EQ + leveler + loudnorm |
scripts/db.py | CRUD по голосам (project-scoped), rotation (next-voice), settings, usage history |
scripts/api_keys.py | Резолвит ELEVENLABS_API_KEY из env или project/api_keys.db |
scripts/schema.py | Schema source-of-truth (v8) — копия одинакова во всех скиллах |
Таблицы БД, которые трогает
| Таблица | Действие | Зачем |
|---|---|---|
voices | Read / INSERT | Каталог всех голосов (ID, имя, язык, описание) |
project_voices | Read / INSERT / UPDATE | Junction + usage stats (times_used, last_used_at) |
voiceover_history | INSERT | Запись каждой генерации (text, voice, path, duration) |
project_voiceovers | INSERT | Junction: scope audio к проекту |
voiceover_settings | Read / UPDATE | Per-project settings (speed/stability/model) |
Конфиг из project.yaml
language: "ru" # язык TTS voiceover: provider: "elevenlabs" # пока единственный model: "eleven_multilingual_v2" # → ElevenLabs API speed: 1.2 # рекламный темп
Если в yaml нет блока voiceover — используются дефолты, либо значения из таблицы voiceover_settings (последняя имеет приоритет, если есть).
Output / Pipeline
ElevenLabs TTS → voiceover.mp3 (raw, длинный, неровный)
↓
audio_process.py → voiceover_processed.mp3 (короче, нормализованный, EQ-ed)
↓
Director (assemble.py) — использует processed-файл
Файлы лежат в:
~/video-projects/<project>/voiceovers/<timestamp>_<voice-name>.mp3 ~/video-projects/<project>/voiceovers/<timestamp>_<voice-name>_processed.mp3
Gotchas и tips
audio_process.py = обязательный шаг, не опциональный.
ELEVENLABS_API_KEY ничего не работает. Скрипт api_keys.py сначала смотрит env, потом project/api_keys.db. Если нет нигде — кидает осмысленную ошибку «set ELEVENLABS_API_KEY».
add-voice указывай --language правильно, и фильтруй list-voices / list-available по нему.
video_clips). Оба пула обновляются независимо — голос Rachel может крутиться 3-й раз подряд, а клипы все разные (или наоборот).
🎬 Примеры работы
Место в пайплайне
Voiceover = мост между текстом и видео. Принимает:
text + voice_id. Отдаёт: mp3 + duration + voice + text. Director без этих 4 параметров не знает, под что монтировать.
Сверху над voiceover обычно стоит orchestrator, который сам вызывает next-voice → tts.py → audio_process.py → record-usage в одном проходе. Снизу — director и captions (последний использует тот же processed-файл для Whisper-замера слов в караоке-субтитрах).