Skill #4 · Video assembly

video-director

AI-режиссёр: берёт готовую озвучку + clip-library проекта, собирает 2-3 варианта смонтированного ролика через FFmpeg. Ядро сборки видео-объявлений.

Путь
~/.claude/skills/video-director/
Триггеры
режиссёр, собери видео, смонтируй, assemble video, монтаж, видеомонтаж
Зависит от
FFmpeg, готовая voiceover.mp3, заполненный clips в БД проекта
Пэйрится с

Что делает простыми словами

Это «монтажёр». Ему дают озвучку (аудио + текст) и библиотеку клипов проекта (база с описаниями каждого — что происходит, сколько секунд, какой mood). Он:

  1. Замеряет длину озвучки → понимает сколько должен длиться ролик
  2. Читает текст озвучки → понимает о чём говорится в каждый момент
  3. Подбирает клипы под смысл (проблема → клип проблемы, продукт → клип продукта)
  4. Режет на сегменты 2-5 секунд, чередует план/общий план для ритма
  5. Проверяет историю использования клипов (чтобы не использовать одни и те же в N роликах подряд)
  6. Собирает 2-3 разных варианта монтажа
  7. FFmpeg-командой склеивает + накладывает фоновую музыку (с ducking под голос)
  8. Результат: 2-3 готовых .mp4
Subtitles НЕ накладывает — это делает отдельный video-captions на следующем шаге. Director выдаёт сырое видео с аудио, captions его дожимает.

Два режима работы

РежимЧто внутриДлинаКогда
voiceover (default) Clips + ElevenLabs voice + музыка (приглушённая 0.20) + Whisper-субтитры = длина озвучки (15-60 сек) Классическое видео-объявление с голосом за кадром
textover Clips + музыка (громче 0.30) + анимированный текст через Remotion Фиксированные 15 секунд Когда нужен текстовый hook без озвучки (молчаливый rant с типографикой)

Принципы монтажа (зашиты в SKILL.md)

Hook (первые 2-3 сек) = всё. Зритель решает «смотреть или скроллить» в это окно. Берётся самый яркий клип: движение, лица, неожиданность, контраст.
Middle несёт сообщение. Клип должен соответствовать тому, что в этот момент говорит голос. Голос про проблему — показываем проблему. Голос про продукт — показываем продукт.
Ending (CTA, последние 2-4 сек) = product shot / лого / результат. Энергия нарастает к этому моменту.
Pacing: сегмент 2-5 сек. Никогда не держать клип дольше 5 сек — внимание падает. Quick cuts (2-3с) = энергия, более длинные holds (4-5с) = акцент.

Команды

Замерить озвучку

python ~/.claude/skills/video-director/scripts/probe_audio.py voiceover.mp3
# → {"duration_seconds": 28.4}

Получить клипы проекта

python ~/.claude/skills/video-director/scripts/db.py list-clips \
  --project-dir ~/video-projects/apparatus

# → JSON массив всех клипов проекта с description, duration, scene_type, mood

Проверить usage history (rotation)

python ~/.claude/skills/video-director/scripts/db.py recent-clips \
  --project-dir ~/video-projects/apparatus --limit 3

# → клипы из последних 3 видео — стараться не использовать снова

Собрать вариант (voiceover-режим)

python ~/.claude/skills/video-director/scripts/assemble.py \
  --project-dir ~/video-projects/apparatus \
  --voiceover voiceover.mp3 \
  --subtitle-text "Текст для субтитров" \
  --montage-plan '[
    {"clip_id": 12, "trim_start": 0, "trim_end": 3.0},
    {"clip_id": 45, "trim_start": 1.5, "trim_end": 5.5},
    {"clip_id": 8, "trim_start": 0, "trim_end": 4.0}
  ]' \
  --variant-name "variant_a" \
  --music background.mp3 \
  --music-volume 0.20 \
  --format voiceover

Собрать вариант (textover-режим)

python ~/.claude/skills/video-director/scripts/assemble.py \
  --project-dir ~/video-projects/apparatus \
  --montage-plan '[{"clip_id": 12, "trim_start": 0, "trim_end": 7.5}, ...]' \
  --variant-name "textover_a" \
  --format textover \
  --music background.mp3 \
  --music-volume 0.30 \
  --duration 15

Внутренности

Скрипты

ФайлЧто делает
scripts/probe_audio.pyПолучает длину аудиофайла через ffprobe
scripts/db.pyCRUD по clips + videos + video_clips (project-scoped через junction-таблицы)
scripts/assemble.pyГлавный воркхорс: принимает montage_plan JSON → строит FFmpeg-команду → cuts + concat + аудио-микс + ducking
scripts/overlay_music.pyНакладывает фоновую музыку с ducking. Используется отдельно (например screencast вызывает с volume 0.08)
scripts/api_keys.pyРезолвит API-ключи из project/api_keys.db или env
scripts/schema.pyОбщий schema-source-of-truth (v8) — копия одинакова во всех скиллах

Таблицы БД, которые трогает

ТаблицаДействиеЗачем
clipsReadБерёт всю clip-library проекта (через project_clips junction)
videosWriteРегистрирует собранный ролик (path, voiceover_id, generated_text_id)
video_clipsWriteЗаписывает «какие клипы пошли в этот ролик» — для rotation
project_videosWriteJunction: linking видео к проекту
musicReadСписок доступных music-треков (через project_music)

Конфиг из project.yaml

Director читает из ~/video-projects/<project>/project.yaml блок director:

director:
  default_format: voiceover        # или textover
  default_aspect: "9:16"           # output aspect
  segment_duration_range: [2, 5]   # min/max длина сегмента
  music_volume_voiceover: 0.20     # дакинг под голос
  music_volume_textover: 0.30      # без голоса — громче
  transition: "none"               # пока без переходов (hard cut)
  rotation_window: 3               # сколько последних видео учитывать в rotation

Output

Финальное видео ложится в:

~/video-projects/<project>/videos/<variant_name>.mp4

Если в project.yaml прописан final_output_dir (например для performante-ai-agency = ~/Desktop/prfmnt creo/) — копия туда же.

Gotchas и tips

Total duration MUST = voiceover duration. Если sumof(trim_end - trim_start) ≠ duration озвучки, FFmpeg либо обрежет хвост, либо растянет (= рассинхрон). assemble.py валидирует это и кидает ошибку если расхождение > 0.3 сек.
Subtitles НЕ здесь. Не путать с video-captions — director выдаёт чистое видео + аудио. Captions = отдельный шаг.
Music ducking уже встроен в assemble.py (ffmpeg sidechaincompress): музыка автоматически приглушается когда говорит голос. Не нужно делать руками.
Clip rotation важна для свежести. Если у проекта 30 клипов и каждый ролик использует 5-7 — за 6 роликов вся library задействована. Без rotation одни и те же клипы кочуют из ролика в ролик = реклама смотрится одинаковой = CTR падает.
Не вызывать без заполненной clip library. Если в clips для проекта пусто — director не сможет собрать. Сначала clipgen (Veo генерация) или analyzer (импорт своих видео).

🎬 Примеры работы

Открой Showcase → final-textover — там 3 финальных textover-видео (T608/ETC5/T8) + 3 voiceover-видео для bas-dent с music ducking. Все примеры — реальные production-runs, embedded локально (работают офлайн).

Место в пайплайне

copywriter voiceover director captions reviewer

Director = центральный узел сборки. Входит: voiceover.mp3 + text + clips[]. Выходит: 2-3 variant_X.mp4 без субтитров.

Сверху над director обычно стоит video-orchestrator — он автоматически прогоняет всю цепочку (copywriter → voiceover → director → captions). Если хочется ручного контроля — вызывают director напрямую.