«Аниме-завод» открыт: выкладываю код конвейера, который сам режет эпизоды на Shorts. ffmpeg.. ffmpeg. llm.. ffmpeg. llm. Open source.. ffmpeg. llm. Open source. opencv.. ffmpeg. llm. Open source. opencv. python.. ffmpeg. llm. Open source. opencv. python. youtube shorts.. ffmpeg. llm. Open source. opencv. python. youtube shorts. автоматизация.. ffmpeg. llm. Open source. opencv. python. youtube shorts. автоматизация. Алгоритмы.. ffmpeg. llm. Open source. opencv. python. youtube shorts. автоматизация. Алгоритмы. архитектура.. ffmpeg. llm. Open source. opencv. python. youtube shorts. автоматизация. Алгоритмы. архитектура. видео.. ffmpeg. llm. Open source. opencv. python. youtube shorts. автоматизация. Алгоритмы. архитектура. видео. искусственный интеллект.. ffmpeg. llm. Open source. opencv. python. youtube shorts. автоматизация. Алгоритмы. архитектура. видео. искусственный интеллект. Обработка изображений.

За последнее время я написал три статьи про систему, которая превращает длинные видео в вертикальные клипы: общий обзор «аниме-завода», разбор виртуальной камеры с face tracking и рассказ о том, почему связка «транскрипт + LLM» почти всегда даёт мусор.

Комментарий, который повторялся под всеми тремя, был один и тот же: «покажи код».

Показываю.

github.com/ialakey/shorts-factory

Это витринная ветка большого приватного проекта: MVP, в котором оставлен полный путь «взял длинное видео → получил готовый вертикальный клип», а всё остальное вырезано. Ниже — что именно внутри, как код соотносится с тремя предыдущими статьями и что пришлось выкинуть.

Видеодемонстрация проекта

Видеодемонстрация проекта

▶ Видеодемонстрация — «Аниме ЗАВОД»
Канал @AnimeFactorio, ради которого всё и затевалось

Что это в одном абзаце

На вход — эпизод целиком. На выходе — несколько готовых роликов 9:16 с динамическим кадрированием, пословными субтитрами, водяным знаком, обработанным звуком, вычищенными метаданными и отправкой в Telegram. Между этими двумя точками:

эпизод.mp4  →  транскрипт  →  сигналы (аудио / лица / склейки / темп / hooks)  →  LLM-отбор моментов
            →  монтажная сборка сегментов  →  виртуальная камера 9:16  →  слои (субтитры, ватермарк, музыка)
            →  пересборка метаданных  →  Telegram

Тринадцать этапов, каждый включается и выключается отдельно. Никаких флагов командной строки: поведение канала целиком описано одним config.yaml, потому что основная работа здесь — это подбор параметров, а подбору нужен файл, который можно диффать и откатывать.

По цифрам: ~7 000 строк основного кода, 249 тестов на ~3 000 строк, полностью прокомментированный демо-конфиг на 344 строки.

Как код соотносится со статьями

Три статьи описывали три разных слоя. Вот где они лежат в репозитории — это, собственно, и есть главная причина, по которой я пишу эту статью: связать текст с исполняемым кодом.

Статья 1 → архитектура целиком

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

В коде это:

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

Где живёт

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

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

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

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

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

  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 + транскрипт почти всегда дают мусор» была самой спорной в комментариях, поэтому по ней покажу конкретику.

Тезис был такой: не надо требовать от одной модели правоты во всём. Сцена сначала описывается по нескольким независимым осям, кандидаты детерминированно ранжируются до того, как модель их увидит, а модель делает ровно то, в чём хороша — судит, интересен ли момент.

Скоринг живёт в analysis/moment_scorer.py, и веса вынесены в конфиг открытым текстом:

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 не принимает её слова на веру:

  • duration пересчитывается как сумма сегментов, а не берётся из ответа;

  • пересечения сегментов разводятся, границы притягиваются к паузам речи и монтажным склейкам;

  • моменты вне диапазона gpt.min_timegpt.max_time отбрасываются;

  • если после фильтрации ничего не осталось — запрос повторяется (до трёх попыток) с уточняющим суффиксом, а если и это не помогло, список добивается эвристическими кандидатами.

То есть эпизод не падает из-за плохого ответа модели — он деградирует до чисто алгоритмического отбора. Это, пожалуй, главный практический вывод всей серии: качество тут делается не поиском идеального решения, а систематической фильтрацией плохих.

И момент — это не «вырезать кусок с 5:30 по 6:00». Это монтажная сборка: segments могут браться из разных частей эпизода, паузы вырезаются, hook всегда в первом сегменте, последний — клиффхэнгер или петля к началу.

Что в статьях не было, а в репозитории есть

Тесты. 249 штук, полностью герметичные: OpenAI, Telegram и Kodik замоканы, модуль whisper подменяется заглушкой в conftest.py — иначе CI тянул бы torch и качал модели. mediapipe и anime_parsers_ru в CI намеренно не ставятся: код обязан деградировать на fallback-детекторы, и это проверяется тестом, а не обещанием в README.

Отдельно есть test_render_stages.py, где каждый этап make_clips реально рендерит валидный mp4, и test_pipeline_e2e.py — сквозной прогон канала от видео до Telegram. CI гоняет быстрый слой и рендер-слой разными шагами, чтобы сразу было видно, сломалась логика или именно рендер.

Слои рендера. Субтитры рендерятся по словам, с опциональным переразпознаванием клипа более тяжёлой моделью 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), pilmoji 2.0.4 жёстко требует emoji 1.x, а Kodik у многих провайдеров заблокирован (этап это переживает и мягко пропускается).

Итого

Три статьи описывали идеи. Этот репозиторий — то же самое, но в виде кода, который запускается и покрыт тестами.

Сквозная мысль всей серии никуда не делась: ни один сигнал не является достаточным. Громкий момент ≠ интересный, красивая реплика ≠ понятная без контекста, идеальная детекция лица ≠ приятное движение камеры. Всё работающее здесь получилось из комбинации слабых сигналов и из аккуратной деградации, когда какой-то из них отвалился.

Если разбирать конкретный слой — вот навигация:

Хочу понять

Читать

Смотреть код

Общую архитектуру и принципы

статья 1

core/channel_processor.py, docs/ARCHITECTURE.md

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

статья 3

analysis/moment_scorer.py, analysis/moment_validator.py, analysis/gpt_analyzer.py

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

статья 2

rendering/face_detector.py

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

channels/DemoChannel/config.yaml, docs/CONFIG_REFERENCE.md

Английские версии первых двух статей: обзор системы, виртуальная камера.

Репозиторий: github.com/ialakey/shorts-factory Звёзды, issues и PR приветствуются. Особенно интересно, если кто-то приспособит это под не-аниме контент: подкасты, лекции, стримы — вся механика к домену не привязана, привязаны только веса, и они в конфиге.

Автор: i_alakey

Источник