Привет, Хабр! 👋
В этой статье я хочу поделиться опытом разработки пет-проекта, который превратился в полноценный инструмент для автоматической генерации коротких видео (Shorts, Reels, TikTok). Идея проста: на входе — тема (например, "История Римской Империи за 1 минуту"), на выходе — готовый видеоролик с озвучкой, субтитрами и сгенерированным видеорядом.
Но вместо того, чтобы накидать "спагетти-код" в одном файле main.py, я решил подойти к задаче как инженер и построить систему на принципах Clean Architecture.
🎯 Зачем?
Генерация видео — это сложный пайплайн:
1. Написать сценарий.
2. Придумать визуальный стиль.
3. Сгенерировать картинки (Midjourney, Flux).
4. Оживить картинки в видео (Kling, Runway, Sora).
5. Озвучить текст (TTS).
6. Собрать всё вместе с субтитрами.
API меняются, модели выходят новые каждую неделю. Сегодня лучший визуал у Flux, завтра у Midjourney v7. Сегодня видео делаем в Runway, завтра в Kling. Жесткая привязка к конкретным API убила бы проект через месяц.
Поэтому Clean Architecture здесь не роскошь, а необходимость.
🏗 Архитектура
Проект разбит на слои, следуя классической "луковой" архитектуре:
1. Domain (Entities): Pydantic-модели, описывающие суть (VideoScript, Scene, Character). Они ничего не знают о внешнем мире.
2. Interfaces: Абстрактные классы (VideoGenerator, ScriptGenerator). Контракты, которые должны соблюдать внешние сервисы.
3. Services (Use Cases): Бизнес-логика. Здесь живут "Агенты": Сценарист, Арт-директор, Режиссер монтажа.
4. Infrastructure: Реализации интерфейсов (API клиентов Comet, Yandex, OpenAI и т.д.).
Стек технологий
* Python 3.11+
* Pydantic: Для строгой типизации данных между слоями.
* Asyncio: Генерация контента — долгий процесс, все запросы к API дол��ны быть асинхронными.
* LangChain: Для удобной работы с LLM.
* MoviePy: Для финальной сборки видео.
🤖 Команда Агентов
Вместо одного "умного" промта, который делает всё, я разделил обязанности между специализированными агентами.
1. Screenwriter (Сценарист)
Его задача — только текст. Он не думает о том, как это будет выглядеть. Он выдает JSON со сценами, диалогами и таймингами.
class Scene(BaseModel): scene_number: int narration: str # Текст для озвучки duration_seconds: int = 5 # Визуальные поля пока пустые image_prompt: str | None = None video_prompt: str | None = None
2. Art Director (Арт-директор)
Вот здесь начинается магия. Арт-директор получает сухой сценарий и превращает его в визуальное описание.
Недавно я разделил логику так, что Арт-директор может генерировать промты с нуля, если Сценарист их не предоставил.
Он использует специальные промты для LLM, чтобы добавить "кинематографичности": cinematic lighting, 8k, dramatic atmosphere.
# app/services/art_director.py async def enhance_script(self, script: VideoScript) -> VideoScript: """Арт-директор проходит по всем сценам и создает визуальный стиль.""" for scene in script.scenes: if not scene.image_prompt: # Если сценарист не дал визуал, придумываем сами scene.image_prompt = await self._generate_scene_visuals(scene) else: # Иначе улучшаем то, что есть scene.image_prompt = await self._enhance_prompt(scene.image_prompt) return scriptt
3. Motion Director (Постановщик движения)
Статичная картинка — это скучно. Motion Director добавляет инструкции для видео-генератора.
* "Camera zoom in"
* "Character turns head left"
* "Wind blowing through hair"
Он использует шаблон motion_director.txt, который заставляет LLM выдавать строгие механические инструкции, понятные нейросетям типа Kling или Runway.
## 🚀 Pipeline (Оркестратор)
Все это связывается в классе VideoPipeline. Это фасад, который управляет потоком данных.
# app/services/pipeline.py async def generate_video(self, topic: str): # 1. Сценарий script = await self.script_gen.generate_script(topic) # 2. Визуальный стиль script = await self.art_director.enhance_script(script) # 3. Движение script = await self.motion_director.enhance_script(script) # 4. Параллельная генерация ассетов await asyncio.gather( self.generate_images(script), self.generate_voiceovers(script) ) # 5. Оживление (Image-to-Video) await self.generate_videos(script) # 6. Монтаж final_video = await self.video_editor.edit_video(script) return final_video
Интересный момент: мы используем asyncio.gather для параллельной генерации. Пока генерируются картинки для 10 сцен, параллельно может идти озвучка. Это экономит кучу времени.
🔌 Адаптеры и Интерфейсы
Благодаря интерфейсам, я могу менять провайдеров видео "на лету".
class VideoGenerator(ABC): @abstractmethod async def generate_video(self, image_path: str, prompt: str) -> str: pass
У меня есть CometVideoGenerator (наш внутренний сервис), KieVideoGenerator (для Grok), и я легко могу добавить RunwayVideoGenerator.
В dependencies.py я просто проверяю конфиг и подставляю нужную реализацию:
if "grok" in settings.VIDEO_GENERATION_MODEL: video_gen = KieVideoGenerator(...) else: video_gen = CometVideoGenerator(...)
🛠 CLI и новые фичи
Недавно добавил возможность генерировать видео только по картинке, игнорируя текстовые промты. Это полезно, когда нейросеть (например, Kling) лучше понимает контекст из самой картинки, а текст её только сбивает.
В cli.py добавил флаг --video-image-only, который прокидывается в пайплайн:
# В пайплайне if not self.use_video_prompt: prompt = None # Генератор получит только image_path
📈 Результат
В итоге получилась гибкая система. Я могу:
1. Поменять LLM для сценария (OpenAI -> Mistral -> Local Llama).
2. Поменять генератор картинок (Midjourney -> Flux).
3. Поменять генератор видео.
И всё это без переписывания бизнес-логики.
Что дальше?
* Веб-интерфейс (FastAPI + React).
* Интерактивный режим (правка сценария человеком перед генерацией).
* Поддержка вертикального/горизонтального форматов на уровне кропа.
Ссылка на проект: GitHub
Пишите в комментариях, какие инструменты для AI видео используете вы!
