# Video Montage

> Конвейер ролика ролями: бриф→сценарий→музыка→раскадровка→монтаж; reels 9:16. Триггеры: «собери рилс», «рилс с нуля». НЕ: футаж→video-editor; AI-сцены→video-generation.

- Skill: `jhamidun/video-montage-2` (Agent Skill, multi-file: 19 files)
- Install (CLI): `npx skillmds@latest add jhamidun/video-montage-2`
- Raw SKILL.md: https://api.skillmd.com/api/skills/jhamidun/video-montage-2/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: JHamidun (https://skillmd.com/u/jhamidun)
- Updated: 2026-09-22
- Page: https://skillmd.com/skills/jhamidun/video-montage-2

---


# Video Montage — Full Production Pipeline

Вертикальные ролики (9:16, 1080×1920, 30fps) от исходников до готового файла.

> **Windows-адаптация (стек пользователя):** `python` вместо `python3`; headless
> Chrome = `"C:/Program Files/Google/Chrome/Application/chrome.exe"` (те же флаги);
> временные файлы → scratchpad, не `/tmp`; ElevenLabs-ключ `ELEVENLABS_API_KEY`
> из `~/.claude/.credentials.master.env`, свой голос — см. навык `elevenlabs`;
> правило кодека то же: только `libx264 + yuv420p`. WhisperX уже стоит (см.
> `video-editor` karaoke_captions). Смежное: монтаж готового футажа →
> `video-editor`; AI-генерация сцен → `video-generation`; музыка → `ace-step`/`elevenlabs`;
> публикация в Telegram-канал → `tg-bot-publish`, в остальные соцсети — своим
> публикатором (например, Postiz self-hosted), готовой обёртки в паке нет.

**Требуется:** ffmpeg (с libass и drawtext) · whisper (`pip install openai-whisper`) ·
Python с Pillow · yt-dlp · ключ ElevenLabs для озвучки.

---

## 0. Конвейер целиком: одна задача — весь ролик

Когда просьба звучит как «сделай ролик про X», а не как отдельная операция монтажа, не
выбирай инструменты вручную — запускай конвейер. Он сам разберёт задачу на роли и
проведёт её от брифа до готового файла.

```
Workflow({
  scriptPath: '~/.claude/skills/video-montage/workflows/video-factory.js',
  args: { brief: 'как за день собрали лендинг вайбкодингом',
          seconds: 35, platform: 'reels', goal: 'watch',
          workdir: '~/Videos/video-factory/lending-35s' }
})
```

Обрыв не страшен: `Workflow({scriptPath, resumeFromRunId})` продолжит с места, а готовые
кадры помечены в конверте и второй раз не оплачиваются.

**Роли и порядок** (полностью — `references/pipeline-architecture.md`):

```
бриф ─┬─ сценарий ─┐
      └─ музыка ───┴─ раскадровка ─ кадры (волнами по 4) ─ монтаж ─ контроль
```

Сценарий и музыка идут одновременно; кадры независимы друг от друга и обрабатываются
волнами — больше шести агентов разом упирается в ограничение сервера. Барьеры стоят
только там, где они честно нужны: раскадровке нужны и структура, и сетка долей.

**Единый документ между ролями** — `schemas/production.schema.json`. Каждая роль пишет
свой раздел и не трогает чужие; это и делает параллельность возможной, и позволяет
перезапускать этапы поодиночке.

**Ремесленная часть** — в каталоге приёмов, 79 записей с источником и статусом
доказательности у каждой:

```bash
python scripts/techniques.py stats
python scripts/techniques.py find "не держится внимание"
python scripts/techniques.py for-block крючок
```

**Структура ролика** строится не по AIDA: та описывает убеждение покупателя (печатная
реклама, 1898) и оптимизирует намерение купить — величину, которую лента не измеряет.
Площадки официально называют главным сигналом досмотр, поэтому план строится вокруг
удержания:

```bash
python scripts/reel_structure.py --message "…" --seconds 35 --goal shares --music track.mp3
python scripts/reel_structure.py --why      # откуда взято каждое число
```

### Приёмка: посмотреть и послушать готовый файл

Последний шаг любого ролика — не «сборка прошла без ошибок», а собственный просмотр и
прослушивание. Сборка молчит о том, что видно и слышно с первой секунды: у персонажа
белый прямоугольник вместо фона, титры серые в момент появления, речь не совпадает с
картинкой, последние семь секунд немые. Всё это даёт нулевой код возврата.

```bash
python scripts/review_cut.py reel.mp4 -o review/ --listen
```

Выдаёт контактный лист (весь ролик одной сеткой — открыть и посмотреть), замеры звука
(громкость, пики, тишина) и разбор дорожки на слух. Три источника не заменяют друг
друга: замеры не слышат кашу в речи, слух не видит призрака в кадре, глаз не измеряет
перегруз. Возраст файла печатается первой строкой — если рендер упал, на диске лежит
прошлая сборка, и приёмка бодро отчитается по ней.

### Липсинк без сторонних платформ

Пока рот на рисунке неподвижен, персонаж читается как мёртвая картинка — это первое,
за что цепляется глаз. Рот открывается по громкости голоса; фонемы для рисованного
персонажа не нужны, так делают в рисованной анимации с начала звукового кино.

```bash
python scripts/mouth_map.py ./poses -o mouth.json --draw check/   # где рот на каждой позе
python scripts/voice_envelope.py voice.mp3 --fps 30 --duration 20 -o env.json
python scripts/cutout.py ./poses -o ./poses_cut                   # убрать фон
python scripts/cutout.py ./stickers -o ./stickers_cut --outline 14 # наклейка с каймой
```

Разметку рта обязательно смотреть глазами через `--draw`: модель отвечает уверенно и
в тех случаях, когда промахнулась.

### Озвучка, попадающая в титры

Монолитная озвучка расходится с картинкой: диктор говорит со своей скоростью, титры
идут со своей. Каждая фраза синтезируется отдельно и ставится в момент своего титра, а
хронометраж подгоняется под реальную речь.

```bash
python scripts/voice_timed.py script.json -o voice.mp3 --duration 20 --retime script_fit.json
```

`--retime` возвращает сценарий, ужатый под то, как реально звучит речь: без него
половина ролика — паузы, и на слух он затянут, хотя каждый кусок нормальный.

---

## 1. Субтитры

### 1.1 Расшифровка

Whisper кормить только 16 kHz моно WAV:

```bash
ffmpeg -y -i video.MOV -ar 16000 -ac 1 -c:a pcm_s16le work/audio.wav
```

| Модель | Когда | Скорость |
|-------|-------------|-------|
| `small` | быстрая проверка, короткие клипы | быстро |
| `medium` | один говорящий в камеру | средне |
| `large-v3` | несколько голосов, реплики ИИ-ассистентов, тихий/далёкий звук | ~10× медленнее medium |

**Всегда `--initial_prompt`** со словами из предметной области — он резко снижает
выдумывание на терминах и именах, которых модель не ждёт:

```bash
whisper work/audio.wav --model medium --language ru --output_format srt --output_dir work/ \
  --initial_prompt "имена, названия продуктов, сленг, технические термины"
```

### 1.2 Правка ошибок

**Сырой вывод Whisper использовать нельзя** — ошибки выглядят правдоподобно и
проходят мимо взгляда. Прочитать каждую строку; типовые поломки: бренды в кашу
(«код-код-экси» → «Claude Code, Codex»), сленг мимо («позадрочишь» →
«позадротишь»), числа словами («сторилл соус» → «сто рилсов»), неверные границы
слов («вайп-код и шпродук» → «вайб-кодить продукт»), пропущенная тихая реплика
ИИ (перезапустить на `large-v3`).

Исправленный файл сохраняется как `audio_corrected.srt` — дальше по конвейеру
источник истины только он.

### 1.3 SRT → ASS

```bash
python scripts/gen_subs.py input.srt output.ass [--font helvetica] [--offset 5.0] [--max-words 2]
```

По умолчанию 3 слова на титр (темп TikTok), `--offset` сдвигает всё на длину
интро, `--max-words 2` замедляет.

| Стиль | Шрифт | Кегль | Обводка | Для чего |
|-------|------|------|---------|----------|
| `impact` (по умолчанию) | Impact Bold | 90pt | 8px чёрная | мем/TikTok, ролики с закадром |
| `helvetica` | Helvetica Neue Bold | 80pt | 3px чёрная | говорящая голова, диалоги, чистый вид |

### 1.4 Вжечь в видео

```bash
ffmpeg -y -i source_video.MOV -vf "format=yuv420p,ass=subs.ass" \
  -c:v libx264 -preset medium -crf 18 -c:a aac -b:a 192k \
  -movflags +faststart output_with_subs.mp4
```

---

## 2. Озвучка (ElevenLabs)

### 2.1 Никогда не монолитом

Не синтезируй одним вызовом речь длиннее 15 секунд: фразы слипаются, темп не
управляется. Режь на сегменты и размечай паузы:

```
[seg] Первое предложение. [pause:short]
[seg] Второе, та же мысль. [pause:medium]
[seg] Новая тема. [pause:long]
[seg] Вывод или призыв.
```

| Пауза | Длина | Когда |
|-------|----------|-------------|
| short | 0.16s | между фразами внутри одной мысли |
| medium | 0.34s | между темами |
| long | 0.55s | перед призывом, ключевой цифрой, выводом |

```bash
ffmpeg -y -f lavfi -i anullsrc=r=44100:cl=mono -t 0.16 -q:a 9 silence_short.mp3
```

### 2.2 Синтез сегмента

```bash
curl -s -X POST "https://api.elevenlabs.io/v1/text-to-speech/$VOICE_ID" \
  -H "xi-api-key: $ELEVENLABS_API_KEY" -H "Content-Type: application/json" \
  -d '{"text":"Текст сегмента.","model_id":"eleven_multilingual_v2",
       "voice_settings":{"stability":0.5,"similarity_boost":0.75,"style":0.0,"use_speaker_boost":true}}' \
  --output segment_01.mp3
```

`style` 0.2-0.4 — энергичнее, `stability` 0.6-0.7 — спокойнее и ровнее. Выше
`style: 0.8` начинается искажение.

### 2.3 Склейка и темп

Собери concat-лист, чередуя сегменты и файлы тишины, склей и ускорь:

```bash
ffmpeg -y -f concat -safe 0 -i concat.txt -c:a libmp3lame -q:a 2 vo_raw.mp3
ffmpeg -y -i vo_raw.mp3 -af "atempo=1.20" vo_final.mp3
```

1.15-1.25× звучит естественно. Выше 1.40× — роботный голос.

Знаки препинания темпом не управляют: ElevenLabs игнорирует большинство пауз,
поэтому файлы тишины обязательны.

---

## 3. Сборка видео

### 3.1 Подготовка клипов

Все клипы приводятся к постоянным 30fps ДО любой склейки — смесь VFR и CFR даёт
подвисшие и продублированные кадры:

```bash
ffmpeg -y -i clip.MOV \
  -vf "scale=1080:1920:force_original_aspect_ratio=increase,crop=1080:1920,fps=30" \
  -c:v libx264 -crf 18 -preset fast \
  -color_range 2 -colorspace bt709 -color_trc bt709 -color_primaries bt709 \
  -c:a aac -b:a 192k clip_prepped.mp4
```

**Вертикальные клипы с телефона:** ffprobe покажет 1920×1080 + rotation=-90.
Метаданным поворота не верить — вытащи кадр и посмотри:
`ffmpeg -ss 1 -i clip.MOV -vframes 1 work/check.jpg`.

### 3.2 Ken Burns для статики

Статичный кадр держит внимание 5-7 секунд, дальше нужен медленный зум:

```bash
ffmpeg -y -loop 1 -i photo.jpg -t 6 \
  -vf "scale=1120:1992,zoompan=z='min(zoom+0.0008,1.05)':d=180:x='iw/2-(iw/zoom/2)':y='ih/2-(ih/zoom/2)':s=1080x1920,fps=30" \
  -c:v libx264 -crf 18 -pix_fmt yuv420p ken_burns.mp4
```

Исходник масштабируется с запасом (1120×1992 при кадре 1080×1920), иначе на зуме
вылезут края.

### 3.3 Картинка под текст закадра

~80 % клипов должны прямо иллюстрировать то, о чём говорит диктор сейчас, ~20 % —
перебивки (зал, ходьба, печатание) для разнообразия между темами. Порядок работы:
расписать таймлайн (`0:00-0:15 тема A, 0:15-0:40 тема B, 0:40-1:00 призыв`),
разложить клипы по слотам, чередовать ракурсы (с рук / со штатива).

### 3.4 Склейка

```bash
# все клипы уже приведены к одному кодеку → без перекодировки
ffmpeg -y -f concat -safe 0 -i concat.txt -c copy body.mp4
```

Если кодеки разные — `-filter_complex "[0:v][0:a][1:v][1:a]concat=n=2:v=1:a=1[v][a]"`
с перекодировкой.

### 3.5 Оверлеи интро/аутро

```bash
ffmpeg -y -i bg_video.mp4 -i title.png -i logo.png -filter_complex "
    [0:v][1:v]overlay=x=(W-w)/2:y=300:enable='between(t,0,5)'[tmp];
    [tmp][2:v]overlay=x=(W-w)/2:y=800:enable='between(t,1,5)'" \
  -c:v libx264 -crf 18 -c:a copy intro.mp4
```

---

## 4. Текстовые ролики

B-roll + стилизованный текст + музыка, без закадра. Для цитат, советов, цифр,
списков.

```bash
python scripts/gen_text_overlay.py --text "Цитата" --output overlay.png \
  [--font Impact] [--font-size 72] [--position lower_third] [--max-chars 25]
```

11 пресетов шрифтов, автоуменьшение кегля при переполнении (стоп на 36pt),
перенос по предложениям, обводка + тень, три позиции (center, lower_third,
upper_third).

Фон под текст затемнить, иначе текст не читается на светлых кадрах:

```bash
ffmpeg -y -i clip.mp4 -i text_overlay.png -filter_complex "
    [0:v]drawbox=c=black@0.27:t=fill[dimmed];
    [dimmed][1:v]overlay=0:0" -c:v libx264 -crf 18 -c:a copy text_reel.mp4
```

0.27 — рабочее значение по умолчанию для текстовых роликов.

Забрать звук у трендового ролика: `yt-dlp -f bestaudio -o "source_audio.%(ext)s" <url>`
(или `ffmpeg -i reel.mp4 -vn -c:a copy source_audio.m4a`), подмешать через
`[1:a]volume=0.15[bgm];[0:a][bgm]amix=inputs=2:duration=first[a]`.

---

## 5. Звук

### 5.1 Уровень музыки

| Тип контента | Громкость BGM |
|-------------|-----------|
| Ролик с закадром | 5-8 % |
| Текстовый ролик (без голоса) | 15-20 % |
| Драматический момент | 3-5 % (приседает под ключевой фразой) |

```bash
ffmpeg -y -i body.mp4 -i bgm.mp3 -filter_complex "
    [1:a]aloop=loop=-1:size=2e+09,atrim=duration=60,volume=0.08[bgm];
    [0:a][bgm]amix=inputs=2:duration=first:dropout_transition=2[a]" \
  -map 0:v -map "[a]" -c:v copy -c:a aac -b:a 192k final.mp4
```

### 5.2 Звук в конкретный момент

```bash
ffmpeg -y -i body.mp4 -i sfx.mp3 -filter_complex "
    [1:a]volume=0.25,adelay=2000|2000[sfx];
    [0:a][sfx]amix=inputs=2:duration=first[a]" \
  -map 0:v -map "[a]" -c:v copy -c:a aac body_with_sfx.mp4
```

`adelay=2000|2000` — 2 секунды, в миллисекундах, значение на каждый канал
отдельно (один канал = звук поедет только слева).

### 5.3 Чистка закадра

```bash
# паузы длиннее 0.3 с
ffmpeg -i vo.mp3 -af "silencedetect=noise=-30dB:d=0.3" -f null - 2>&1 | grep "silence_end"
# запинки — по пословным таймкодам
whisper vo.mp3 --model small --language ru --word_timestamps True --output_format json --output_dir work/
# срезать тишину в начале (в записях с телефона её всегда 0.5-1.5 с)
ffmpeg -y -i vo.mp3 -ss 0.8 -c:a copy vo_trimmed.mp3
```

---

## 6. Цвет и формат

Телефон пишет HEVC в HLG/bt2020 10 бит — большинство плееров и браузеров
показывают это неверно (выцветшая или перенасыщенная картинка). Проверка:

```bash
ffprobe -v error -select_streams v:0 \
  -show_entries stream=codec_name,pix_fmt,color_space,color_transfer,color_primaries video.MOV
```

`bt2020` / `arib-std-b67` / `yuv420p10le` → это HDR, нужен перегон в SDR:
`-vf "format=yuv420p" -color_primaries bt709 -color_trc bt709 -colorspace bt709`.

Эти же три тега ставить на КАЖДОМ кодировании libx264 — без них картинка теряет
насыщенность понемногу на каждом проходе, и к финалу видно.

| Назначение | Кодек | CRF | Примечание |
|----------|-------|-----|-------|
| Мастер | libx264 | 18 | всегда |
| Telegram / сжатое | libx264 | 28-30 | при нужде до <50 МБ — `scale=720:1280`, аудио 96k |
| Превью | libx264 | 30 | быстрая проверка |
| Никогда | hevc_videotoolbox | — | файл может не открыться в iCloud/QuickTime |

`-movflags +faststart` — на каждом экспорте, иначе видео не стримится в вебе и на
мобильных.

---

## 7. Приёмка и выдача

```bash
# кадры на ключевых секундах — смотреть глазами
for t in 1 5 10 20 30; do ffmpeg -y -ss $t -i final.mp4 -vframes 1 -q:v 2 work/check_t${t}s.jpg; done
# речь в финале совпадает со сценарием?
whisper final.mp4 --model small --language ru --output_format txt --output_dir work/qa/
# концовка не срезана? (-shortest молча режет последнюю фразу)
ffprobe -v error -show_entries format=duration -of csv=p=0 final.mp4
ffmpeg -y -sseof -10 -i final.mp4 -vn -c:a pcm_s16le work/ending.wav
whisper work/ending.wav --model small --output_format txt --output_dir work/qa/
```

На кадрах проверяй: титры видны и на месте, шрифт и обводка те, нет двойных
титров от прошлых рендеров, оверлеи не съехали.

---

## 8. Пословная синхронизация, карточки, липсинк под трек

Когда титр или карточка должны попадать в конкретное слово (музыка, рэп, быстрый
сленг), всплывающие карточки-определения, откуда брать настоящие фото и логотипы,
сборка музыкального клипа из нескольких дублей → **`references/word-sync-cards.md`**.

Оттуда три вещи, о которых легко забыть в основном потоке: посегментные тайминги
Whisper плывут на ±1-2 с и на музыке выглядят сломанными; трек почти никогда не
начинается с t=0; однокадровый PNG нельзя гасить `fade=alpha` — альфа замерзает
на нуле и карточка не появляется вовсе.

## 9. Безопасные зоны (1080×1920)

Интерфейс площадки (подпись, имя, полоса прогресса, кнопки реакций) перекрывает
края кадра. Титры, карточки и логотипы держать внутри:

| Поле | Instagram/FB Reels | TikTok | Что там |
|---|---|---|---|
| Сверху | 220px (мин. 108) | 108px | профиль, поиск |
| Снизу | 420px (мин. 320) | 320px | подпись, имя, прогресс |
| Слева | 60px | 60px | — |
| **Справа** | 120px | **120px** | колонка лайк/коммент/шер |

**Кросс-платформенный безопасный прямоугольник: 900×1400 по центру.** Карточки
центровать с симметричными боковыми полями; вертикаль субтитров задавать через
`MarginV` в стиле ASS, чтобы текст сидел выше нижней зоны.

⚠️ Уникализация (§10) зумит кадр примерно в 1,075× и съедает эти поля — закладывай
запас заранее и перемеряй на финальном файле, а не на исходном.

## 10. Уникализация под репост

Заливаешь заново уже опубликованный ролик и площадка помечает дубликатом →
**`references/uniquify.md`**: три слоя детекта (визуальный CNN, аудиоотпечаток,
хэш+метаданные) и две готовые команды под них.

Главное оттуда: кроп/яркость/перекодировка по отдельности НЕ работают; на роликах
с вшитыми карточками нельзя применять бочку и виньетку (выгнут прямые края);
никогда не отражать по горизонтали и не менять скорость/питч звука.

---

## 11. Правила, оплаченные ошибками

1. **CFR до склейки** — все сегменты приводятся к постоянным 30fps перед любой конкатенацией. VFR + CFR = подвисшие кадры, дубли, расход звука.

2. **Не накладывать поверх вшитого текста** — если в видео уже вжжены субтитры, вторые сверху дают призрачный текст. Пересобирать из исходных слоёв.

3. **Максимум 3 версии** — если после v3 не работает, остановиться и разобраться системно, а не латать симптомы.

4. **Диагностика до пересборки** — ffprobe, кадры, логи ДО новой попытки сборки.

5. **Текст субтитров проверять руками** — даже large-v3 делает правдоподобные ошибки.

6. **Тишина в начале закадра** — в записях с телефона всегда 0.5-1.5 с, детектировать и срезать.

7. **Картинка под слова** — говорит «письмо» → показываем письмо. 80 % по теме, 20 % перебивки.

8. **Проверять ориентацию клипа** — метаданные поворота врут, вытащить кадр и посмотреть.

9. **Никогда `hevc_videotoolbox` в финал** — QuickTime и iCloud могут отказаться открыть. Только `libx264 + yuv420p`.

10. **Всегда `-movflags +faststart`** — иначе видео не стримится в вебе и на мобильных.

11. **Пословная синхронизация требует пословных таймкодов** — для музыки, рэпа и быстрого сленга посегментные тайминги плывут ±1-2 с. `--word_timestamps True` и свой текст поверх потока слов. Не считать, что трек начинается с t=0.

12. **Не гасить однокадровый PNG альфа-фейдом** — альфа замерзает на нуле и оверлей не появляется. Только `overlay=…:enable='between(t,a,b)'`.

13. **Ключ картинки — имя, а не индекс** — после пересортировки списка индекс укажет на чужой элемент.

14. **Настоящие фото для настоящих людей и брендов** — сгенерированные лица и логотипы выглядят плохо; AI оставить абстракциям и предметам.

15. **Соблюдать безопасные зоны и закладывать запас под уникализацию** — верх 220 / низ 420 / слева 60 / справа 120 (§9); центральный кроп из §10 съедает поля, поэтому мерить на финальном файле.

16. **Уникализировать, не искажая вшитую графику** — при вшитых карточках и плашках никакой бочки и виньетки; зерно + оттенок + кроп + срез метаданных. Не отражать и не менять скорость звука.

