- BrainTools - https://www.braintools.ru -

«Аниме-завод» открыт: выкладываю код конвейера, который сам режет эпизоды на Shorts

За последнее время я написал три статьи про систему, которая превращает длинные видео в вертикальные клипы: общий обзор «аниме-завода» [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 → архитектура целиком

«Как я построил аниме-завод» [1] была про принципы: независимые модули вместо монолитной end-to-end модели, fail-soft вместо fail-fast, явные промежуточные артефакты.

В коде это:

Принцип из статьи

Где живёт

Оркестрация по этапам

core/channel_processor.py [8] — один канал, список включённых этапов

Fail-soft

падение канала не роняет остальные; отсутствие mediapipe не роняет детекцию; недоступность Kodik мягко пропускает этап

Явные артефакты

_transcript.txt, _moments.json, _candidates.json, _signals.json, *_chatgpt_payload.txt — всё пишется рядом с клипами

Отладка этапа в изоляции

debug: true: вход из test_data/, выход в output_test_data/, дампы всех промежуточных данных

Отдельно про последнюю строку. Пайплайн, в котором падение на девятом этапе стоит тебе первых восьми, — это пайплайн, который никто не будет крутить итеративно. Поэтому в debug-режиме, если моменты ещё не считались, а пост-клиповые этапы включены, берётся весь ролик целиком: чтобы можно было отлаживать субтитры или ватермарк, не гоняя Whisper и LLM по кругу.

Статья 2 → rendering/face_detector.py

«Я научил виртуальную камеру быть оператором» [2] — про то, почему центровой кроп выбрасывает половину кадра, а наивное следование за лицом даёт камеру, на которую физически неприятно смотреть.

Весь описанный алгоритм — в одном модуле:

  1. Каскад детекторов: MediaPipe → YuNet (ONNX через OpenCV) → Haar Cascade. Отказ любого бэкенда не ломает этап.

  2. Выбор героя по saliency (уверенность + крупность + центральность) с гистерезисом: face_switch_margin и face_switch_hold_s не дают камере пинг-понговать между персонажами.

  3. Фильтрация: анти-джерк (жёсткий лимит скачка между кадрами) + low-pass.

  4. Физика: a = k·error − c·v с лимитами скорости и ускорения, мёртвой зоной, предиктивным упреждением и микролагом «живой руки».

  5. Композиция: eye-level lift, смещение по правилу третей, защитные поля до краёв.

  6. Склейки: на монтажном резе камера не переезжает через кадр, а мгновенно пересобирается.

  7. Ken Burns как fallback: без лиц камера не замирает, а мягко панорамирует вокруг последнего известного положения героя.

Плюс то, о чём в статье я не говорил: профили static / operator / action задают базовый темперамент камеры, а любой ключ в dynamic_shorts переопределяет профиль точечно. Анализ идёт на 8 к/с, состояния камеры интерполируются на полную частоту кадров — считать детекцию на каждом кадре незачем.

Статья 3 → analysis/

«Почему 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_timegpt.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]

core/channel_processor.py [8], docs/ARCHITECTURE.md [15]

Как выбирается момент

статья 3 [3]

analysis/moment_scorer.py [9], analysis/moment_validator.py [10], analysis/gpt_analyzer.py [16]

Виртуальную камеру

статья 2 [2]

rendering/face_detector.py [17]

Что вообще можно покрутить

channels/DemoChannel/config.yaml, docs/CONFIG_REFERENCE.md [18]

Английские версии первых двух статей: обзор системы [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

www.BrainTools.ru

Rambler's Top100