🎬
Получить материал в Telegram Подпишись на канал и заполни короткую анкету — конфиги, ffmpeg-скрипты и Playwright-шаблоны откроются в нашем приложении
Открыть в Telegram →
Гайд · Контент-автоматизация

Reels-автопайплайн: идея → публикация без меня

6 шагов, в которых я участвую только на съёмке одним дублем. Остальное — Claude Code: сценарий приходит ночью, монтаж занимает 7 минут, лендинг под ролик с кодовым словом, постинг на IG и YT по расписанию, вечерний отчёт в Telegram.

Формат: Архитектура + реальные команды Время: ~45–60 минут на чтение Стек: Claude Code · faster-whisper · Playwright · ffmpeg · Meta Graph API · YT Data API Бюджет: ~$125–135/мес (VPS + CC + APIs + музыка)

Что внутри гайда

1

Темы и сценарии — пока ты спишь

Pre-research-агент · cron 06:30 МСК

В 07:00 в личку падает файл. Внутри — два-три сценария: тема, хук первые 4 секунды, структура пяти сегментов, финальный CTA с кодовым словом. Ты не выбираешь тему — ты выбираешь снимать первый или второй. Это не «идеи» — это готовый текст, который читаешь прямо с телефона.

Агент запускается в 06:30 МСК через cron. Алгоритм трёхшаговый: сигналы → паттерны → сценарий. Ни одной лишней операции — всё что нужно для следующего шага (съёмки) уже внутри.

prompt_template

Это полный системный промпт агента-исследователя. Вставляется в Claude как system, дополняется переменными из profile.yaml.

SYSTEM PROMPT — Reels Research Agent v2.1
=========================================

Ты — агент генерации сценариев для личного бренда в нише {niche}.
Запускаешься в {cron_time} МСК ежедневно.

ВХОДНЫЕ ДАННЫЕ (читаешь из profile.yaml и DB):
- niche: {niche}                         # например: "DeFi / AI-автоматизация"
- brand_voice: {brand_voice}             # "короткие предложения, без канцелярита, ты, реальные цифры"
- past_topics: [{slug_1}, {slug_2}, ...] # список slug'ов последних 14 дней — не повторять
- banned_patterns: [{pattern_1}, ...]    # паттерны с retention < 45% — пропустить
- target_duration_sec: {duration}        # обычно 45–55 сек
- language: ru
- cta_keyword: {cta_keyword}             # текущее кодовое слово, например ПАЙПЛАЙН

ШАГ 1 — VIRLO MCP ПОИСК:
Вызови virlo.search_keywords с параметрами:
  keywords: топ-5 терминов ниши
  time_period: "this_week"
  min_views: 50000
  platforms: ["instagram", "youtube"]
Из результата возьми топ-20 роликов по (saves + completion_rate), отбрось по просмотрам.

ШАГ 2 — АНАЛИЗ ПАТТЕРНОВ:
Для каждого из топ-20 роликов выдели:
- тип хука: цифра_в_первых_3_словах / вопрос / провокация / контрнарратив
- структуру: listicle / before-after / personal-story / tutorial
- позицию разворота нарратива (если есть): секунда
- CTA-тип: кодовое_слово / ссылка_в_bio / комментарий

Найди 2–3 паттерна, которые встречаются ≥3 раз в топ-10.

ШАГ 3 — ГЕНЕРАЦИЯ СЦЕНАРИЕВ:
Под каждый найденный паттерн напиши один сценарий.
Формат каждого сценария:
---
ТЕМА: [одна строка]
ПАТТЕРН: [тип]
ХУК (0–4 сек): [точный текст, ≤18 слов]
СЕГМЕНТ 1 (5–14 сек): [текст, ~90–110 символов]
СЕГМЕНТ 2 (15–24 сек): [текст]
СЕГМЕНТ 3 (25–34 сек): [текст]
СЕГМЕНТ 4 (35–44 сек): [текст]
CTA (45–52 сек): [текст с кодовым словом {cta_keyword}]
SLUG: [latin-kebab-case-до-5-слов]
BROLL_HINTS: [5 коротких описаний визуальных вставок по сегментам]
THUMBNAIL_HOOK: [одна строка — для обложки, ≤6 слов, цифра обязательна]
---

ПРАВИЛА:
- Обращение «ты» везде
- Конкретные цифры из реального опыта нарратора (из profile.yaml)
- Без «данный», «осуществляется», «является»
- Каждое предложение ≤15 слов
- Кодовое слово — в последней реплике, органично

ВЫХОД: JSON с полем "scenarios": [...] + поле "top_patterns": [...]
Отправь через TG Bot API на {telegram_chat_id}.

Virlo MCP — два инструмента

Virlo MCP — сервис агрегации вирального контента. В пайплайне используются два инструмента: search_keywords и search_hashtags.

search_keywords — поиск роликов по ключевым словам:

Стоимость базового вызова: $0.50 ($1.50 с data intelligence). При ежедневном запуске и без data_intelligence = ~$15/мес.

search_hashtags — поиск трендовых хэштегов за период:

Возвращает: hashtag, video_count, total_views. Используется для выбора 7–10 хэштегов в caption на шаге 5.

Что НЕ возвращает Virlo напрямую: completion rate и save rate по отдельным роликам — только views. Для retention-фильтрации агент вызывает дополнительно analyze_video на топ-5 роликов ($0.50 × 5 = $2.50). Это опционально — запускать раз в неделю, не ежедневно.

Пример готового сценария

Пример готового сценария (Сегмент 1–CTA) на выходе агента:

ТЕМА: Монтажный агент за $0/мес
ПАТТЕРН: listicle + цифра в хуке
ХУК: Этот ролик смонтирован без человека. Покажу как.
СЕГМЕНТ 1: Шаг один. Агент утром. Сканит виральные ролики в нише.
Присылает текст — читаю с телефона.
СЕГМЕНТ 2: Шаг два. Я снимаю. Один дубль. Монтажный агент берёт файл.
СЕГМЕНТ 3: faster-whisper режет паузы. Playwright вставляет B-roll.
ffmpeg собирает всё в один MOV — 7 минут.
СЕГМЕНТ 4: Лендинг уже готов. ManyChat ловит слово в комментах.
Бот отправляет ссылку. Без меня.
CTA: Пиши слово ПАЙПЛАЙН в комменты — пришлю архитектуру стека.
SLUG: montage-agent-zero-cost
BROLL_HINTS: [terminal с JSON, ffmpeg process bar, telegram chat screenshot,
              Playwright browser render, ManyChat flow diagram]
THUMBNAIL_HOOK: 7 минут — полный монтаж

profile.yaml — конфиг агента

Это единственный файл, который нужно поддерживать руками. Агент читает его на каждом запуске.

# profile.yaml — конфиг pre-research агента
niche: "DeFi / AI-автоматизация"
brand_voice: |
  Короткие предложения. Без канцелярита. «Ты», не «вы».
  Реальные цифры из опыта (а не «много», «быстро»).
  Каждая 3-я фраза — конкретика: число / название инструмента / срок.
target_duration_sec: 50
cta_keyword: "ПАЙПЛАЙН"
telegram_chat_id: 123456789

# Темы, которые уже отработаны за 14 дней — не повторять
past_topics:
  - news-agent-5-systems
  - montage-zero-cost
  - reels-autopipeline

# Паттерны с retention < 45% — выкинуть из ротации
banned_patterns:
  - "question_hook_without_number"   # ХУК-вопрос без цифры — у меня плохо работает
  - "personal_story_over_30sec"      # длинные сторителлинги — < 50% retention

# Сезонные/нишевые сигналы — добавляются вручную раз в неделю
context:
  - "Май 2026: рост интереса к Claude Code в РФ-комьюнити"
  - "AI-агенты на TG/IG — пик трендов"
  - "Audit Funnel запущен — нужны лиды через коммент-DM"

cron + watchdog

Запуск агента — обычный cron-job. Никаких облачных функций, никаких очередей. Один файл, один процесс, одна задача.

# crontab -e (на VPS, время в UTC — +3 для МСК)
# Pre-research агент: 06:30 МСК = 03:30 UTC
30 3 * * * cd /home/reels && python3 research_agent.py >> logs/research.log 2>&1

# Watchdog для дроп-папки (через systemd unit)
# /etc/systemd/system/reels-watchdog.service
[Unit]
Description=Reels incoming watchdog
After=network.target

[Service]
ExecStart=/usr/bin/python3 /home/reels/watchdog.py
Restart=always
User=reels
WorkingDirectory=/home/reels

[Install]
WantedBy=multi-user.target
Частота и ниши: агент запускается ежедневно, но сценарий не обязательно использовать каждый день. Оптимальная частота публикаций для DeFi/AI-ниши — 3–4 ролика в неделю по данным анализа батча май 2026. Агент строит очередь — сценарии лежат в queue.json, выбирается следующий по расписанию. Retention rate как фильтр: паттерны с completion < 45% в banned_patterns profile.yaml — агент их не повторяет. Ниши, где пайплайн особенно эффективен: AI-инструменты, DeFi/крипто, автоматизация бизнеса, личные финансы — лидеры по save-rate в Virlo-данных.
2

Один дубль ≤90 секунд

Listicle-формат · face cam · 15 минут включая setup

Ты открываешь телефон. Читаешь сценарий, который агент прислал три часа назад. Нажимаешь запись. Говоришь. Нажимаешь стоп. Отправляешь файл в папку. Всё — твоё участие закончено на следующие 7 минут.

Сценарий уже в нужном формате: 45–55 секунд чистой речи, listicle, первые 4 секунды — хук. Никакого импровизированного монтажа в голове — агент написал текст так, чтобы он читался с первого дубля. Паузы ≥0.4 сек автоматически вырежутся на шаге 3.

Чек-лист съёмки — 10 пунктов

  1. Свет — источник перед лицом. Окно или LED-панель спереди, не сбоку и не сзади. Тень позади — окей. Тень на лице — нет. Время суток: утро или пасмурный день = идеально, прямой полуденный солнечный свет = жёсткие тени → шторы.
  2. Фон — однотонный или минимальный. Белая стена, тёмная стена, книжная полка в расфокусе. Без мигающих экранов позади, без людей в кадре. Размытие через Blur Background приложение — только если другого варианта нет (артефакты на краях).
  3. Одежда — контраст с фоном. Тёмный фон → светлая одежда, светлый фон → тёмная. Без мелкого узора (полосы, мелкая клетка) — артефакт кодека на движении. Без логотипов, которые Meta может пометить.
  4. Ракурс — камера на уровне глаз или чуть выше. Штатив или стабилизатор обязательно. Телефон вертикально. Лицо занимает верхние 2/3 кадра — нижняя треть уйдёт под субтитры.
  5. Расстояние — 60–80 см от камеры. Ближе — слишком крупный план, дальше — субтитры перекроют лицо. Face cam listicle = средний план по грудь.
  6. Длина записи — не дольше 90 сек исходника. Финальный ролик 45–55 сек, speed-up 1.2× на монтаже. Если затянул → агент обрежет по VAD, но лучше не давать лишнего материала.
  7. Формат файла — нативная съёмка 1080p/60fps, MOV или MP4. Не включай встроенную стабилизацию с warp (размыливает края) — физический штатив лучше.
  8. Пространство — тишина. Закрой окна, выключи кондиционер. Проверь через наушники 15 сек тишины перед стартом — слышен ли фон. Если слышен — устрани или перенеси съёмку.
  9. Дубль — один. Читаешь сценарий, пауза 2 сек, нажимаешь запись, смотришь в объектив. Если забыл слово — пауза 1 сек + продолжаешь. faster-whisper вырежет паузу, B-roll её закроет. Второго дубля нет — это правило, не рекомендация.
  10. Отправка — дроп-папка в облако. После съёмки файл улетает в ~/Reels/incoming/ (iCloud Drive или Google Drive). Watchdog на сервере видит новый файл → автоматически запускает монтажный pipeline.

Аудио — 80% восприятия

Плохое видео прощается, плохое аудио — нет.

Тайминг по платформам

Частые ошибки на съёмке:
  • Смотришь в экран, не в объектив — аудитория чувствует «он не смотрит на меня»
  • Начинаешь говорить сразу при записи — дать 1–2 сек паузы в начале для ingest
  • Свет сзади → лицо в силуэте, Playwright color grading не спасёт
  • Фоновый шум после обработки звучит как «металлический» — это артефакт агрессивного шумоподавления faster-whisper. Лучше тишина изначально
  • Слишком длинный ролик (>90 сек) — монтаж добавит speed-up 1.2×, речь станет неразборчивой
3

faster-whisper → Playwright broll → ffmpeg

~7 минут на ролик · 5 проходов · 18 stages под капотом

Ты отправил файл. Watchdog подхватил. Дальше — конвейер. 5 мегапроходов, 18 под-стадий, ~7 минут, на выходе готовый MOV с субтитрами, B-roll, цветокором, cover-кадром и сведённым аудио. Без тебя. Ниже — каждый шаг с реальной командой.

Pipeline основан на build_reel.py (master script) + render_scenes_v4.py (Playwright multiprocessing). Все стадии обязательны — пропуск любой запрещён. Стадии 3.5 (Cover) и 14.5 (QA) особенно важны: первая даёт thumbnail, вторая не даёт выпустить брак.

Проход 1 — Ingest + Crop + Audio Extract (Stage 0–2)

Что делает: копирует исходник в рабочую директорию, кропит до 9:16 1080×1920, добавляет blur-pad фон, нормализует аудио, удаляет паузы ≥0.4 сек через silencedetect.

# Stage 0 — Ingest (копия, без изменений)
cp ~/Reels/incoming/source.mov ~/brain/reels-production/$(date +%Y-%m-%d)_${SLUG}/source.mov
cd ~/brain/reels-production/$(date +%Y-%m-%d)_${SLUG}/

# Stage 2 — Crop 9:16 с blur-pad фоном
ffmpeg -i source.mov \
  -filter_complex \
    "[0:v]scale=-2:1920,crop=1080:1920,gblur=sigma=28,eq=brightness=-0.12[bg]; \
     [0:v]crop=868:1080:(in_w-868)/2:0,scale=1080:1344[main]; \
     [bg][main]overlay=0:288" \
  -c:v libx264 -crf 18 -pix_fmt yuv420p -an \
  renders/base_crop.raw.mp4

# Вытащить аудио отдельно для Whisper
ffmpeg -i source.mov -vn -ar 16000 -ac 1 -c:a pcm_s16le audio_raw.wav

# Silence detection — найти паузы для последующей нарезки
ffmpeg -i audio_raw.wav \
  -af silencedetect=noise=-30dB:duration=0.4 \
  -f null - 2>&1 | grep "silence_" > silence_markers.txt

Что получается: renders/base_crop.raw.mp4 — базовый кроп без цветокора, audio_raw.wav — 16kHz моно для Whisper, silence_markers.txt — временные метки пауз.

Проход 2 — faster-whisper word-level STT (Stage 1)

Что делает: транскрибирует речь с точностью до слова (±10 мс при использовании WhisperX с wav2vec2 alignment), выдаёт JSON с таймингами. Этот JSON — основа для субтитров и B-roll синхронизации.

# CLI — faster-whisper 1.2.1 (последняя версия на май 2026)
# Локально на CPU M-серии: ~10 сек на минуту аудио
python3 -m faster_whisper \
  audio_raw.wav \
  --model large-v3-turbo \
  --language ru \
  --word_timestamps True \
  --vad_filter True \
  --vad_parameters '{"min_silence_duration_ms": 400}' \
  --output_format json \
  --output_dir . \
  --beam_size 5

# Переименовать в стандартный путь
mv audio_raw.json transcript.json

Python wrapper для интеграции в build pipeline:

from faster_whisper import WhisperModel

def transcribe(audio_path: str, model_size: str = "large-v3-turbo") -> dict:
    """
    Возвращает dict с ключами:
      - segments: список сегментов с start/end/text
      - words: плоский список {word, start, end, probability}
    """
    model = WhisperModel(model_size, device="cpu", compute_type="int8")
    segments, info = model.transcribe(
        audio_path,
        language="ru",
        word_timestamps=True,
        vad_filter=True,
        vad_parameters={"min_silence_duration_ms": 400},
        beam_size=5
    )
    words = []
    seg_list = []
    for seg in segments:
        seg_list.append({"start": seg.start, "end": seg.end, "text": seg.text})
        if seg.words:
            for w in seg.words:
                words.append({
                    "word": w.word.strip(),
                    "start": round(w.start, 3),
                    "end": round(w.end, 3),
                    "probability": round(w.probability, 3)
                })
    return {"segments": seg_list, "words": words}

Формат word-level JSON на выходе:

{
  "segments": [
    {"start": 0.0, "end": 3.8, "text": "Этот ролик смонтирован без человека."}
  ],
  "words": [
    {"word": "Этот",        "start": 0.42, "end": 0.78, "probability": 0.99},
    {"word": "ролик",       "start": 0.82, "end": 1.10, "probability": 0.98},
    {"word": "смонтирован", "start": 1.14, "end": 1.82, "probability": 0.97},
    {"word": "без",         "start": 1.86, "end": 2.04, "probability": 0.99},
    {"word": "человека",    "start": 2.08, "end": 2.64, "probability": 0.98}
  ]
}

Этот JSON читают 4 downstream-стадии: субтитры, B-roll синхронизация, music brief, cover.

Проход 3 — Субтитрные overlays через Playwright → PNG sequence (Stage 4, 6C, 8)

Что делает: генерирует HTML-субтитры по word-level JSON, рендерит через Playwright в PNG-последовательность с прозрачным фоном, накладывает на base_crop через ffmpeg.

Два формата субтитров — выбор зависит от типа контента:

Выбор формата определяется из транскрипта автоматически: listicle/tutorial → Format A, личная история/эмоции → Format D.

# Генерация HTML из word JSON (Format A — glass)
def gen_subtitle_html_glass(words: list, out_path: str):
    """Собирает overlay_glass.html по word-level данным"""
    # Группируем слова в строки по 4-5 слов
    # Каждая группа = один frame-range для субтитра
    lines = group_words_into_lines(words, max_words=5)
    # Генерируем GSAP timeline с точными start/end из JSON
    html = render_template("subtitle_glass_template.html", lines=lines)
    Path(out_path).write_text(html)
# Playwright render PNG sequence (render_scenes_v4.py — multiprocessing)
python3 render_scenes_v4.py \
  --scenes overlay_glass.html:$(ffprobe -v error -show_entries format=duration \
    -of csv=p=0 renders/base_crop.raw.mp4) \
  --fps 30 \
  --out out_v4/overlay/
# Output: out_v4/overlay/frame_%05d.png (alpha PNG, 1080×1920)
# render_scenes_v4.py — дефолтный рендерер, 5× быстрее baseline
# Копируй из эталона, адаптируй список BROLLS
cp ~/brain/reels-production/_etalon/render_scenes_v4.py render_v4.py
# Отредактируй BROLLS = [("overlay_glass.html", <duration_frames>), ...]
python3 render_v4.py
# Output: out_v4/overlay/frame_%05d.png

Наложение субтитров через ffmpeg:

# Format A — один слой overlay
ffmpeg -i renders/base_crop.mp4 \
  -i out_v4/overlay/frame_%05d.png \
  -filter_complex \
    "[1:v]format=rgba[sub]; \
     [0:v][sub]overlay=0:0:format=auto" \
  -map 0:a \
  -c:v libx264 -preset fast -crf 18 -pix_fmt yuv420p \
  renders/composite_subs.mp4

Проход 4 — B-roll HTML render (Stage 5–6, 12 типов)

Что делает: для каждой из 5 B-roll вставок генерирует HTML-сцену, рендерит в PNG-sequence через Playwright, вставляет в timeline по таймингу из word-level JSON.

12 типов B-roll сцен (Format D HTML-типы): chart-card, terminal, listicle-ticker, meme-reference, named-entity-card, progress-bar, split-compare, counter-animate, flow-diagram, pictogram-multi, podcast-hook, narrative-canvas.

Логика выбора типа (из transcript):

# Пример ffmpeg-вставки B-roll поверх face cam
# B-roll занимает секунды [start_t, end_t] в итоговом таймлайне
ffmpeg -i renders/composite_subs.mp4 \
  -i out_v4/b01/frame_%05d.png \
  -filter_complex \
    "[1:v]format=rgba[broll]; \
     [0:v][broll]overlay=0:0:enable='between(t,${BROLL_START},${BROLL_END})':format=auto" \
  -map 0:a \
  -c:v libx264 -preset fast -crf 18 -pix_fmt yuv420p \
  renders/composite_broll.mp4

Важно перед concat: нормализация fps + timescale (иначе duration сломается в 20–30×):

for f in segments/*.mp4; do
  ffmpeg -y -i "$f" \
    -r 30 -fps_mode cfr -vf "fps=30" \
    -video_track_timescale 15360 \
    -c:v libx264 -crf 18 -preset fast \
    -c:a aac -b:a 192k \
    "${f}.norm.mp4"
  mv "${f}.norm.mp4" "$f"
done

# Concat без re-encoding (~200× быстрее)
ffmpeg -f concat -safe 0 -i segments/concat.txt -c copy out/concat.mp4

# Проверка длительности
ffprobe -v error -show_entries format=duration -of csv=p=0 out/concat.mp4

Cover Branch (Stage 3.5 — параллельно с основным flow)

Запускается параллельно со Stage 3, не блокирует основной pipeline. Даёт cover.png для Stage 14.7.

# Stage 3.5.b — выбор лучшего hero-кадра через mediapipe FaceMesh
# Скорит по: face_size × open_eyes × (1 − rotation) + hands_bonus
python3 cover_hero_select.py \
  --video renders/base_crop.raw.mp4 \
  --samples 5 \
  --out cover/hero.jpg

# Stage 3.5.d — composite через Playwright (шаблон split_v3.html)
# text_above: THUMBNAIL_HOOK из сценария агента
# hero_image: cover/hero.jpg
python3 cover_builder.py \
  --template split_v3.html \
  --hook "${THUMBNAIL_HOOK}" \
  --hero cover/hero.jpg \
  --out cover/cover.png

# Stage 14.7 — embed cover как frame 0 (hold 0.3с — IG берёт первый кадр)
ffmpeg -y \
  -loop 1 -t 0.3 -framerate 30 -i cover/cover.png \
  -i FINAL.mp4 \
  -f lavfi -t 0.3 -i anullsrc=r=44100:cl=stereo \
  -filter_complex "
    [0:v]scale=1080:1920,setsar=1,fps=30,format=yuv420p[c];
    [1:v]scale=1080:1920,setsar=1,fps=30,format=yuv420p[m];
    [c][m]concat=n=2:v=1:a=0[outv];
    [2:a][1:a]concat=n=2:v=0:a=1[outa]" \
  -map "[outv]" -map "[outa]" \
  -c:v libx264 -preset medium -crf 18 \
  -c:a aac -b:a 192k -pix_fmt yuv420p -movflags +faststart \
  FINAL_with_cover.mp4

Проход 5 — Speed-up + Audio mux + Ducking (Stage 9–14)

Что делает: ускоряет на 1.2×, прикрепляет оригинальный голос, обнаруживает сцены, генерирует музыку, делает финальный микс с sidechain-ducking. Это самый длинный проход по времени (~3–4 мин).

# Stage 9 — Speed-up 1.2× (ДО генерации музыки)
ffmpeg -i composite_broll.mp4 \
  -filter_complex "[0:v]setpts=PTS/1.2[v]" \
  -map "[v]" -c:v libx264 -preset fast -crf 18 -pix_fmt yuv420p \
  composite_silent_120.mp4

# Stage 10 — Mux оригинального голоса (тоже atempo 1.2)
ffmpeg -i composite_silent_120.mp4 -i source.mov \
  -filter_complex "[1:a]atempo=1.2[a]" \
  -map 0:v -map "[a]" -c:v copy -c:a aac \
  composite_voice.mp4

# Stage 11 — Scene detection (для music brief)
ffmpeg -i composite_voice.mp4 \
  -filter:v "select='gt(scene,0.25)',showinfo" -f null - 2>&1 \
  | grep showinfo > scene_cuts.txt

# Stage 14 — Final mux + sidechain ducking
# Voice: loudnorm -14 LUFS; Music: -20 dB; Sidechain: ratio=12 attack=15ms release=400ms
ffmpeg -i composite_voice.mp4 -i music.mp3 \
  -filter_complex "
    [0:a]loudnorm=I=-14:LRA=11:TP=-1.5,asplit=2[voice_main][voice_trig];
    [1:a]atrim=0:${DURATION},volume=0.20,aformat=channel_layouts=stereo[music];
    [music][voice_trig]sidechaincompress=threshold=0.05:ratio=12:attack=15:release=400:makeup=1[music_ducked];
    [voice_main][music_ducked]amix=inputs=2:duration=first:normalize=0[aout]
  " \
  -map 0:v -map "[aout]" \
  -c:v copy -c:a aac -b:a 192k \
  -t ${DURATION} \
  FINAL.mp4

# Финальная проверка аудио (должно быть 48000 Hz)
ffprobe -v error -select_streams a:0 \
  -show_entries stream=sample_rate,codec_name,channels \
  -of csv=p=0 FINAL.mp4

QA Engine (Stage 14.5)

Обязателен перед выпуском. Без исключений. Минимальный порог: aggregate ≥ 7.0. Три движка проверки:

Engine A — детерминированные чеки:

Engine B — визуальный ревью (Playwright):

Engine C — общий просмотр: смотришь финальный ролик полностью, аудио разборчиво, нет артефактов resize/crop на гранях. Если ≥1 critical rubric «нет» — не публиковать, исправить, перебилдить проход.

QA Engine как gate: ни один ролик не уходит в публикацию без Stage 14.5. Это правило, не рекомендация. Aggregate score < 7.0 → blocking, исправлять и перебилдить. B-roll quality V6: минимум 250 кадров на сцену (иначе анимация рваная), нет статичных кадров дольше 0.5 сек, каждый B-roll визуально связан с нарративом сегмента — не декоративный.
5 известных косяков (must-fix):
  • Кириллица в имени файла → Meta 400. video_url с кирилличным именем файла = ошибка при создании контейнера. Имя всегда ASCII + slug: reels-autopipeline-2026-05-21.mov. Автоматически форсировать через os.path.basename(slugify(filename)).
  • Аудио >48kHz → Meta отклоняет ролик. Meta принимает AAC максимум 48 kHz. Если исходник 44.1 kHz или 96 kHz — ffmpeg явно ресемплит: -ar 48000. Проверять через ffprobe до отправки.
  • Playwright --disable-gpu обязателен в headless. Без флага backdrop-blur в CSS рендерится с артефактами (серые прямоугольники вместо blur). Обязательный аргумент при launch: chromium.launch(args=["--disable-gpu", "--no-sandbox"]). На Mac локально — не нужен, на Linux VPS — обязателен.
  • GSAP opacity:0 в paused timeline = пустой PNG. Не использовать gsap.set('#el', {opacity:0}) или CSS opacity:0 без последующего to({opacity:1}). Результат — полностью прозрачный alpha-PNG на всех кадрах. Фикс: opacity:1 в CSS, fade через ffmpeg -vf fade=t=in.
  • B-roll пропуск или меньше 5 вставок. Pipeline проверяет через Engine A, но если скипнуть QA — Meta может опубликовать, но ролик без B-roll теряет save-rate × 2.4 (данные батча май 2026). Всегда 5 вставок. Всегда.
4

Коммент → DM → Mini App → лид

Архитектура воронки · ManyChat · IG Graph webhook · TG Bot

Каждый ролик — это не просто ролик. Это воронка. Под каждый slug — своя страница, своё кодовое слово, свой лид-магнит. Ты читаешь эту страницу потому что написал слово ПАЙПЛАЙН в комментах или пришёл по ссылке из личного сообщения. Вот как это собрано технически.

Воронка автономна — ни одного ручного шага после публикации ролика. Параллельно с монтажом агент генерирует лендинг, деплоит его, настраивает ManyChat keyword, регистрирует webhook. К моменту публикации ролика всё готово.

8 шагов от комментария до лида

  1. Пользователь пишет кодовое слово в комментарии к ролику IG. ManyChat слушает comments webhook на посте. Keyword detection: точное совпадение (case-insensitive) с полем cta_keyword из конфига (например, ПАЙПЛАЙН).
  2. ManyChat отправляет автоматический DM через Instagram Messaging API. Задержка ≤60 сек от комментария. Тело DM: «Держи ссылку: defi-cap.com/audit/{slug}/» + кнопка «Открыть».
  3. Параллельно ManyChat отправляет публичный ответ на комментарий (опционально — повышает engagement для алгоритма): «Отправил ссылку в личку!»
  4. Пользователь открывает лендинг defi-cap.com/audit/{slug}/. UTM-параметры автоматически: ?utm_source=ig_reels&utm_medium=comment&utm_content={slug}. Лендинг уже задеплоен Build-агентом до публикации ролика.
  5. На лендинге — CTA «Получить полный гайд». Кнопка ведёт на https://t.me/defi_cap_bot?start=audit_{slug}. Deep-link формат: audit_{slug} (underscore, не дефис — ограничение Telegram start-параметра).
  6. TG Bot принимает /start audit_{slug}. Проверяет подписку на канал @defi_cap_blog. Если нет — предлагает подписаться (soft gate, не hard block). Отправляет анкету из 3 вопросов через inline keyboard.
  7. Анкета (3 вопроса): «Твоя ниша?» (DeFi / AI-автоматизация / Другое), «Главная задача?» (Монетизировать контент / Автоматизировать работу / Разобраться в DeFi), «Опыт с агентами?» (Нет / Немного / Активно использую).
  8. Ответы → Postgres, лид создан. Поля: user_id, source_slug, utm_content, niche, goal, experience, created_at. После анкеты бот открывает материал в Mini App и отправляет «Держи гайд + ffmpeg-скрипты».

ManyChat setup — keyword flow через API

Настройка в ManyChat для каждого нового ролика (автоматически через ManyChat API):

import requests

MANYCHAT_API = "https://api.manychat.com"
TOKEN = "Bearer {MANYCHAT_TOKEN}"

def setup_keyword_flow(keyword: str, dm_text: str, page_id: str):
    """Регистрирует keyword trigger + DM flow для нового ролика"""
    # Создать Flow с DM-ответом
    flow_payload = {
        "name": f"KW_{keyword}",
        "type": "automation",
        "trigger": {
            "type": "ig_comment_keyword",
            "keywords": [keyword],
            "match_type": "exact"  # или "contains"
        },
        "messages": [
            {
                "type": "ig_dm",
                "text": dm_text,
                "buttons": [{"type": "url", "caption": "Открыть", "url": "{LANDING_URL}"}]
            }
        ]
    }
    r = requests.post(f"{MANYCHAT_API}/fb/sending/sendFlow",
                      headers={"Authorization": TOKEN},
                      json=flow_payload)
    return r.json()

ManyChat Pro план ($15–65/мес) обязателен для comment-to-DM automation. Free plan позволяет только reply в thread, не DM. Ограничения 2026:

IG Graph webhook payload

ManyChat слушает comments нативно. Если нужен прямой webhook (для кастомной логики):

# FastAPI endpoint для IG Webhooks
from fastapi import FastAPI, Request
import hmac, hashlib

app = FastAPI()
IG_APP_SECRET = "{YOUR_APP_SECRET}"

@app.post("/webhook/ig-comments")
async def ig_comment_webhook(request: Request):
    # Signature verification
    body = await request.body()
    sig = request.headers.get("x-hub-signature-256", "")
    expected = "sha256=" + hmac.new(
        IG_APP_SECRET.encode(), body, hashlib.sha256
    ).hexdigest()
    if not hmac.compare_digest(sig, expected):
        return {"status": "invalid_signature"}

    data = await request.json()
    for entry in data.get("entry", []):
        for change in entry.get("changes", []):
            if change["field"] == "comments":
                comment_text = change["value"].get("text", "").upper()
                if comment_text == "ПАЙПЛАЙН":  # или из config
                    await handle_keyword_comment(change["value"])
    return {"status": "ok"}

Подписка на webhook при регистрации приложения: Field = comments, Object = instagram, Callback URL = https://defiserv.org/webhook/ig-comments, Verify token — произвольная строка из .env.

TG Bot deep-link формат

Формат deep-link для TG Mini App:

https://t.me/defi_cap_bot?start=audit_{slug}

Где {slug} — latin kebab-case, но в start-параметре Telegram только латиница, цифры и подчёркивания. Дефисы не поддерживаются в start. Поэтому audit_reels-pipelineaudit_reels_pipeline.

# TG Bot handler для deep-link
from aiogram import Router
from aiogram.filters import CommandStart
from aiogram.types import Message

router = Router()

@router.message(CommandStart(deep_link=True))
async def handle_start_deeplink(message: Message, command):
    payload = command.args  # например: "audit_reels_pipeline"
    if payload.startswith("audit_"):
        slug = payload[6:].replace("_", "-")  # обратно в kebab
        await send_survey(message.from_user.id, source_slug=slug)
    else:
        await message.answer("Привет! Напиши ПАЙПЛАЙН под последним роликом.")

Mini App survey → Postgres CRM

Postgres-схема для хранения лидов из воронки:

CREATE TABLE reel_leads (
    id          SERIAL PRIMARY KEY,
    tg_user_id  BIGINT NOT NULL,
    source_slug VARCHAR(100) NOT NULL,  -- slug ролика-источника
    utm_source  VARCHAR(50),
    utm_medium  VARCHAR(50),
    niche       VARCHAR(50),             -- DeFi / AI-automation / Other
    goal        VARCHAR(100),
    experience  VARCHAR(50),             -- none / some / active
    subscribed  BOOLEAN DEFAULT FALSE,   -- подписан на канал
    created_at  TIMESTAMPTZ DEFAULT NOW(),
    UNIQUE(tg_user_id, source_slug)     -- дедупликация
);

CREATE INDEX idx_reel_leads_slug ON reel_leads(source_slug);
CREATE INDEX idx_reel_leads_created ON reel_leads(created_at DESC);
Типичные конверсии (данные батча май 2026):
  • 1 ролик → в среднем 4–12 комментариев с кодовым словом
  • Комментарий → DM открытие: ~70% (ManyChat delivery rate)
  • DM открытие → клик на лендинг: ~45%
  • Лендинг → TG Bot: ~35%
  • TG Bot → анкета заполнена: ~65%
  • Итого: 1 ролик → 1–4 квалифицированных лида через воронку коммент→DM→бот
  • Кейс май 2026: 4 ролика → 17 лидов (avg 4.25/ролик) без платной рекламы
5

Одновременно, по расписанию

Meta Graph API v21.0 · YT Data API v3 · ASCII-filename · 48kHz

MOV готов, лендинг задеплоен, ManyChat keyword настроен. Агент публикует сам. Instagram Reels и YouTube Shorts одновременно — по расписанию, через официальные API. Ручного действия ноль.

Два независимых публикатора запускаются одним cron-job. Meta Graph API и YT Data API v3 — никаких сторонних сервисов, никаких буферов. Прямой вызов, полный контроль метаданных.

Meta Graph API v21.0 — три шага публикации

Шаг 1 — загрузка видео на CDN + создание контейнера:

# Сначала выгрузить файл на публично доступный CDN (S3, Cloudflare R2, или свой VPS)
# Имя файла — только ASCII! Кириллица = ошибка 400
VIDEO_URL="https://cdn.defiserv.org/reels/reels-autopipeline-2026-05-21.mp4"
IG_USER_ID="17841400000000000"  # из приложения Meta
ACCESS_TOKEN="EAAGm..."         # long-lived token, обновляется каждые 60 дней

curl -X POST \
  "https://graph.facebook.com/v21.0/${IG_USER_ID}/media" \
  -H "Content-Type: application/json" \
  -d '{
    "media_type": "REELS",
    "video_url": "'"${VIDEO_URL}"'",
    "caption": "Этот ролик снят, смонтирован и опубликован — без меня.\n\nПиши ПАЙПЛАЙН в комменты — пришлю архитектуру стека.\n\n#ClaudeCode #AIавтоматизация #DeFi #ffmpeg #Reels",
    "share_to_feed": true,
    "thumb_offset": 0
  }'
# Возвращает: {"id": "<CONTAINER_ID>"}

Обязательные permissions для App: instagram_business_basic, instagram_business_content_publish (старые instagram_basic + instagram_content_publish deprecated с 27 января 2025).

Шаг 2 — polling статуса (не публиковать до FINISHED):

import time
import requests

def wait_for_container(container_id: str, token: str, max_wait: int = 300) -> bool:
    """Ждёт FINISHED статуса, возвращает True если успешно"""
    url = f"https://graph.facebook.com/v21.0/{container_id}"
    for attempt in range(max_wait // 10):
        resp = requests.get(url, params={
            "fields": "status_code,status",
            "access_token": token
        }).json()
        status = resp.get("status_code")
        if status == "FINISHED":
            return True
        if status == "ERROR":
            print(f"Container error: {resp.get('status')}")
            return False
        time.sleep(10)
    return False  # timeout

Шаг 3 — публикация:

def publish_reel(ig_user_id: str, container_id: str, token: str) -> str:
    resp = requests.post(
        f"https://graph.facebook.com/v21.0/{ig_user_id}/media_publish",
        json={"creation_id": container_id, "access_token": token}
    )
    data = resp.json()
    if "id" in data:
        return data["id"]  # media_id опубликованного поста
    raise Exception(f"Publish error: {data}")

Технические требования к файлу (актуально 2026):

YT Data API v3 — videos.insert

YouTube Data API v3, videos.insert. Shorts — обычное видео с тегом #Shorts.

from google.oauth2.credentials import Credentials
from googleapiclient.discovery import build
from googleapiclient.http import MediaFileUpload

def upload_short(
    video_path: str,
    title: str,
    description: str,
    tags: list[str],
    credentials: Credentials
) -> str:
    """Возвращает video_id опубликованного Shorts"""
    youtube = build("youtube", "v3", credentials=credentials)

    body = {
        "snippet": {
            "title": title,               # ≤100 символов
            "description": description,    # ≤5000 символов
            "tags": tags,                  # список строк, 500 chars total
            "categoryId": "28",            # Science & Technology
            "defaultLanguage": "ru"
        },
        "status": {
            "privacyStatus": "public",     # public / unlisted / private
            "selfDeclaredMadeForKids": False,
            "madeForKids": False
        }
    }

    # Resumable upload — обязателен для файлов >5 MB
    media = MediaFileUpload(
        video_path,
        mimetype="video/mp4",
        resumable=True,
        chunksize=256 * 1024  # 256 KB chunks
    )

    request = youtube.videos().insert(
        part="snippet,status",
        body=body,
        media_body=media
    )

    response = None
    while response is None:
        status, response = request.next_chunk()
        if status:
            print(f"Upload {int(status.progress() * 100)}%")

    return response["id"]

Квота YT Data API: 10 000 units/день (free). videos.insert = 1 600 units. Итого: 6 Shorts/день в рамках квоты.

OAuth2 Setup (одноразово): Google Cloud Console → API & Services → Credentials → OAuth 2.0 Client ID (Desktop app) → скачать client_secrets.json → запустить flow авторизации через google-auth-oauthlib → получить token.json. Хранить в .env или Secret Manager — не в репо.

Метаданные — caption / description / теги

Стандартный шаблон caption/description для каждого ролика:

# IG Caption (до 2200 символов)
{HOOK_FIRST_LINE}

{BODY_2_3_LINES}

Пиши {CTA_KEYWORD} в комменты — пришлю {LEAD_MAGNET}.

{HASHTAGS_7_10}

# YT Description (до 5000 символов)
{HOOK_FIRST_LINE}

{FULL_BODY_3_5_LINES}

Полный гайд: https://defi-cap.com/audit/{SLUG}/

Пиши {CTA_KEYWORD} в комментарии — пришлю ссылку.

Теги: {TAGS_COMMA_SEPARATED}

#Shorts

Расписание и музыка

Оптимальное время публикации для DeFi/AI-ниши (данные батча + Instagram Insights):

Gotchas при публикации:
  • Meta: если media_publish вызван до FINISHED → 400. Всегда polling. Если container зависает в ERROR — создать новый контейнер, не retry старый
  • YT: видео ≤60 сек + 9:16 → автоматически Shorts. Не нужен отдельный endpoint — просто добавь #Shorts в описание
  • IG: нельзя добавить трек из библиотеки Instagram через API. Только аудио, встроенное в файл до загрузки
  • YT квота: 1600 units за upload. При 6 роликах/день = 9600 units из 10000 квоты. Остаток уходит на Analytics API — считай заранее
  • Meta: access_token живёт 60 дней. Настрой авто-обновление через /oauth/access_token?grant_type=fb_exchange_token раз в 45 дней
6

Метрики каждого ролика — вечером в Telegram

Daily report · 22:00 МСК · три источника

В 22:00 в личку приходит отчёт. По каждому активному ролику (последние 14 дней) — одна строка: просмотры, retention, комменты, клики, лиды. Открываешь — сразу видишь что сработало сегодня.

Агент дёргает три источника, сводит в таблицу, сравнивает с порогами — и если что-то выходит за рамки (хорошо или плохо), добавляет флаг. Один раз смотришь, одно решение принимаешь.

Метрики — что собирается

IG Insights доступны через ~6 часов после публикации, YT Analytics — через ~24 часа. Daily report в 22:00 МСК охватывает данные за предыдущие 48 часов (по IG) и 72 часа (по YT).

Формат TG-сообщения

TG-сообщение репорта (JetBrains Mono блок):

📊 Daily Report · 21.05.2026 · 22:00

Ролик                  │ Views │ Ret% │ Saves │ DMs  │ Лиды │ Паттерн
reels-autopipeline     │  1240 │  68% │   87  │  34  │   4  │ listicle/цифра
news-agent-5sys        │  3579 │  71% │  214  │ 120  │  12  │ counter-narrative
montage-zero-cost      │   890 │  54% │   41  │  18  │   2  │ tutorial

---
⬆️ Топ: news-agent-5sys · Ret% 71% · Лидов 12
⚠️  Ниже avg: montage-zero-cost · Ret% 54% (< 60% threshold)

Источники: IG Insights + YT Analytics + Postgres CRM
Следующий постинг: 22.05 10:00 МСК → reels-budget-breakdown

Алертинг — когда добавляется флаг

Бенчмарки DeFi/AI-ниши

Бенчмарки для DeFi/AI-ниши (50–500K подписчиков, данные батча май 2026):

7

$125–135/мес — куда именно

Бюджет в деталях · по строкам · с альтернативами

Никакой магии. Каждый доллар виден. Разбивка по статьям с альтернативами для каждой.

Breakdown по строкам

Альтернативы — как сократить бюджет

8

От нуля до первой публикации — 7 шагов

Чек-лист · 2–3 дня · от установки стека до Submit

Весь стек звучит громоздко. На деле первый ролик можно выпустить за 2–3 дня. Вот конкретный чек-лист.

7 шагов с временными рамками

  1. День 1 (3 часа): Стек и окружение.
    • Арендовать VPS (Hetzner или Hostman, Ubuntu 22.04, 4 GB RAM)
    • Установить: Python 3.11, ffmpeg 6+, Node.js 20+, Playwright (pip install playwright && playwright install chromium)
    • Установить faster-whisper: pip install faster-whisper==1.2.1
    • Склонировать build_reel.py из эталона, прогнать на тестовом MOV
    • Результат: конвейер работает локально на тестовом видео
  2. День 1 (1 час): profile.yaml.
    • Создать profile.yaml: ниша, бренд-войс, target_duration, cta_keyword, banned_patterns: []
    • Создать queue.json: []
    • Проверить: агент читает профиль без ошибок
  3. День 2 (2 часа): Meta App + YT OAuth.
    • Meta for Developers → создать App → добавить Instagram Basic Display + Content Publishing permissions
    • Получить ig_user_id + long-lived access token
    • Google Cloud Console → YouTube Data API v3 → OAuth Client → скачать client_secrets.json → авторизоваться
    • Проверить: тестовая публикация в черновик (privacyStatus: "private") на YT + тест container creation на Meta
  4. День 2 (1 час): ManyChat flow.
    • Подключить IG Business аккаунт к ManyChat
    • Создать первый keyword flow: слово = твоё кодовое, DM = текст с ссылкой на лендинг
    • Протестировать: написать слово с другого аккаунта → получить DM
  5. День 3 (2 часа): Pre-research агент.
    • Настроить Virlo MCP (API key)
    • Залить research_agent.py с prompt_template из section 1
    • Запустить вручную: убедиться что агент возвращает 2–3 сценария в нужном формате
    • Настроить cron: 30 3 * * * python3 /home/reels/research_agent.py (06:30 МСК = 03:30 UTC)
  6. День 3 (1 час): Первая съёмка.
    • Взять первый сценарий из агента
    • Снять по чек-листу section 2 (10 пунктов)
    • Отправить в дроп-папку — проверить что watchdog подхватил и запустил build_reel.py
    • Дождаться FINAL_with_cover.mp4 (~7 минут)
  7. День 3 (30 минут): Первая публикация.
    • Запустить publisher.py: Meta + YT одновременно
    • Проверить в IG и YT через 5 минут — ролик опубликован
    • Настроить daily_report cron: 0 19 * * * python3 /home/reels/daily_report.py (22:00 МСК = 19:00 UTC)
    • Готово. Смотришь вечерний отчёт и видишь первые метрики.

Один час в неделю на запись, всё остальное — машина

Итог: что получаешь на выходе

Честный итог: сборка занимает 6 месяцев, не 3 дня. build_reel.py работает — но он написан под мой стек, мой бренд-войс, мою нишу. Под твой нужно адаптировать profile.yaml, переписать prompt_template, откалибровать silence detection под темп твоей речи. Это не plug-and-play.

Второй честный момент: HUMAN-in-the-loop здесь реальный. Агент не знает когда ты облажался на съёмке и не заметил. Агент не чувствует что тема устарела за ночь (быстрые новостные хуки). Агент не заменяет твой личный опыт в сценарии — он структурирует, ты наполняешь. QA Engine на Stage 14.5 ловит технический брак, но смысловой брак — на тебе.

Третий: бюджет $125–135/мес — это дополнительно к Claude Code ($120 уже есть если используешь). Реальная новая статья: VPS ($20) + музыка ($15) + ManyChat ($15) + Virlo ($15) = ~$65/мес. Остальное уже оплачено или бесплатно.

Бюджет базовый ~$65/мес (если CC уже есть): VPS $20 + музыка $15 + ManyChat $15 + Virlo $15. Дополнительные материалы:
  • ~/brain/reels-production/_etalon/ — эталоны pipeline
  • ~/brain/reels-production/_pipeline/build_reel.py — master script
  • content/reels/01-pipeline.md — 18 stages подробно
  • content/reels/07-broll-format-D-html.md — 12 типов B-roll HTML

Забери архитектуру всего пайплайна + ffmpeg-скрипты + Playwright-шаблоны

Внутри: scenario-agent prompt-template с profile.yaml, ffmpeg-команды всех 5 проходов, broll-html шаблоны (12 типов), ManyChat flow JSON, Meta Graph API + YT Data API setup с реальными payload'ами, конфиг daily-report агента. Всё открывается в Mini App после короткой анкеты (3 вопроса, 2 минуты).

Открыть в Telegram →
КОДОВОЕ СЛОВО · ПАЙПЛАЙН