Skill #11 · Raw recording editor

video-screencast

Монтажёр для сырых записей с экрана и talking-head. Авто-детект тишины, дедупликация дублей по канонический сценарию, voice cleanup для реального микрофона, паддинг до 9:16. Для Screen Studio / Loom / телефонных съёмок где юзер перезаписывает фразы пока не понравится.

Путь
~/.claude/skills/video-screencast/
Триггеры
смонтируй запись, обработай запись, screen studio, talking head, dedupe takes, убрать дубли, удали тишину, оставь последний дубль
Зависит от
FFmpeg, openai-whisper, canonical script (текст что хотел сказать)
Пэйрится с
captions (после), director/overlay_music.py (после)

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

Это монтажёр для случая «я записал себя на телефон / Screen Studio, переписал несколько раз каждое предложение, и хочу чтобы оставили только финальные дубли без пауз». Алгоритм:

  1. Извлекает аудио из сырого .mp4 → моно WAV 16kHz
  2. silencedetect через FFmpeg (порог -32dB, мин 0.5с тишины) → нарезка на speech-сегменты
  3. На каждом сегменте отдельно гонит Whispercondition_on_previous_text=False чтобы предыдущая транскрипция не влияла)
  4. Каждый сегмент фаззи-мэтчит на предложения из канонического сценария (word overlap)
  5. Если на одно предложение несколько дублей — оставляет последний (по позиции во времени)
  6. Формирует cut plan JSON: какие сегменты kept: true
  7. FFmpeg trim + concat по cut plan + voice cleanup chain → cleaned.mp4
  8. Опционально: pad до 1080×1920 если запись не 9:16 (Screen Studio часто экспортирует 864×1080 = 4:5)
Зачем отдельно от основного пайплайна. Реальный микрофон требует cleanup chain (highpass + denoise + compressor + loudnorm) — ElevenLabs TTS этого не нужно. И музыка должна быть тише чтобы живой голос оставался разборчивым: 0.08 вместо дефолтного 0.20 для TTS.

Пайплайн

raw .mp4
extract audio (mono 16k WAV)
ffmpeg silencedetect (-32dB, 0.5s) → speech segments
Whisper small per-segment
match segments → canonical sentences (fuzzy word overlap)
keep LAST segment per (sentence, position) → cut plan JSON
ffmpeg trim+concat with voice cleanup chain → cleaned.mp4
optional: pad to 1080×1920 for 9:16
optional: video-captions (Remotion) → captioned.mp4
optional: video-director overlay_music.py → final.mp4

Voice cleanup chain

Стандартная цепочка фильтров для реального микрофона (НЕ для ElevenLabs TTS — там аудио уже чистое):

highpass=f=80,
afftdn=nf=-25,
acompressor=threshold=-18dB:ratio=3:attack=20:release=200:makeup=2,
loudnorm=I=-16:TP=-1.5:LRA=11
ФильтрЧто делает
highpass=f=80 Срезает суб-бас румбл (кондиционер, удары по столу, вибрация стойки микрофона) — всё что ниже 80Hz
afftdn=nf=-25 FFT-based адаптивный denoise — снимает фоновый шипяще-шумовой ковёр (room tone, мелкий шум)
acompressor (-18dB / 3:1, makeup +2dB) Выравнивает громкие и тихие места речи. Юзер шепчет — поднимает, орёт — придавливает. Makeup +2dB поднимает общий уровень обратно.
loudnorm I=-16 Нормализует до broadcast/podcast стандарта: -16 LUFS integrated, true peak -1.5, LRA 11. Это та громкость которую ждут Reels/TikTok/Spotify.

Скрипты

ФайлЧто делает
scripts/detect_takes.py Audio → silence detection → per-segment Whisper → cut plan JSON. Мэтчит на canonical text, помечает дубли. Главный «думающий» скрипт.
scripts/cut_and_clean.py Cut plan JSON + raw video → cleaned mp4. Trim+concat по плану, прогоняет audio через voice cleanup chain, опционально pad до 1080×1920.
scripts/voice_cleanup_chain.py Reusable: возвращает -af filter-строку для real-mic voice cleanup. Можно импортировать в другие скрипты.
scripts/render_pipeline.sh End-to-end driver: detect → review JSON в $EDITOR → cut+clean → captions → music. Запускает весь маршрут одной командой.

Команды

Шаг 1: детект дублей + cut plan

python ~/.claude/skills/video-screencast/scripts/detect_takes.py \
  --video raw_recording.mp4 \
  --canonical-text "$(cat script.txt)" \
  --whisper-model small \
  --silence-threshold -32dB \
  --silence-min 0.5 \
  --min-segment 0.5 \
  --output cut_plan.json

Шаг 2: ручной review (рекомендуется для сложных записей)

# Открывает cut_plan.json в $EDITOR
# Юзер вручную флипает "kept": true/false для проблемных сегментов
$EDITOR cut_plan.json

Шаг 3: cut + clean + pad

python ~/.claude/skills/video-screencast/scripts/cut_and_clean.py \
  --cut-plan cut_plan.json \
  --output cleaned.mp4 \
  --voice-cleanup \
  --pad-to 1080x1920

End-to-end (всё одной командой)

bash ~/.claude/skills/video-screencast/scripts/render_pipeline.sh \
  --video raw_recording.mp4 \
  --canonical-text script.txt \
  --music ~/video-projects/bas_dent/music/calm_piano.mp3 \
  --project-dir ~/video-projects/bas_dent \
  --output final.mp4

Captions integration

После cut_and_clean идёт video-captions. Для talking-head дефолтный вариант — monochrome scale-pop (анимация размером, без вспышки цвета):

python ~/.claude/skills/video-captions/scripts/remotion_captions.py \
  --video cleaned.mp4 \
  --text "$(cat canonical.txt)" \
  --style karaoke \
  --highlight-color "#FFFFFF" \
  --font-size 64 \
  --project-dir ~/video-projects/bas_dent \
  --output captioned.mp4
Почему именно monochrome. Karaoke-пресет в Remotion имеет wordAnimation: scale-pop. Если передать --highlight-color "#FFFFFF", активное слово окрашивается в тот же цвет что и неактивный текст — остаётся только пульсация размера. Без жёлтой/cyan вспышки. Это «спокойный» вариант для личного бренда и talking-head.

Music overlay

Для финального микса — video-director overlay_music.py. Для talking-head дефолтная громкость 0.20 слишком громко над живым голосом — используем 0.08:

python ~/.claude/skills/video-director/scripts/overlay_music.py \
  --video captioned.mp4 \
  --music ~/video-projects/bas_dent/music/track.mp3 \
  --volume 0.08 \
  --output final.mp4
КонтекстVolumeПочему
ElevenLabs TTS (основной пайплайн)0.20TTS-голос уже громкий и чистый, музыка может быть громче
Real-mic talking-head (screencast)0.08Живой голос имеет динамику, музыка должна оставлять headroom

Aspect ratio padding

Screen Studio часто экспортирует не стандартный aspect (например 864×1080 = 4:5). Для downstream Remotion captions требуется 1080×1920, поэтому cut_and_clean.py --pad-to 1080x1920:

Detection tuning

detect_takes.py дефолты подобраны для «нормального» голоса в комнатных условиях. Подкручивать когда:

ПараметрDefaultКогда менять
--silence-threshold -32dB Тихий голос → -38dB. Очень экспрессивная запись (смех, вздохи) → -28dB.
--silence-min 0.5s Минимальная длительность тишины чтобы считать за break. Меньше — больше мелких разрывов.
--min-segment 0.5s Минимальный speech-блип. Дропает клики мыши, вдохи, причмокивания.
--whisper-model small Для коротких клипов — быстро и точно. medium если канонический сценарий длинный и сложный.
Слишком громкий порог (-20dB) → тишины не детектятся, дубли сливаются. Слишком тихий (-45dB) → ложные срабатывания speech, оставит шум как «сегмент».

Cut Plan JSON формат

{
  "source": "/path/to/raw.mp4",
  "canonical_text": "...",
  "voice_cleanup": true,
  "segments": [
    {"start": 46.30, "end": 49.85, "sentence_idx": 0, "take": 3, "kept": true,  "text": "..."},
    {"start": 21.47, "end": 24.71, "sentence_idx": 0, "take": 1, "kept": false, "text": "..."},
    {"start": 30.10, "end": 33.42, "sentence_idx": 0, "take": 2, "kept": false, "text": "..."},
    {"start": 52.00, "end": 56.20, "sentence_idx": 1, "take": 1, "kept": true,  "text": "..."}
  ]
}

Для предложения sentence_idx: 0 было 3 take'а — оставлен только последний (take: 3, по времени 46.30-49.85). Остальные помечены kept: false.

Флаг --review в detect_takes.py открывает план в $EDITOR чтобы юзер вручную поправил kept: true/false перед рендером. Рекомендуется для нетривиальных записей (раздробленные дубли, частичные перезаписи). Дёшево по времени, спасает от плохих автоматических решений.

Когда использовать

Input contract:

  1. Raw .mp4 (любой формат, любой aspect)
  2. Canonical script text (тот текст что юзер хотел озвучить)

Output contract:

Gotchas и tips

Никогда не применять voice cleanup chain к TTS-аудио. ElevenLabs выдаёт уже чистый и нормализованный голос. Compressor + loudnorm второй раз = pumping, искажения, лишний цикл нормализации. Cleanup chain — только для реального микрофона.
Whisper падает на полностью тихих сегментах. Если --min-segment слишком маленький, в плане могут оказаться сегменты которые Whisper не смог распознать (вернул пустой текст). detect_takes.py их дропает автоматически, но если что-то идёт не так — глянуть JSON руками.
Loudness target -16 LUFS подходит для соцсетей (Reels, TikTok, YouTube Shorts). Для подкаста-релиза тише — -19 LUFS. Для broadcast TV — -23 LUFS. Менять через loudnorm=I=-19 в voice_cleanup_chain.py.
Сценарий должен быть «честным» относительно того что юзер реально говорил. Если в сценарии «Привет, я Стас» а в записи «Здравствуйте, меня зовут Станислав» — fuzzy-match не сматчит, сегмент попадёт в неопределённый. Перед запуском проверить что текст соответствует тому что было сказано.
Pad-to 1080×1920 не обязателен. Если конечный платформенный формат — YouTube widescreen или Telegram, оставить native aspect Screen Studio. Padding нужен только когда дальше идут Remotion captions требующие 1080×1920.

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

screencast captions director/overlay_music.py final.mp4

Альтернативный трек к основному пайплайну. Заменяет copywriter + voiceover + director — юзер сам пишет себя на камеру со своим скриптом и микрофоном.

Финальный mp4 проходит те же captions и music-overlay шаги что и TTS-вариант, но с другими параметрами (monochrome scale-pop captions, volume 0.08 для музыки). По желанию можно прогнать через video-reviewer для финального QA.