- BrainTools - https://www.braintools.ru -
За последнее время я написал три статьи про систему, которая превращает длинные видео в вертикальные клипы: общий обзор «аниме-завода» [1], разбор виртуальной камеры с face tracking [2] и рассказ о том, почему связка «транскрипт + LLM» почти всегда даёт мусор [3].
Комментарий, который повторялся под всеми тремя, был один и тот же: «покажи код».
Показываю.
→ github.com/ialakey/shorts-factory [4]
Это витринная ветка большого приватного проекта: MVP, в котором оставлен полный путь «взял длинное видео → получил готовый вертикальный клип», а всё остальное вырезано. Ниже — что именно внутри, как код соотносится с тремя предыдущими статьями и что пришлось выкинуть.
▶ Видеодемонстрация — «Аниме ЗАВОД» [5]
Канал @AnimeFactorio [6], ради которого всё и затевалось
На вход — эпизод целиком. На выходе — несколько готовых роликов 9:16 с динамическим кадрированием, пословными субтитрами, водяным знаком, обработанным звуком, вычищенными метаданными и отправкой в Telegram. Между этими двумя точками:
эпизод.mp4 → транскрипт → сигналы (аудио / лица / склейки / темп / hooks) → LLM-отбор моментов
→ монтажная сборка сегментов → виртуальная камера 9:16 → слои (субтитры, ватермарк, музыка)
→ пересборка метаданных → Telegram
Тринадцать этапов, каждый включается и выключается отдельно. Никаких флагов командной строки: поведение [7] канала целиком описано одним config.yaml, потому что основная работа здесь — это подбор параметров, а подбору нужен файл, который можно диффать и откатывать.
По цифрам: ~7 000 строк основного кода, 249 тестов на ~3 000 строк, полностью прокомментированный демо-конфиг на 344 строки.
Три статьи описывали три разных слоя. Вот где они лежат в репозитории — это, собственно, и есть главная причина, по которой я пишу эту статью: связать текст с исполняемым кодом.
«Как я построил аниме-завод» [1] была про принципы: независимые модули вместо монолитной end-to-end модели, fail-soft вместо fail-fast, явные промежуточные артефакты.
В коде это:
|
Принцип из статьи |
Где живёт |
|---|---|
|
Оркестрация по этапам |
|
|
Fail-soft |
падение канала не роняет остальные; отсутствие |
|
Явные артефакты |
|
|
Отладка этапа в изоляции |
|
Отдельно про последнюю строку. Пайплайн, в котором падение на девятом этапе стоит тебе первых восьми, — это пайплайн, который никто не будет крутить итеративно. Поэтому в debug-режиме, если моменты ещё не считались, а пост-клиповые этапы включены, берётся весь ролик целиком: чтобы можно было отлаживать субтитры или ватермарк, не гоняя Whisper и LLM по кругу.
«Я научил виртуальную камеру быть оператором» [2] — про то, почему центровой кроп выбрасывает половину кадра, а наивное следование за лицом даёт камеру, на которую физически неприятно смотреть.
Весь описанный алгоритм — в одном модуле:
Каскад детекторов: MediaPipe → YuNet (ONNX через OpenCV) → Haar Cascade. Отказ любого бэкенда не ломает этап.
Выбор героя по saliency (уверенность + крупность + центральность) с гистерезисом: face_switch_margin и face_switch_hold_s не дают камере пинг-понговать между персонажами.
Фильтрация: анти-джерк (жёсткий лимит скачка между кадрами) + low-pass.
Физика: a = k·error − c·v с лимитами скорости и ускорения, мёртвой зоной, предиктивным упреждением и микролагом «живой руки».
Композиция: eye-level lift, смещение по правилу третей, защитные поля до краёв.
Склейки: на монтажном резе камера не переезжает через кадр, а мгновенно пересобирается.
Ken Burns как fallback: без лиц камера не замирает, а мягко панорамирует вокруг последнего известного положения героя.
Плюс то, о чём в статье я не говорил: профили static / operator / action задают базовый темперамент камеры, а любой ключ в dynamic_shorts переопределяет профиль точечно. Анализ идёт на 8 к/с, состояния камеры интерполируются на полную частоту кадров — считать детекцию на каждом кадре незачем.
«Почему LLM + транскрипт почти всегда дают мусор» [3] была самой спорной в комментариях, поэтому по ней покажу конкретику.
Тезис был такой: не надо требовать от одной модели правоты во всём. Сцена сначала описывается по нескольким независимым осям, кандидаты детерминированно ранжируются до того, как модель их увидит, а модель делает ровно то, в чём хороша — судит, интересен ли момент.
Скоринг живёт в analysis/moment_scorer.py [9], и веса вынесены в конфиг открытым текстом:
moment_scoring:
cell_s: 0.5 # разрешение временной сетки сигналов
step_s: 1.0 # шаг скользящего окна
max_candidates: 12 # сколько остаётся после NMS
prompt_limit: 8 # сколько из них реально уходит в промпт
weights: # нормализуются к сумме 1
transcript: 0.24 # плотность и содержание реплик
audio: 0.20 # эмоциональные пики звука
face: 0.14 # есть ли на чём держать вертикальный фокус
scene: 0.12 # смены планов и движение
pacing: 0.14 # темп, отсутствие провисаний
hook: 0.16 # сила входа в первые 1–2 секунды
score = Σ weight_i · signal_i, затем NMS по пересечению окон. В промпт уходит верхушка списка — восемь кандидатов вместо всего транскрипта. Это заметно стабильнее свободного отбора и попутно дешевле по токенам.
Вторая половина, которой в статье почти не было, — что происходит после ответа модели. analysis/moment_validator.py [10] не принимает её слова на веру:
duration пересчитывается как сумма сегментов, а не берётся из ответа;
пересечения сегментов разводятся, границы притягиваются к паузам речи и монтажным склейкам;
моменты вне диапазона gpt.min_time–gpt.max_time отбрасываются;
если после фильтрации ничего не осталось — запрос повторяется (до трёх попыток) с уточняющим суффиксом, а если и это не помогло, список добивается эвристическими кандидатами.
То есть эпизод не падает из-за плохого ответа модели — он деградирует до чисто алгоритмического отбора. Это, пожалуй, главный практический вывод всей серии: качество тут делается не поиском идеального решения, а систематической фильтрацией плохих.
И момент — это не «вырезать кусок с 5:30 по 6:00». Это монтажная сборка: segments могут браться из разных частей эпизода, паузы вырезаются, hook всегда в первом сегменте, последний — клиффхэнгер или петля к началу.
Тесты. 249 штук, полностью герметичные: OpenAI, Telegram и Kodik замоканы, модуль whisper подменяется заглушкой в conftest.py [11] — иначе CI тянул бы torch и качал модели. mediapipe и anime_parsers_ru в CI намеренно не ставятся: код обязан деградировать на fallback-детекторы, и это проверяется тестом, а не обещанием в README.
Отдельно есть test_render_stages.py [12], где каждый этап make_clips реально рендерит валидный mp4, и test_pipeline_e2e.py — сквозной прогон канала от видео до Telegram. CI гоняет быстрый слой и рендер-слой разными шагами, чтобы сразу было видно, сломалась логика [13] или именно рендер.
Слои рендера. Субтитры рендерятся по словам, с опциональным переразпознаванием клипа более тяжёлой моделью Whisper и последующей LLM-чисткой текста: правится орфография и ровно одно ключевое слово оборачивается в <hl>…</hl>, которое рендерер красит отдельным стилем. Цветные эмодзи — через Pilmoji. Плюс soft-knee компрессор диалоговой дорожки, размытый фон из исходника, титры с автоподбором кегля.
Доставка. spoof_metadata пересобирает контейнер без перекодирования (-c copy), полностью стирает исходные метаданные (-map_metadata -1) и подставляет правдоподобные данные редактора. Оригинал удаляется только после успешной пересборки — при падении ffmpeg клип не теряется.
Честно, чтобы не было вопросов:
автозагрузка на YouTube и TikTok — осталась в приватном проекте;
планировщик публикаций;
контур аналитики (обратная связь по метрикам роликов в веса скоринга).
Витрина заканчивается на «готовый клип на диске, отправленный в Telegram». Все параметры конфигурации сброшены к нейтральным значениям — это дефолты для первого запуска, а не «правильные» числа. Ключей, доступов и чужих медиафайлов в репозитории нет: шрифты положены только те, что под SIL OFL, музыку и фоны нужно принести свои.
git clone https://github.com/ialakey/shorts-factory
cd shorts-factory
python -m venv .venv && source .venv/bin/activate # Windows: ..venvScriptsActivate.ps1
pip install -r requirements.txt
cp .env.example .env # OPENAI_API_KEY обязателен
python app.py
Нужны Python 3.12+ и ffmpeg с ffprobe в PATH — именно бинарники, потому что они вызываются напрямую как команды; того, что внутри imageio-ffmpeg, не хватит, там нет ffprobe. Есть Dockerfile, если возиться с системными зависимостями OpenCV не хочется.
Рекомендуемый порядок: прогнать отладочный запуск на test_data/ с debug: true, посмотреть промежуточные артефакты, покрутить веса — и только потом ставить реальные эпизоды.
Известные шероховатости тоже перечислю, они все в README: используется legacy-API openai==0.28.0, OPENAI_API_KEY требуется даже для сценариев без LLM (проверка стоит на импорте config.py [14]), pilmoji 2.0.4 жёстко требует emoji 1.x, а Kodik у многих провайдеров заблокирован (этап это переживает и мягко пропускается).
Три статьи описывали идеи. Этот репозиторий — то же самое, но в виде кода, который запускается и покрыт тестами.
Сквозная мысль всей серии никуда не делась: ни один сигнал не является достаточным. Громкий момент ≠ интересный, красивая реплика ≠ понятная без контекста, идеальная детекция лица ≠ приятное движение камеры. Всё работающее здесь получилось из комбинации слабых сигналов и из аккуратной деградации, когда какой-то из них отвалился.
Если разбирать конкретный слой — вот навигация:
|
Хочу понять |
Читать |
Смотреть код |
|---|---|---|
|
Общую архитектуру и принципы |
статья 1 [1] |
|
|
Как выбирается момент |
статья 3 [3] |
|
|
Виртуальную камеру |
статья 2 [2] |
|
|
Что вообще можно покрутить |
— |
|
Английские версии первых двух статей: обзор системы [19], виртуальная камера [20].
Репозиторий: github.com/ialakey/shorts-factory [4] Звёзды, issues и PR приветствуются. Особенно интересно, если кто-то приспособит это под не-аниме контент: подкасты, лекции, стримы — вся механика к домену не привязана, привязаны только веса, и они в конфиге.
Автор: i_alakey
Источник [21]
Сайт-источник BrainTools: https://www.braintools.ru
Путь до страницы источника: https://www.braintools.ru/article/35006
URLs in this post:
[1] общий обзор «аниме-завода»: https://habr.com/ru/articles/1014810/
[2] виртуальной камеры с face tracking: https://habr.com/ru/articles/1021278/
[3] почему связка «транскрипт + LLM» почти всегда даёт мусор: https://habr.com/ru/articles/1021552/
[4] github.com/ialakey/shorts-factory: https://github.com/ialakey/shorts-factory
[5] ▶ Видеодемонстрация — «Аниме ЗАВОД»: https://youtu.be/xu8sg_mXEh0
[6] @AnimeFactorio: https://www.youtube.com/@AnimeFactorio
[7] поведение: http://www.braintools.ru/article/9372
[8] processor.py: http://processor.py
[9] scorer.py: http://scorer.py
[10] validator.py: http://validator.py
[11] conftest.py: http://conftest.py
[12] stages.py: http://stages.py
[13] логика: http://www.braintools.ru/article/7640
[14] config.py: http://config.py
[15] ARCHITECTURE.md: http://ARCHITECTURE.md
[16] analyzer.py: http://analyzer.py
[17] detector.py: http://detector.py
[18] REFERENCE.md: http://REFERENCE.md
[19] обзор системы: https://habr.com/ru/articles/1022250/
[20] виртуальная камера: https://habr.com/ru/articles/1022298/
[21] Источник: https://habr.com/ru/articles/1078032/?utm_source=habrahabr&utm_medium=rss&utm_campaign=1078032
Нажмите здесь для печати.