video-captions
Накладывает анимированные субтитры на видео локально через Whisper + ASS + FFmpeg или Remotion. Karaoke-стиль с подсветкой по словам, без внешних API, без расходов. Финальный шаг основного видео-пайплайна.
Что делает простыми словами
Это «титровальщик». На вход: видео с озвучкой + текст копирайтера. На выход: то же видео, но с анимированными субтитрами, где активное слово подсвечивается синхронно с речью. Алгоритм:
- Прогоняет аудио через Whisper → получает word-level timestamps (start/end на каждое слово)
- Берёт оригинальный текст копирайтера (не транскрипт Whisper!) и подставляет в найденные тайминги
- Группирует слова в фразы (2-4 на строку для karaoke / 1 для pop)
- Генерирует ASS-файл (Advanced SubStation Alpha) с karaoke-тегами
\kfдля подсветки - FFmpeg либо Remotion прожигает субтитры в кадры → готовое
.mp4
--text с оригинальным копирайтерским текстом. Whisper делает ошибки распознавания (например, «Баздент» вместо «Bas Dent», «с травман» вместо «Straumann»). Текст для отображения берётся из --text, Whisper нужен ТОЛЬКО для тайминга. Без --text — в финальном видео будут косяки транскрипции.
Два рендерера
| Скрипт | Движок | Скорость | Гибкость | Когда |
|---|---|---|---|---|
remotion_captions.pydefault |
Remotion (React) | 2-5 минут | Полная — spring physics, фоны, любые анимации, sentence-aware группировка (не разрывает предложения между caption-страницами) | Финальная доставка, соцсети, когда визуал важен |
local_captions.py |
FFmpeg + ASS burn-in | Секунды | Ограничена ASS-форматом — цвет, обводка, тень, karaoke-подсветка. Никаких физических анимаций. | Fallback / быстрый драфт, bulk-производство |
remotion_captions.py. Это user-preference. Внутри captions-скилла не использовать local_captions.py (ASS burn-in), если только пользователь явно не попросит fallback. Также не использовать встроенные субтитры из assemble.py в director — это отдельный шаг, делается этим скиллом.
Стили субтитров
| Style | Описание | Лучше для |
|---|---|---|
karaoke | Подсветка по словам жёлтым на белом тексте + чёрная обводка, 60px, снизу. Универсал. | Дефолт — работает для всего |
bold | То же что karaoke, но 70px и обводка толще. Бьёт по глазам. | Короткие ролики, hook-фокус |
minimal | Маленький белый текст 40px, тонкая обводка. Деликатно. | Корпоратив, B2B |
pop | По одному слову по центру, 80px, scale-in анимация. | TikTok / Reels стиль |
tiktok | Золотая подсветка на активном слове, 2-3 слова в группе, pop-in scale, полупрозрачный бэкграунд-бокс, центр, 72px. | Соцсети, виральный контент |
karaoke с --highlight-color "#FFFFFF": активное слово окрашивается в тот же цвет что и неактивный текст, остаётся только пульсация размера (scale-pop). Без жёлтой/cyan вспышки. Использует video-screencast по умолчанию.
Команды
Основной вызов (Remotion-режим, дефолт)
python ~/.claude/skills/video-captions/scripts/remotion_captions.py \ --video assembled_video.mp4 \ --text "$(cat copywriter_original.txt)" \ --style karaoke \ --language ru \ --model small \ --project-dir ~/video-projects/bas_dent \ --output captioned.mp4
FFmpeg fallback (быстрый, ASS burn-in)
python ~/.claude/skills/video-captions/scripts/local_captions.py \ --video assembled_video.mp4 \ --text "$(cat copywriter_original.txt)" \ --style karaoke \ --language ru \ --model small \ --project-dir ~/video-projects/bas_dent
Monochrome scale-pop (для talking-head)
python ~/.claude/skills/video-captions/scripts/remotion_captions.py \ --video cleaned_talking_head.mp4 \ --text "$(cat canonical.txt)" \ --style karaoke \ --highlight-color "#FFFFFF" \ --font-size 64 \ --project-dir ~/video-projects/bas_dent \ --output captioned.mp4
Hook overlay (анимированный заголовок на первой сцене)
python ~/.claude/skills/video-captions/scripts/hook_overlay.py \ --video assembled_video.mp4 \ --text "$(cat voiceover_full.txt)" \ --voiceover voiceover.mp3 \ --hook-style neon \ --sfx-file ~/.claude/skills/video-soundfx/sfx/text/pop_bright.mp3 \ --project-dir ~/video-projects/bas_dent \ --language ru
Пропустить captions во время hook-сцены
python ~/.claude/skills/video-captions/scripts/remotion_captions.py \ --video video_with_hook.mp4 \ --text "..." \ --style karaoke \ --skip-until 4.9 \ --project-dir ~/video-projects/bas_dent
Полные опции
--style karaoke|bold|minimal|pop|tiktok # анимация (default: karaoke) --font "Arial Black" # имя шрифта --font-size 60 # размер в пикселях --highlight-color "&H0000FFFF" # ASS-цвет активного слова (жёлтый) --primary-color "&H00FFFFFF" # ASS-цвет обычного текста (белый) --model tiny|base|small|medium|large # Whisper-модель (default: small) --language ru|en|kk|uk # язык для Whisper hint --text "..." # ОРИГИНАЛЬНЫЙ текст копирайтера (обязательно!) --skip-until 4.9 # пропустить субтитры до X сек (для hook overlay)
Hook templates (15 пресетов)
Hook overlay — анимированный заголовок на первой сцене ролика. Слова влетают по одному со spring-анимацией + SFX. Применяется только к первому предложению из voiceover-текста.
| Style | Шрифты | Анимация | Характер |
|---|---|---|---|
neon | Bebas+Oswald+Bangers | zoom-in | Неоновое свечение, розовый/cyan/gold |
marker | PermanentMarker+LilitaOne | scale-slam | Маркерный, жёлтый/коралл |
cyber | ChakraPetch+PaytoneOne | slide-right | Кибер, cyan/фиолет |
gradient | Oswald+Bangers+Bebas | drop | Контраст, фиолет/белый/коралл |
stencil | Bangers | slide-left | Трафарет, белый/красный/жёлтый |
Hook overlay автоматически:
- Достаёт первое предложение из voiceover-текста
- Берёт word-level timestamps через Whisper
- Классифицирует слова на «key» (большие, цветные) и «filler» (мелкие, белые)
- Рендерит с per-word spring-анимациями + опциональный SFX pop
- Возвращает
hook_end_secдля--skip-untilв шаге captions
Скрипты
| Файл | Что делает |
|---|---|
scripts/remotion_captions.py default | Главный воркхорс. Whisper тайминги → JSON props → npx remotion render. Качественные React-анимации, sentence-aware группировка. |
scripts/local_captions.py | Fallback FFmpeg-режим. Whisper → ASS-файл с karaoke-тегами → ffmpeg -vf "ass=..." burn-in. |
scripts/transcribe_words.py | Обёртка над Whisper для получения чистого word-level транскрипта (используется hook_overlay и captions_api). |
scripts/captions_api.py | API-уровень: парсит --text копирайтера, мэтчит на тайминги Whisper, отдаёт нормализованные phrases для рендера. |
scripts/hook_overlay.py | Рендерит анимированный заголовок на первой сцене. 5 hook-стилей (neon/marker/cyber/gradient/stencil), per-word spring, SFX pops. |
scripts/render_final.py | Финальный композ для multi-stage пайплайна (hook + captions + музыка в одном проходе). |
Цвета в ASS-формате
ASS использует &HAABBGGRR (alpha, blue, green, red — обратный порядок к RGB):
| Цвет | ASS | Hex (для Remotion-режима) |
|---|---|---|
| Белый | &H00FFFFFF | #FFFFFF |
| Жёлтый | &H0000FFFF | #FFFF00 |
| Cyan | &H00FFFF00 | #00FFFF |
| Красный | &H000000FF | #FF0000 |
| Зелёный | &H0000FF00 | #00FF00 |
Whisper-модели
| Модель | Размер | Скорость | Точность | Когда |
|---|---|---|---|---|
tiny | 39MB | самая быстрая | базовая | быстрый тест |
base | 74MB | быстрая | хорошая | драфт |
small | 461MB | средняя | отличная | default — лучший баланс |
medium | 1.5GB | медленная | почти идеальная | когда точность критична |
large | 2.9GB | самая медленная | лучшая | критический контент |
Первый запуск качает модель (~461MB для small) в ~/.cache/whisper/ — потом из кэша.
Конфиг из project.yaml
Captions читают из ~/video-projects/<project>/project.yaml:
language: ru # язык для Whisper hint captions: style: karaoke # karaoke|bold|minimal|pop|tiktok font: "Arial Black" # имя шрифта (опционально) font_size: 60 # размер в пикселях highlight_color: "&H0000FFFF" # жёлтый (default для karaoke) primary_color: "&H00FFFFFF" # белый model: small # Whisper-модель
Gotchas и tips
--text ничего не делать. Whisper-транскрипт ≠ копирайтерский текст. Бренды/имена/термины распознаются с ошибками. Всегда копировать текст из voiceover/copywriter и передавать через --text. Это user-preference, зашитое в memory.
ffmpeg из Homebrew часто без libass. Нужен ffmpeg-full: brew install ffmpeg-full. Проверка: ffmpeg -filters | grep ass должна показывать subtitle-фильтр.
ffmpeg -i input.mp4 -vf "ass=subtitles.ass" -c:v libx264 -crf 23 output.mp4.
🎬 Примеры работы
Место в пайплайне
Captions = финальный шаг основного видео-пайплайна. Вход:
assembled.mp4 + copywriter_text.txt. Выход: captioned.mp4 с прожжёнными анимированными субтитрами. Дальше — только reviewer для контроля качества.
Также используется в screencast-пайплайне для talking-head контента — обычно с --style karaoke --highlight-color "#FFFFFF" (monochrome scale-pop):
В этом маршруте captions работают по чистому видео с реальным микрофоном (после voice cleanup).